001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.dataAccess; 019 020import com.fasterxml.jackson.annotation.JsonIgnore; 021import io.stallion.Context; 022import io.stallion.dataAccess.db.Converter; 023import io.stallion.dataAccess.db.converters.JsonListConverter; 024import io.stallion.settings.SecondaryDomain; 025import io.stallion.settings.Settings; 026import io.stallion.utils.DateUtils; 027import io.stallion.utils.GeneralUtils; 028 029import javax.persistence.Column; 030import java.time.ZonedDateTime; 031import java.time.format.DateTimeFormatter; 032import java.util.List; 033 034import static io.stallion.Context.settings; 035import static io.stallion.utils.Literals.empty; 036import static io.stallion.utils.Literals.list; 037 038/** 039 * A base class for model objects that can be rendered as web pages. 040 */ 041public class StandardDisplayableModel extends ModelBase implements Displayable { 042 // Unfortunately these fields all have to be null, so when the object is passed in from 043 // a JSON web request, we can identify which fields are set and which are not 044 private String title; 045 private String slug; 046 private String content; 047 private String originalContent; 048 private String template; 049 private ZonedDateTime publishDate; 050 private Boolean draft = false; 051 private String author = ""; 052 private String metaDescription = ""; 053 private String overrideDomain = null; 054 private String relCanonical = ""; 055 private String metaKeywords = ""; 056 private String titleTag = ""; 057 private String image = ""; 058 private String ogType = "article"; 059 private String previewKey = ""; 060 private List<String> oldUrls = list(); 061 private String contentType = ""; 062 063 /** 064 * Used in the page <title> tag. 065 * 066 * @return 067 */ 068 @Column 069 public String getTitle() { 070 return title; 071 } 072 073 public StandardDisplayableModel setTitle(String title) { 074 this.title = title; 075 return this; 076 } 077 078 /** 079 * The user settable path component of the URL at which this page can be accessed, before 080 * applying the override/secondary domain (if applicable) 081 * 082 * @return 083 */ 084 @Override 085 @Column 086 @UniqueKey 087 public String getSlug() { 088 return this.slug; 089 } 090 091 /** 092 * The path component of the URL at which this page can be accessed. This is the slug by default, 093 * but if on an override domain, the slug minus the root 094 */ 095 @Override 096 public String getRealHttpPath() { 097 if (empty(getOverrideDomain())) { 098 return getSlug(); 099 } else { 100 SecondaryDomain sd = settings().getSecondaryDomainByDomain(getOverrideDomain()); 101 if (sd.isStripRootFromPageSlug()) { 102 return getSlug().substring(sd.getRewriteRoot().length()); 103 } else { 104 return getSlug(); 105 } 106 } 107 } 108 109 /** 110 * Empty by default, but if secondary domains are found, and the page slug matches that of a secondary domain, 111 * then this method will return the secondary domain. 112 * 113 * @return 114 */ 115 @Override 116 @JsonIgnore 117 public String getOverrideDomain() { 118 if (overrideDomain != null) { 119 return overrideDomain; 120 } 121 if (Settings.instance() == null) { 122 overrideDomain = ""; 123 return overrideDomain; 124 } 125 if (Settings.instance().getSecondaryDomains().size() == 0) { 126 overrideDomain = ""; 127 return overrideDomain; 128 } 129 if (getSlug() == null) { 130 overrideDomain = ""; 131 return overrideDomain; 132 } 133 for (SecondaryDomain d : Settings.instance().getSecondaryDomains()) { 134 if (getSlug().startsWith(d.getRewriteRoot())) { 135 overrideDomain = d.getDomain(); 136 return overrideDomain; 137 } 138 } 139 overrideDomain = ""; 140 return overrideDomain; 141 } 142 143 /** 144 * Creates a CSS id for the page <body> tag based on the slug. This is used 145 * to make it easy to do per-page stylings in a stylesheet. 146 * 147 * @return 148 */ 149 public String getSlugForCssId() { 150 String s = slug; 151 if (slug == null) { 152 return ""; 153 } 154 if (s.startsWith("/")) { 155 s = s.substring(1); 156 } 157 if (empty(s)) { 158 s = "site-root"; 159 } 160 s = GeneralUtils.slugify(s); 161 return s; 162 } 163 164 /** 165 * The actual page content that gets displayed in the body of the web page. This should 166 * already be HTML (for HTML pages). 167 * 168 * @return 169 */ 170 @Override 171 @Column(columnDefinition = "longtext") 172 public String getContent() { 173 return this.content; 174 } 175 176 public <D extends StandardDisplayableModel> D setSlug(String slug) { 177 this.slug = slug; 178 return (D) this; 179 } 180 181 public <D extends StandardDisplayableModel> D setContent(String content) { 182 this.content = content; 183 return (D) this; 184 } 185 186 /** 187 * The path of the template used to render the page. Will default to page.jinja if it is not set. 188 * 189 * @return 190 */ 191 @Override 192 @Column 193 public String getTemplate() { 194 return template; 195 } 196 197 public <D extends StandardDisplayableModel> D setTemplate(String template) { 198 this.template = template; 199 return (D) this; 200 } 201 202 203 /** 204 * Get the date at which the page should be live, if this is in the future, the page will 205 * not be visible. 206 * 207 * @return 208 */ 209 @Column 210 public ZonedDateTime getPublishDate() { 211 return publishDate; 212 } 213 214 public <D extends StandardDisplayableModel> D setPublishDate(ZonedDateTime publishDate) { 215 this.publishDate = publishDate; 216 return (D) this; 217 } 218 219 /** 220 * If true, the page will not be visible. 221 * 222 * @return 223 */ 224 @Column 225 public Boolean getDraft() { 226 return draft; 227 } 228 229 public <D extends StandardDisplayableModel> D setDraft(Boolean draft) { 230 this.draft = draft; 231 return (D) this; 232 } 233 234 /** 235 * True if draft is false, and publishDate is before the current time. 236 * 237 * @return 238 */ 239 public Boolean getPublished() { 240 if (draft == false && (getPublishDate() == null || getPublishDate().isBefore(DateUtils.utcNow()))) { 241 return true; 242 } else { 243 return false; 244 } 245 } 246 247 public ZonedDateTime getDate() { 248 return this.getPublishDate(); 249 } 250 251 /** 252 * Get the publish date and format it with the passed in DateTimeFormatter 253 * 254 * @param format 255 * @return 256 */ 257 public String formattedDate(String format) { 258 DateTimeFormatter formatter = 259 DateTimeFormatter.ofPattern(format); 260 return getPublishDate().format(formatter); 261 } 262 263 /** 264 * Get the full URL to this page 265 * 266 * @return 267 */ 268 public String getPermalink() { 269 if (empty(getOverrideDomain())) { 270 return Context.getSettings().getSiteUrl() + getRealHttpPath(); 271 } else { 272 String scheme = settings().getSchemeForSecondaryDomain(getOverrideDomain()); 273 274 return scheme + "://" + getOverrideDomain() + getRealHttpPath(); 275 } 276 277 } 278 279 /** 280 * Get the author name of this page. 281 * 282 * @return 283 */ 284 @Column 285 public String getAuthor() { 286 return author; 287 } 288 289 public <D extends StandardDisplayableModel> D setAuthor(String author) { 290 this.author = author; 291 return (D) this; 292 } 293 294 /** 295 * For the description meta tag, often used by Google and others to give a one sentence 296 * explanation of the page in search results. 297 * 298 * @return 299 */ 300 @Column 301 public String getMetaDescription() { 302 return metaDescription; 303 } 304 305 public <D extends StandardDisplayableModel> D setMetaDescription(String metaDescription) { 306 this.metaDescription = metaDescription; 307 return (D) this; 308 } 309 310 /** 311 * Used for the canonical tag in the HTML head section, set this manually if this page 312 * is accessible at multiple URL's and you do not want to get duplicate content penalties 313 * in Google. 314 * 315 * @return 316 */ 317 @Override 318 @Column 319 public String getRelCanonical() { 320 return relCanonical; 321 } 322 323 public <D extends StandardDisplayableModel> D setRelCanonical(String relCanonical) { 324 this.relCanonical = relCanonical; 325 return (D) this; 326 } 327 328 /** 329 * Get a list of comma separated keywords for the keywords tag. 330 * 331 * @return 332 */ 333 @Override 334 @Column 335 public String getMetaKeywords() { 336 return metaKeywords; 337 } 338 339 public <D extends StandardDisplayableModel> D setMetaKeywords(String metaKeywords) { 340 this.metaKeywords = metaKeywords; 341 return (D) this; 342 } 343 344 345 @Override 346 @Column 347 public String getTitleTag() { 348 return titleTag; 349 } 350 351 public <D extends StandardDisplayableModel> D setTitleTag(String titleTag) { 352 this.titleTag = titleTag; 353 return (D) this; 354 } 355 356 /** 357 * Get used for the og:image meta tag. 358 * 359 * @return 360 */ 361 @Override 362 @Column 363 public String getImage() { 364 return image; 365 } 366 367 public <D extends StandardDisplayableModel> D setImage(String image) { 368 this.image = image; 369 return (D) this; 370 } 371 372 /** 373 * OpenGraph page type, for the HTML meta section 374 * 375 * @return 376 */ 377 @Override 378 @Column(length = 30) 379 public String getOgType() { 380 return ogType; 381 } 382 383 public <D extends StandardDisplayableModel> D setOgType(String ogType) { 384 this.ogType = ogType; 385 return (D) this; 386 } 387 388 /** 389 * Set this to some custom value, and then you can view an unpublished page via 390 * /slug?stPreview=(your preview key) 391 * 392 * @return 393 */ 394 @Override 395 @Column(length = 40) 396 public String getPreviewKey() { 397 return previewKey; 398 } 399 400 public <D extends StandardDisplayableModel> D setPreviewKey(String previewKey) { 401 this.previewKey = previewKey; 402 return (D) this; 403 } 404 405 /** 406 * If you change the URL of a page, add the old url here, Stallion will then 301 redirect 407 * the old url to the new url. 408 * 409 * @return 410 */ 411 @Override 412 @Column(columnDefinition = "longtext") 413 @Converter(cls = JsonListConverter.class) 414 public List<String> getOldUrls() { 415 return oldUrls; 416 } 417 418 @Override 419 public <D extends Displayable> D setOldUrls(List<String> oldUrls) { 420 this.oldUrls = oldUrls; 421 return (D) this; 422 } 423 424 @Override 425 @Column(columnDefinition = "longtext") 426 public String getOriginalContent() { 427 return originalContent; 428 } 429 430 public <D extends StandardDisplayableModel> D setOriginalContent(String originalContent) { 431 this.originalContent = originalContent; 432 return (D) this; 433 } 434 435 public String getSummary() { 436 if (empty(getContent())) { 437 return ""; 438 } 439 int i = getContent().indexOf("<!--more-->"); 440 if (i > -1) { 441 return getContent().substring(0, i); 442 } 443 i = getContent().indexOf("</p>"); 444 if (i > -1) { 445 return getContent().substring(0, i + 4); 446 } 447 i = getContent().indexOf("</div>"); 448 if (i > -1) { 449 return getContent().substring(0, i + 6); 450 } 451 return getContent(); 452 } 453 454 public String getTruncatedSummary(int max) { 455 String summary = getSummary(); 456 if (summary.length() < max) { 457 return summary; 458 } else { 459 max = summary.lastIndexOf(" ", max); 460 return summary.substring(0, max) + "…"; 461 } 462 } 463 464 465 public String formatPublishDate() { 466 return formatPublishDate("MMM d, YYYY"); 467 } 468 469 public String formatPublishDate(String pattern) { 470 ZonedDateTime dt = getPublishDate(); 471 if (dt == null) { 472 dt = DateUtils.utcNow(); 473 } 474 // TODO: Localize 475 return this.getPublishDate().format(DateTimeFormatter.ofPattern(pattern)); 476 } 477 478 @Override 479 public String getContentType() { 480 return contentType; 481 } 482 483 public StandardDisplayableModel setContentType(String contentType) { 484 this.contentType = contentType; 485 return this; 486 } 487}