1// Copyright 2019 The Hugo Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package hugolib
15
16import (
17	"fmt"
18	"html/template"
19	"io"
20	"log"
21	"mime"
22	"net/url"
23	"os"
24	"path"
25	"path/filepath"
26	"regexp"
27	"sort"
28	"strconv"
29	"strings"
30	"time"
31
32	"github.com/gohugoio/hugo/common/types"
33
34	"github.com/gohugoio/hugo/common/paths"
35
36	"github.com/gohugoio/hugo/common/constants"
37
38	"github.com/gohugoio/hugo/common/loggers"
39
40	"github.com/gohugoio/hugo/resources"
41
42	"github.com/gohugoio/hugo/identity"
43
44	"github.com/gohugoio/hugo/markup/converter/hooks"
45
46	"github.com/gohugoio/hugo/resources/resource"
47
48	"github.com/gohugoio/hugo/markup/converter"
49
50	"github.com/gohugoio/hugo/hugofs/files"
51
52	"github.com/gohugoio/hugo/common/maps"
53
54	"github.com/pkg/errors"
55
56	"github.com/gohugoio/hugo/common/text"
57
58	"github.com/gohugoio/hugo/common/hugo"
59	"github.com/gohugoio/hugo/publisher"
60	_errors "github.com/pkg/errors"
61
62	"github.com/gohugoio/hugo/langs"
63
64	"github.com/gohugoio/hugo/resources/page"
65
66	"github.com/gohugoio/hugo/config"
67	"github.com/gohugoio/hugo/lazy"
68
69	"github.com/gohugoio/hugo/media"
70
71	"github.com/fsnotify/fsnotify"
72	bp "github.com/gohugoio/hugo/bufferpool"
73	"github.com/gohugoio/hugo/deps"
74	"github.com/gohugoio/hugo/helpers"
75	"github.com/gohugoio/hugo/navigation"
76	"github.com/gohugoio/hugo/output"
77	"github.com/gohugoio/hugo/related"
78	"github.com/gohugoio/hugo/resources/page/pagemeta"
79	"github.com/gohugoio/hugo/source"
80	"github.com/gohugoio/hugo/tpl"
81
82	"github.com/spf13/afero"
83	"github.com/spf13/cast"
84)
85
86// Site contains all the information relevant for constructing a static
87// site.  The basic flow of information is as follows:
88//
89// 1. A list of Files is parsed and then converted into Pages.
90//
91// 2. Pages contain sections (based on the file they were generated from),
92//    aliases and slugs (included in a pages frontmatter) which are the
93//    various targets that will get generated.  There will be canonical
94//    listing.  The canonical path can be overruled based on a pattern.
95//
96// 3. Taxonomies are created via configuration and will present some aspect of
97//    the final page and typically a perm url.
98//
99// 4. All Pages are passed through a template based on their desired
100//    layout based on numerous different elements.
101//
102// 5. The entire collection of files is written to disk.
103type Site struct {
104
105	// The owning container. When multiple languages, there will be multiple
106	// sites .
107	h *HugoSites
108
109	*PageCollections
110
111	taxonomies TaxonomyList
112
113	Sections Taxonomy
114	Info     *SiteInfo
115
116	language   *langs.Language
117	siteBucket *pagesMapBucket
118
119	siteCfg siteConfigHolder
120
121	disabledKinds map[string]bool
122
123	// Output formats defined in site config per Page Kind, or some defaults
124	// if not set.
125	// Output formats defined in Page front matter will override these.
126	outputFormats map[string]output.Formats
127
128	// All the output formats and media types available for this site.
129	// These values will be merged from the Hugo defaults, the site config and,
130	// finally, the language settings.
131	outputFormatsConfig output.Formats
132	mediaTypesConfig    media.Types
133
134	siteConfigConfig SiteConfig
135
136	// How to handle page front matter.
137	frontmatterHandler pagemeta.FrontMatterHandler
138
139	// We render each site for all the relevant output formats in serial with
140	// this rendering context pointing to the current one.
141	rc *siteRenderingContext
142
143	// The output formats that we need to render this site in. This slice
144	// will be fixed once set.
145	// This will be the union of Site.Pages' outputFormats.
146	// This slice will be sorted.
147	renderFormats output.Formats
148
149	// Logger etc.
150	*deps.Deps `json:"-"`
151
152	// The func used to title case titles.
153	titleFunc func(s string) string
154
155	relatedDocsHandler *page.RelatedDocsHandler
156	siteRefLinker
157
158	publisher publisher.Publisher
159
160	menus navigation.Menus
161
162	// Shortcut to the home page. Note that this may be nil if
163	// home page, for some odd reason, is disabled.
164	home *pageState
165
166	// The last modification date of this site.
167	lastmod time.Time
168
169	// Lazily loaded site dependencies
170	init *siteInit
171}
172
173func (s *Site) Taxonomies() TaxonomyList {
174	s.init.taxonomies.Do()
175	return s.taxonomies
176}
177
178type taxonomiesConfig map[string]string
179
180func (t taxonomiesConfig) Values() []viewName {
181	var vals []viewName
182	for k, v := range t {
183		vals = append(vals, viewName{singular: k, plural: v})
184	}
185	sort.Slice(vals, func(i, j int) bool {
186		return vals[i].plural < vals[j].plural
187	})
188
189	return vals
190}
191
192type siteConfigHolder struct {
193	sitemap          config.Sitemap
194	taxonomiesConfig taxonomiesConfig
195	timeout          time.Duration
196	hasCJKLanguage   bool
197	enableEmoji      bool
198}
199
200// Lazily loaded site dependencies.
201type siteInit struct {
202	prevNext          *lazy.Init
203	prevNextInSection *lazy.Init
204	menus             *lazy.Init
205	taxonomies        *lazy.Init
206}
207
208func (init *siteInit) Reset() {
209	init.prevNext.Reset()
210	init.prevNextInSection.Reset()
211	init.menus.Reset()
212	init.taxonomies.Reset()
213}
214
215func (s *Site) initInit(init *lazy.Init, pctx pageContext) bool {
216	_, err := init.Do()
217	if err != nil {
218		s.h.FatalError(pctx.wrapError(err))
219	}
220	return err == nil
221}
222
223func (s *Site) prepareInits() {
224	s.init = &siteInit{}
225
226	var init lazy.Init
227
228	s.init.prevNext = init.Branch(func() (interface{}, error) {
229		regularPages := s.RegularPages()
230		for i, p := range regularPages {
231			np, ok := p.(nextPrevProvider)
232			if !ok {
233				continue
234			}
235
236			pos := np.getNextPrev()
237			if pos == nil {
238				continue
239			}
240
241			pos.nextPage = nil
242			pos.prevPage = nil
243
244			if i > 0 {
245				pos.nextPage = regularPages[i-1]
246			}
247
248			if i < len(regularPages)-1 {
249				pos.prevPage = regularPages[i+1]
250			}
251		}
252		return nil, nil
253	})
254
255	s.init.prevNextInSection = init.Branch(func() (interface{}, error) {
256		var sections page.Pages
257		s.home.treeRef.m.collectSectionsRecursiveIncludingSelf(pageMapQuery{Prefix: s.home.treeRef.key}, func(n *contentNode) {
258			sections = append(sections, n.p)
259		})
260
261		setNextPrev := func(pas page.Pages) {
262			for i, p := range pas {
263				np, ok := p.(nextPrevInSectionProvider)
264				if !ok {
265					continue
266				}
267
268				pos := np.getNextPrevInSection()
269				if pos == nil {
270					continue
271				}
272
273				pos.nextPage = nil
274				pos.prevPage = nil
275
276				if i > 0 {
277					pos.nextPage = pas[i-1]
278				}
279
280				if i < len(pas)-1 {
281					pos.prevPage = pas[i+1]
282				}
283			}
284		}
285
286		for _, sect := range sections {
287			treeRef := sect.(treeRefProvider).getTreeRef()
288
289			var pas page.Pages
290			treeRef.m.collectPages(pageMapQuery{Prefix: treeRef.key + cmBranchSeparator}, func(c *contentNode) {
291				pas = append(pas, c.p)
292			})
293			page.SortByDefault(pas)
294
295			setNextPrev(pas)
296		}
297
298		// The root section only goes one level down.
299		treeRef := s.home.getTreeRef()
300
301		var pas page.Pages
302		treeRef.m.collectPages(pageMapQuery{Prefix: treeRef.key + cmBranchSeparator}, func(c *contentNode) {
303			pas = append(pas, c.p)
304		})
305		page.SortByDefault(pas)
306
307		setNextPrev(pas)
308
309		return nil, nil
310	})
311
312	s.init.menus = init.Branch(func() (interface{}, error) {
313		s.assembleMenus()
314		return nil, nil
315	})
316
317	s.init.taxonomies = init.Branch(func() (interface{}, error) {
318		err := s.pageMap.assembleTaxonomies()
319		return nil, err
320	})
321}
322
323type siteRenderingContext struct {
324	output.Format
325}
326
327func (s *Site) Menus() navigation.Menus {
328	s.init.menus.Do()
329	return s.menus
330}
331
332func (s *Site) initRenderFormats() {
333	formatSet := make(map[string]bool)
334	formats := output.Formats{}
335	s.pageMap.pageTrees.WalkRenderable(func(s string, n *contentNode) bool {
336		for _, f := range n.p.m.configuredOutputFormats {
337			if !formatSet[f.Name] {
338				formats = append(formats, f)
339				formatSet[f.Name] = true
340			}
341		}
342		return false
343	})
344
345	// Add the per kind configured output formats
346	for _, kind := range allKindsInPages {
347		if siteFormats, found := s.outputFormats[kind]; found {
348			for _, f := range siteFormats {
349				if !formatSet[f.Name] {
350					formats = append(formats, f)
351					formatSet[f.Name] = true
352				}
353			}
354		}
355	}
356
357	sort.Sort(formats)
358	s.renderFormats = formats
359}
360
361func (s *Site) GetRelatedDocsHandler() *page.RelatedDocsHandler {
362	return s.relatedDocsHandler
363}
364
365func (s *Site) Language() *langs.Language {
366	return s.language
367}
368
369func (s *Site) isEnabled(kind string) bool {
370	if kind == kindUnknown {
371		panic("Unknown kind")
372	}
373	return !s.disabledKinds[kind]
374}
375
376// reset returns a new Site prepared for rebuild.
377func (s *Site) reset() *Site {
378	return &Site{
379		Deps:                s.Deps,
380		disabledKinds:       s.disabledKinds,
381		titleFunc:           s.titleFunc,
382		relatedDocsHandler:  s.relatedDocsHandler.Clone(),
383		siteRefLinker:       s.siteRefLinker,
384		outputFormats:       s.outputFormats,
385		rc:                  s.rc,
386		outputFormatsConfig: s.outputFormatsConfig,
387		frontmatterHandler:  s.frontmatterHandler,
388		mediaTypesConfig:    s.mediaTypesConfig,
389		language:            s.language,
390		siteBucket:          s.siteBucket,
391		h:                   s.h,
392		publisher:           s.publisher,
393		siteConfigConfig:    s.siteConfigConfig,
394		init:                s.init,
395		PageCollections:     s.PageCollections,
396		siteCfg:             s.siteCfg,
397	}
398}
399
400// newSite creates a new site with the given configuration.
401func newSite(cfg deps.DepsCfg) (*Site, error) {
402	if cfg.Language == nil {
403		cfg.Language = langs.NewDefaultLanguage(cfg.Cfg)
404	}
405	if cfg.Logger == nil {
406		panic("logger must be set")
407	}
408
409	ignoreErrors := cast.ToStringSlice(cfg.Language.Get("ignoreErrors"))
410	ignorableLogger := loggers.NewIgnorableLogger(cfg.Logger, ignoreErrors...)
411
412	disabledKinds := make(map[string]bool)
413	for _, disabled := range cast.ToStringSlice(cfg.Language.Get("disableKinds")) {
414		disabledKinds[disabled] = true
415	}
416
417	if disabledKinds["taxonomyTerm"] {
418		// Correct from the value it had before Hugo 0.73.0.
419		if disabledKinds[page.KindTaxonomy] {
420			disabledKinds[page.KindTerm] = true
421		} else {
422			disabledKinds[page.KindTaxonomy] = true
423		}
424
425		delete(disabledKinds, "taxonomyTerm")
426	} else if disabledKinds[page.KindTaxonomy] && !disabledKinds[page.KindTerm] {
427		// This is a potentially ambigous situation. It may be correct.
428		ignorableLogger.Errorsf(constants.ErrIDAmbigousDisableKindTaxonomy, `You have the value 'taxonomy' in the disabledKinds list. In Hugo 0.73.0 we fixed these to be what most people expect (taxonomy and term).
429But this also means that your site configuration may not do what you expect. If it is correct, you can suppress this message by following the instructions below.`)
430	}
431
432	var (
433		mediaTypesConfig    []map[string]interface{}
434		outputFormatsConfig []map[string]interface{}
435
436		siteOutputFormatsConfig output.Formats
437		siteMediaTypesConfig    media.Types
438		err                     error
439	)
440
441	// Add language last, if set, so it gets precedence.
442	for _, cfg := range []config.Provider{cfg.Cfg, cfg.Language} {
443		if cfg.IsSet("mediaTypes") {
444			mediaTypesConfig = append(mediaTypesConfig, cfg.GetStringMap("mediaTypes"))
445		}
446		if cfg.IsSet("outputFormats") {
447			outputFormatsConfig = append(outputFormatsConfig, cfg.GetStringMap("outputFormats"))
448		}
449	}
450
451	siteMediaTypesConfig, err = media.DecodeTypes(mediaTypesConfig...)
452	if err != nil {
453		return nil, err
454	}
455
456	siteOutputFormatsConfig, err = output.DecodeFormats(siteMediaTypesConfig, outputFormatsConfig...)
457	if err != nil {
458		return nil, err
459	}
460
461	rssDisabled := disabledKinds[kindRSS]
462	if rssDisabled {
463		// Legacy
464		tmp := siteOutputFormatsConfig[:0]
465		for _, x := range siteOutputFormatsConfig {
466			if !strings.EqualFold(x.Name, "rss") {
467				tmp = append(tmp, x)
468			}
469		}
470		siteOutputFormatsConfig = tmp
471	}
472
473	var siteOutputs map[string]interface{}
474	if cfg.Language.IsSet("outputs") {
475		siteOutputs = cfg.Language.GetStringMap("outputs")
476
477		// Check and correct taxonomy kinds vs pre Hugo 0.73.0.
478		v1, hasTaxonomyTerm := siteOutputs["taxonomyterm"]
479		v2, hasTaxonomy := siteOutputs[page.KindTaxonomy]
480		_, hasTerm := siteOutputs[page.KindTerm]
481		if hasTaxonomy && hasTaxonomyTerm {
482			siteOutputs[page.KindTaxonomy] = v1
483			siteOutputs[page.KindTerm] = v2
484			delete(siteOutputs, "taxonomyTerm")
485		} else if hasTaxonomy && !hasTerm {
486			// This is a potentially ambigous situation. It may be correct.
487			ignorableLogger.Errorsf(constants.ErrIDAmbigousOutputKindTaxonomy, `You have configured output formats for 'taxonomy' in your site configuration. In Hugo 0.73.0 we fixed these to be what most people expect (taxonomy and term).
488But this also means that your site configuration may not do what you expect. If it is correct, you can suppress this message by following the instructions below.`)
489		}
490		if !hasTaxonomy && hasTaxonomyTerm {
491			siteOutputs[page.KindTaxonomy] = v1
492			delete(siteOutputs, "taxonomyterm")
493		}
494	}
495
496	outputFormats, err := createSiteOutputFormats(siteOutputFormatsConfig, siteOutputs, rssDisabled)
497	if err != nil {
498		return nil, err
499	}
500
501	taxonomies := cfg.Language.GetStringMapString("taxonomies")
502
503	var relatedContentConfig related.Config
504
505	if cfg.Language.IsSet("related") {
506		relatedContentConfig, err = related.DecodeConfig(cfg.Language.GetParams("related"))
507		if err != nil {
508			return nil, errors.Wrap(err, "failed to decode related config")
509		}
510	} else {
511		relatedContentConfig = related.DefaultConfig
512		if _, found := taxonomies["tag"]; found {
513			relatedContentConfig.Add(related.IndexConfig{Name: "tags", Weight: 80})
514		}
515	}
516
517	titleFunc := helpers.GetTitleFunc(cfg.Language.GetString("titleCaseStyle"))
518
519	frontMatterHandler, err := pagemeta.NewFrontmatterHandler(cfg.Logger, cfg.Cfg)
520	if err != nil {
521		return nil, err
522	}
523
524	timeout := 30 * time.Second
525	if cfg.Language.IsSet("timeout") {
526		v := cfg.Language.Get("timeout")
527		d, err := types.ToDurationE(v)
528		if err == nil {
529			timeout = d
530		}
531	}
532
533	siteConfig := siteConfigHolder{
534		sitemap:          config.DecodeSitemap(config.Sitemap{Priority: -1, Filename: "sitemap.xml"}, cfg.Language.GetStringMap("sitemap")),
535		taxonomiesConfig: taxonomies,
536		timeout:          timeout,
537		hasCJKLanguage:   cfg.Language.GetBool("hasCJKLanguage"),
538		enableEmoji:      cfg.Language.Cfg.GetBool("enableEmoji"),
539	}
540
541	var siteBucket *pagesMapBucket
542	if cfg.Language.IsSet("cascade") {
543		var err error
544		cascade, err := page.DecodeCascade(cfg.Language.Get("cascade"))
545		if err != nil {
546			return nil, errors.Errorf("failed to decode cascade config: %s", err)
547		}
548
549		siteBucket = &pagesMapBucket{
550			cascade: cascade,
551		}
552
553	}
554
555	s := &Site{
556		language:      cfg.Language,
557		siteBucket:    siteBucket,
558		disabledKinds: disabledKinds,
559
560		outputFormats:       outputFormats,
561		outputFormatsConfig: siteOutputFormatsConfig,
562		mediaTypesConfig:    siteMediaTypesConfig,
563
564		siteCfg: siteConfig,
565
566		titleFunc: titleFunc,
567
568		rc: &siteRenderingContext{output.HTMLFormat},
569
570		frontmatterHandler: frontMatterHandler,
571		relatedDocsHandler: page.NewRelatedDocsHandler(relatedContentConfig),
572	}
573
574	s.prepareInits()
575
576	return s, nil
577}
578
579// NewSite creates a new site with the given dependency configuration.
580// The site will have a template system loaded and ready to use.
581// Note: This is mainly used in single site tests.
582func NewSite(cfg deps.DepsCfg) (*Site, error) {
583	s, err := newSite(cfg)
584	if err != nil {
585		return nil, err
586	}
587
588	var l configLoader
589	if err = l.applyDeps(cfg, s); err != nil {
590		return nil, err
591	}
592
593	return s, nil
594}
595
596// NewSiteDefaultLang creates a new site in the default language.
597// The site will have a template system loaded and ready to use.
598// Note: This is mainly used in single site tests.
599// TODO(bep) test refactor -- remove
600func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
601	l := configLoader{cfg: config.New()}
602	if err := l.applyConfigDefaults(); err != nil {
603		return nil, err
604	}
605	return newSiteForLang(langs.NewDefaultLanguage(l.cfg), withTemplate...)
606}
607
608// NewEnglishSite creates a new site in English language.
609// The site will have a template system loaded and ready to use.
610// Note: This is mainly used in single site tests.
611// TODO(bep) test refactor -- remove
612func NewEnglishSite(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
613	l := configLoader{cfg: config.New()}
614	if err := l.applyConfigDefaults(); err != nil {
615		return nil, err
616	}
617	return newSiteForLang(langs.NewLanguage("en", l.cfg), withTemplate...)
618}
619
620// newSiteForLang creates a new site in the given language.
621func newSiteForLang(lang *langs.Language, withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
622	withTemplates := func(templ tpl.TemplateManager) error {
623		for _, wt := range withTemplate {
624			if err := wt(templ); err != nil {
625				return err
626			}
627		}
628		return nil
629	}
630
631	cfg := deps.DepsCfg{WithTemplate: withTemplates, Cfg: lang}
632
633	return NewSiteForCfg(cfg)
634}
635
636// NewSiteForCfg creates a new site for the given configuration.
637// The site will have a template system loaded and ready to use.
638// Note: This is mainly used in single site tests.
639func NewSiteForCfg(cfg deps.DepsCfg) (*Site, error) {
640	h, err := NewHugoSites(cfg)
641	if err != nil {
642		return nil, err
643	}
644	return h.Sites[0], nil
645}
646
647type SiteInfo struct {
648	Authors page.AuthorList
649	Social  SiteSocial
650
651	hugoInfo     hugo.Info
652	title        string
653	RSSLink      string
654	Author       map[string]interface{}
655	LanguageCode string
656	Copyright    string
657
658	permalinks map[string]string
659
660	LanguagePrefix string
661	Languages      langs.Languages
662
663	BuildDrafts bool
664
665	canonifyURLs bool
666	relativeURLs bool
667	uglyURLs     func(p page.Page) bool
668
669	owner                          *HugoSites
670	s                              *Site
671	language                       *langs.Language
672	defaultContentLanguageInSubdir bool
673	sectionPagesMenu               string
674}
675
676func (s *SiteInfo) Pages() page.Pages {
677	return s.s.Pages()
678}
679
680func (s *SiteInfo) RegularPages() page.Pages {
681	return s.s.RegularPages()
682}
683
684func (s *SiteInfo) AllPages() page.Pages {
685	return s.s.AllPages()
686}
687
688func (s *SiteInfo) AllRegularPages() page.Pages {
689	return s.s.AllRegularPages()
690}
691
692func (s *SiteInfo) Permalinks() map[string]string {
693	// Remove in 0.61
694	helpers.Deprecated(".Site.Permalinks", "", true)
695	return s.permalinks
696}
697
698func (s *SiteInfo) LastChange() time.Time {
699	return s.s.lastmod
700}
701
702func (s *SiteInfo) Title() string {
703	return s.title
704}
705
706func (s *SiteInfo) Site() page.Site {
707	return s
708}
709
710func (s *SiteInfo) Menus() navigation.Menus {
711	return s.s.Menus()
712}
713
714// TODO(bep) type
715func (s *SiteInfo) Taxonomies() interface{} {
716	return s.s.Taxonomies()
717}
718
719func (s *SiteInfo) Params() maps.Params {
720	return s.s.Language().Params()
721}
722
723func (s *SiteInfo) Data() map[string]interface{} {
724	return s.s.h.Data()
725}
726
727func (s *SiteInfo) Language() *langs.Language {
728	return s.language
729}
730
731func (s *SiteInfo) Config() SiteConfig {
732	return s.s.siteConfigConfig
733}
734
735func (s *SiteInfo) Hugo() hugo.Info {
736	return s.hugoInfo
737}
738
739// Sites is a convenience method to get all the Hugo sites/languages configured.
740func (s *SiteInfo) Sites() page.Sites {
741	return s.s.h.siteInfos()
742}
743
744func (s *SiteInfo) String() string {
745	return fmt.Sprintf("Site(%q)", s.title)
746}
747
748func (s *SiteInfo) BaseURL() template.URL {
749	return template.URL(s.s.PathSpec.BaseURL.String())
750}
751
752// ServerPort returns the port part of the BaseURL, 0 if none found.
753func (s *SiteInfo) ServerPort() int {
754	ps := s.s.PathSpec.BaseURL.URL().Port()
755	if ps == "" {
756		return 0
757	}
758	p, err := strconv.Atoi(ps)
759	if err != nil {
760		return 0
761	}
762	return p
763}
764
765// GoogleAnalytics is kept here for historic reasons.
766func (s *SiteInfo) GoogleAnalytics() string {
767	return s.Config().Services.GoogleAnalytics.ID
768}
769
770// DisqusShortname is kept here for historic reasons.
771func (s *SiteInfo) DisqusShortname() string {
772	return s.Config().Services.Disqus.Shortname
773}
774
775// SiteSocial is a place to put social details on a site level. These are the
776// standard keys that themes will expect to have available, but can be
777// expanded to any others on a per site basis
778// github
779// facebook
780// facebook_admin
781// twitter
782// twitter_domain
783// pinterest
784// instagram
785// youtube
786// linkedin
787type SiteSocial map[string]string
788
789// Param is a convenience method to do lookups in SiteInfo's Params map.
790//
791// This method is also implemented on Page.
792func (s *SiteInfo) Param(key interface{}) (interface{}, error) {
793	return resource.Param(s, nil, key)
794}
795
796func (s *SiteInfo) IsMultiLingual() bool {
797	return len(s.Languages) > 1
798}
799
800func (s *SiteInfo) IsServer() bool {
801	return s.owner.running
802}
803
804type siteRefLinker struct {
805	s *Site
806
807	errorLogger *log.Logger
808	notFoundURL string
809}
810
811func newSiteRefLinker(cfg config.Provider, s *Site) (siteRefLinker, error) {
812	logger := s.Log.Error()
813
814	notFoundURL := cfg.GetString("refLinksNotFoundURL")
815	errLevel := cfg.GetString("refLinksErrorLevel")
816	if strings.EqualFold(errLevel, "warning") {
817		logger = s.Log.Warn()
818	}
819	return siteRefLinker{s: s, errorLogger: logger, notFoundURL: notFoundURL}, nil
820}
821
822func (s siteRefLinker) logNotFound(ref, what string, p page.Page, position text.Position) {
823	if position.IsValid() {
824		s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s: %s", s.s.Lang(), ref, position.String(), what)
825	} else if p == nil {
826		s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s", s.s.Lang(), ref, what)
827	} else {
828		s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q from page %q: %s", s.s.Lang(), ref, p.Path(), what)
829	}
830}
831
832func (s *siteRefLinker) refLink(ref string, source interface{}, relative bool, outputFormat string) (string, error) {
833	p, err := unwrapPage(source)
834	if err != nil {
835		return "", err
836	}
837
838	var refURL *url.URL
839
840	ref = filepath.ToSlash(ref)
841
842	refURL, err = url.Parse(ref)
843
844	if err != nil {
845		return s.notFoundURL, err
846	}
847
848	var target page.Page
849	var link string
850
851	if refURL.Path != "" {
852		var err error
853		target, err = s.s.getPageRef(p, refURL.Path)
854		var pos text.Position
855		if err != nil || target == nil {
856			if p, ok := source.(text.Positioner); ok {
857				pos = p.Position()
858			}
859		}
860
861		if err != nil {
862			s.logNotFound(refURL.Path, err.Error(), p, pos)
863			return s.notFoundURL, nil
864		}
865
866		if target == nil {
867			s.logNotFound(refURL.Path, "page not found", p, pos)
868			return s.notFoundURL, nil
869		}
870
871		var permalinker Permalinker = target
872
873		if outputFormat != "" {
874			o := target.OutputFormats().Get(outputFormat)
875
876			if o == nil {
877				s.logNotFound(refURL.Path, fmt.Sprintf("output format %q", outputFormat), p, pos)
878				return s.notFoundURL, nil
879			}
880			permalinker = o
881		}
882
883		if relative {
884			link = permalinker.RelPermalink()
885		} else {
886			link = permalinker.Permalink()
887		}
888	}
889
890	if refURL.Fragment != "" {
891		_ = target
892		link = link + "#" + refURL.Fragment
893
894		if pctx, ok := target.(pageContext); ok {
895			if refURL.Path != "" {
896				if di, ok := pctx.getContentConverter().(converter.DocumentInfo); ok {
897					link = link + di.AnchorSuffix()
898				}
899			}
900		} else if pctx, ok := p.(pageContext); ok {
901			if di, ok := pctx.getContentConverter().(converter.DocumentInfo); ok {
902				link = link + di.AnchorSuffix()
903			}
904		}
905
906	}
907
908	return link, nil
909}
910
911func (s *Site) running() bool {
912	return s.h != nil && s.h.running
913}
914
915func (s *Site) multilingual() *Multilingual {
916	return s.h.multilingual
917}
918
919type whatChanged struct {
920	source bool
921	files  map[string]bool
922}
923
924// RegisterMediaTypes will register the Site's media types in the mime
925// package, so it will behave correctly with Hugo's built-in server.
926func (s *Site) RegisterMediaTypes() {
927	for _, mt := range s.mediaTypesConfig {
928		for _, suffix := range mt.Suffixes() {
929			_ = mime.AddExtensionType(mt.Delimiter+suffix, mt.Type()+"; charset=utf-8")
930		}
931	}
932}
933
934func (s *Site) filterFileEvents(events []fsnotify.Event) []fsnotify.Event {
935	var filtered []fsnotify.Event
936	seen := make(map[fsnotify.Event]bool)
937
938	for _, ev := range events {
939		// Avoid processing the same event twice.
940		if seen[ev] {
941			continue
942		}
943		seen[ev] = true
944
945		if s.SourceSpec.IgnoreFile(ev.Name) {
946			continue
947		}
948
949		// Throw away any directories
950		isRegular, err := s.SourceSpec.IsRegularSourceFile(ev.Name)
951		if err != nil && os.IsNotExist(err) && (ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Rename == fsnotify.Rename) {
952			// Force keep of event
953			isRegular = true
954		}
955		if !isRegular {
956			continue
957		}
958
959		filtered = append(filtered, ev)
960	}
961
962	return filtered
963}
964
965func (s *Site) translateFileEvents(events []fsnotify.Event) []fsnotify.Event {
966	var filtered []fsnotify.Event
967
968	eventMap := make(map[string][]fsnotify.Event)
969
970	// We often get a Remove etc. followed by a Create, a Create followed by a Write.
971	// Remove the superfluous events to mage the update logic simpler.
972	for _, ev := range events {
973		eventMap[ev.Name] = append(eventMap[ev.Name], ev)
974	}
975
976	for _, ev := range events {
977		mapped := eventMap[ev.Name]
978
979		// Keep one
980		found := false
981		var kept fsnotify.Event
982		for i, ev2 := range mapped {
983			if i == 0 {
984				kept = ev2
985			}
986
987			if ev2.Op&fsnotify.Write == fsnotify.Write {
988				kept = ev2
989				found = true
990			}
991
992			if !found && ev2.Op&fsnotify.Create == fsnotify.Create {
993				kept = ev2
994			}
995		}
996
997		filtered = append(filtered, kept)
998	}
999
1000	return filtered
1001}
1002
1003var (
1004	// These are only used for cache busting, so false positives are fine.
1005	// We also deliberately do not match for file suffixes to also catch
1006	// directory names.
1007	// TODO(bep) consider this when completing the relevant PR rewrite on this.
1008	cssFileRe   = regexp.MustCompile("(css|sass|scss)")
1009	cssConfigRe = regexp.MustCompile(`(postcss|tailwind)\.config\.js`)
1010	jsFileRe    = regexp.MustCompile("(js|ts|jsx|tsx)")
1011)
1012
1013// reBuild partially rebuilds a site given the filesystem events.
1014// It returns whatever the content source was changed.
1015// TODO(bep) clean up/rewrite this method.
1016func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) error, events []fsnotify.Event) error {
1017	events = s.filterFileEvents(events)
1018	events = s.translateFileEvents(events)
1019
1020	changeIdentities := make(identity.Identities)
1021
1022	s.Log.Debugf("Rebuild for events %q", events)
1023
1024	h := s.h
1025
1026	// First we need to determine what changed
1027
1028	var (
1029		sourceChanged       = []fsnotify.Event{}
1030		sourceReallyChanged = []fsnotify.Event{}
1031		contentFilesChanged []string
1032
1033		tmplChanged bool
1034		tmplAdded   bool
1035		dataChanged bool
1036		i18nChanged bool
1037
1038		sourceFilesChanged = make(map[string]bool)
1039
1040		// prevent spamming the log on changes
1041		logger = helpers.NewDistinctErrorLogger()
1042	)
1043
1044	var cachePartitions []string
1045	// Special case
1046	// TODO(bep) I have a ongoing branch where I have redone the cache. Consider this there.
1047	var (
1048		evictCSSRe *regexp.Regexp
1049		evictJSRe  *regexp.Regexp
1050	)
1051
1052	for _, ev := range events {
1053		if assetsFilename, _ := s.BaseFs.Assets.MakePathRelative(ev.Name); assetsFilename != "" {
1054			cachePartitions = append(cachePartitions, resources.ResourceKeyPartitions(assetsFilename)...)
1055			if evictCSSRe == nil {
1056				if cssFileRe.MatchString(assetsFilename) || cssConfigRe.MatchString(assetsFilename) {
1057					evictCSSRe = cssFileRe
1058				}
1059			}
1060			if evictJSRe == nil && jsFileRe.MatchString(assetsFilename) {
1061				evictJSRe = jsFileRe
1062			}
1063		}
1064
1065		id, found := s.eventToIdentity(ev)
1066		if found {
1067			changeIdentities[id] = id
1068
1069			switch id.Type {
1070			case files.ComponentFolderContent:
1071				logger.Println("Source changed", ev)
1072				sourceChanged = append(sourceChanged, ev)
1073			case files.ComponentFolderLayouts:
1074				tmplChanged = true
1075				if !s.Tmpl().HasTemplate(id.Path) {
1076					tmplAdded = true
1077				}
1078				if tmplAdded {
1079					logger.Println("Template added", ev)
1080				} else {
1081					logger.Println("Template changed", ev)
1082				}
1083
1084			case files.ComponentFolderData:
1085				logger.Println("Data changed", ev)
1086				dataChanged = true
1087			case files.ComponentFolderI18n:
1088				logger.Println("i18n changed", ev)
1089				i18nChanged = true
1090
1091			}
1092		}
1093	}
1094
1095	changed := &whatChanged{
1096		source: len(sourceChanged) > 0,
1097		files:  sourceFilesChanged,
1098	}
1099
1100	config.whatChanged = changed
1101
1102	if err := init(config); err != nil {
1103		return err
1104	}
1105
1106	// These in memory resource caches will be rebuilt on demand.
1107	for _, s := range s.h.Sites {
1108		s.ResourceSpec.ResourceCache.DeletePartitions(cachePartitions...)
1109		if evictCSSRe != nil {
1110			s.ResourceSpec.ResourceCache.DeleteMatches(evictCSSRe)
1111		}
1112		if evictJSRe != nil {
1113			s.ResourceSpec.ResourceCache.DeleteMatches(evictJSRe)
1114		}
1115	}
1116
1117	if tmplChanged || i18nChanged {
1118		sites := s.h.Sites
1119		first := sites[0]
1120
1121		s.h.init.Reset()
1122
1123		// TOD(bep) globals clean
1124		if err := first.Deps.LoadResources(); err != nil {
1125			return err
1126		}
1127
1128		for i := 1; i < len(sites); i++ {
1129			site := sites[i]
1130			var err error
1131			depsCfg := deps.DepsCfg{
1132				Language:      site.language,
1133				MediaTypes:    site.mediaTypesConfig,
1134				OutputFormats: site.outputFormatsConfig,
1135			}
1136			site.Deps, err = first.Deps.ForLanguage(depsCfg, func(d *deps.Deps) error {
1137				d.Site = site.Info
1138				return nil
1139			})
1140			if err != nil {
1141				return err
1142			}
1143		}
1144	}
1145
1146	if dataChanged {
1147		s.h.init.data.Reset()
1148	}
1149
1150	for _, ev := range sourceChanged {
1151		removed := false
1152
1153		if ev.Op&fsnotify.Remove == fsnotify.Remove {
1154			removed = true
1155		}
1156
1157		// Some editors (Vim) sometimes issue only a Rename operation when writing an existing file
1158		// Sometimes a rename operation means that file has been renamed other times it means
1159		// it's been updated
1160		if ev.Op&fsnotify.Rename == fsnotify.Rename {
1161			// If the file is still on disk, it's only been updated, if it's not, it's been moved
1162			if ex, err := afero.Exists(s.Fs.Source, ev.Name); !ex || err != nil {
1163				removed = true
1164			}
1165		}
1166
1167		if removed && files.IsContentFile(ev.Name) {
1168			h.removePageByFilename(ev.Name)
1169		}
1170
1171		sourceReallyChanged = append(sourceReallyChanged, ev)
1172		sourceFilesChanged[ev.Name] = true
1173	}
1174
1175	if config.ErrRecovery || tmplAdded || dataChanged {
1176		h.resetPageState()
1177	} else {
1178		h.resetPageStateFromEvents(changeIdentities)
1179	}
1180
1181	if len(sourceReallyChanged) > 0 || len(contentFilesChanged) > 0 {
1182		var filenamesChanged []string
1183		for _, e := range sourceReallyChanged {
1184			filenamesChanged = append(filenamesChanged, e.Name)
1185		}
1186		if len(contentFilesChanged) > 0 {
1187			filenamesChanged = append(filenamesChanged, contentFilesChanged...)
1188		}
1189
1190		filenamesChanged = helpers.UniqueStringsReuse(filenamesChanged)
1191
1192		if err := s.readAndProcessContent(*config, filenamesChanged...); err != nil {
1193			return err
1194		}
1195
1196	}
1197
1198	return nil
1199}
1200
1201func (s *Site) process(config BuildCfg) (err error) {
1202	if err = s.initialize(); err != nil {
1203		err = errors.Wrap(err, "initialize")
1204		return
1205	}
1206	if err = s.readAndProcessContent(config); err != nil {
1207		err = errors.Wrap(err, "readAndProcessContent")
1208		return
1209	}
1210	return err
1211}
1212
1213func (s *Site) render(ctx *siteRenderContext) (err error) {
1214	if err := page.Clear(); err != nil {
1215		return err
1216	}
1217
1218	if ctx.outIdx == 0 {
1219		// Note that even if disableAliases is set, the aliases themselves are
1220		// preserved on page. The motivation with this is to be able to generate
1221		// 301 redirects in a .htacess file and similar using a custom output format.
1222		if !s.Cfg.GetBool("disableAliases") {
1223			// Aliases must be rendered before pages.
1224			// Some sites, Hugo docs included, have faulty alias definitions that point
1225			// to itself or another real page. These will be overwritten in the next
1226			// step.
1227			if err = s.renderAliases(); err != nil {
1228				return
1229			}
1230		}
1231	}
1232
1233	if err = s.renderPages(ctx); err != nil {
1234		return
1235	}
1236
1237	if ctx.outIdx == 0 {
1238		if err = s.renderSitemap(); err != nil {
1239			return
1240		}
1241
1242		if ctx.multihost {
1243			if err = s.renderRobotsTXT(); err != nil {
1244				return
1245			}
1246		}
1247
1248		if err = s.render404(); err != nil {
1249			return
1250		}
1251	}
1252
1253	if !ctx.renderSingletonPages() {
1254		return
1255	}
1256
1257	if err = s.renderMainLanguageRedirect(); err != nil {
1258		return
1259	}
1260
1261	return
1262}
1263
1264func (s *Site) Initialise() (err error) {
1265	return s.initialize()
1266}
1267
1268func (s *Site) initialize() (err error) {
1269	return s.initializeSiteInfo()
1270}
1271
1272// HomeAbsURL is a convenience method giving the absolute URL to the home page.
1273func (s *SiteInfo) HomeAbsURL() string {
1274	base := ""
1275	if s.IsMultiLingual() {
1276		base = s.Language().Lang
1277	}
1278	return s.owner.AbsURL(base, false)
1279}
1280
1281// SitemapAbsURL is a convenience method giving the absolute URL to the sitemap.
1282func (s *SiteInfo) SitemapAbsURL() string {
1283	p := s.HomeAbsURL()
1284	if !strings.HasSuffix(p, "/") {
1285		p += "/"
1286	}
1287	p += s.s.siteCfg.sitemap.Filename
1288	return p
1289}
1290
1291func (s *Site) initializeSiteInfo() error {
1292	var (
1293		lang      = s.language
1294		languages langs.Languages
1295	)
1296
1297	if s.h != nil && s.h.multilingual != nil {
1298		languages = s.h.multilingual.Languages
1299	}
1300
1301	permalinks := s.Cfg.GetStringMapString("permalinks")
1302
1303	defaultContentInSubDir := s.Cfg.GetBool("defaultContentLanguageInSubdir")
1304	defaultContentLanguage := s.Cfg.GetString("defaultContentLanguage")
1305
1306	languagePrefix := ""
1307	if s.multilingualEnabled() && (defaultContentInSubDir || lang.Lang != defaultContentLanguage) {
1308		languagePrefix = "/" + lang.Lang
1309	}
1310
1311	uglyURLs := func(p page.Page) bool {
1312		return false
1313	}
1314
1315	v := s.Cfg.Get("uglyURLs")
1316	if v != nil {
1317		switch vv := v.(type) {
1318		case bool:
1319			uglyURLs = func(p page.Page) bool {
1320				return vv
1321			}
1322		case string:
1323			// Is what be get from CLI (--uglyURLs)
1324			vvv := cast.ToBool(vv)
1325			uglyURLs = func(p page.Page) bool {
1326				return vvv
1327			}
1328		default:
1329			m := maps.ToStringMapBool(v)
1330			uglyURLs = func(p page.Page) bool {
1331				return m[p.Section()]
1332			}
1333		}
1334	}
1335
1336	s.Info = &SiteInfo{
1337		title:                          lang.GetString("title"),
1338		Author:                         lang.GetStringMap("author"),
1339		Social:                         lang.GetStringMapString("social"),
1340		LanguageCode:                   lang.GetString("languageCode"),
1341		Copyright:                      lang.GetString("copyright"),
1342		language:                       lang,
1343		LanguagePrefix:                 languagePrefix,
1344		Languages:                      languages,
1345		defaultContentLanguageInSubdir: defaultContentInSubDir,
1346		sectionPagesMenu:               lang.GetString("sectionPagesMenu"),
1347		BuildDrafts:                    s.Cfg.GetBool("buildDrafts"),
1348		canonifyURLs:                   s.Cfg.GetBool("canonifyURLs"),
1349		relativeURLs:                   s.Cfg.GetBool("relativeURLs"),
1350		uglyURLs:                       uglyURLs,
1351		permalinks:                     permalinks,
1352		owner:                          s.h,
1353		s:                              s,
1354		hugoInfo:                       hugo.NewInfo(s.Cfg.GetString("environment")),
1355	}
1356
1357	rssOutputFormat, found := s.outputFormats[page.KindHome].GetByName(output.RSSFormat.Name)
1358
1359	if found {
1360		s.Info.RSSLink = s.permalink(rssOutputFormat.BaseFilename())
1361	}
1362
1363	return nil
1364}
1365
1366func (s *Site) eventToIdentity(e fsnotify.Event) (identity.PathIdentity, bool) {
1367	for _, fs := range s.BaseFs.SourceFilesystems.FileSystems() {
1368		if p := fs.Path(e.Name); p != "" {
1369			return identity.NewPathIdentity(fs.Name, filepath.ToSlash(p)), true
1370		}
1371	}
1372	return identity.PathIdentity{}, false
1373}
1374
1375func (s *Site) readAndProcessContent(buildConfig BuildCfg, filenames ...string) error {
1376	sourceSpec := source.NewSourceSpec(s.PathSpec, buildConfig.ContentInclusionFilter, s.BaseFs.Content.Fs)
1377
1378	proc := newPagesProcessor(s.h, sourceSpec)
1379
1380	c := newPagesCollector(sourceSpec, s.h.getContentMaps(), s.Log, s.h.ContentChanges, proc, filenames...)
1381
1382	if err := c.Collect(); err != nil {
1383		return err
1384	}
1385
1386	return nil
1387}
1388
1389func (s *Site) getMenusFromConfig() navigation.Menus {
1390	ret := navigation.Menus{}
1391
1392	if menus := s.language.GetStringMap("menus"); menus != nil {
1393		for name, menu := range menus {
1394			m, err := cast.ToSliceE(menu)
1395			if err != nil {
1396				s.Log.Errorf("unable to process menus in site config\n")
1397				s.Log.Errorln(err)
1398			} else {
1399				handleErr := func(err error) {
1400					if err == nil {
1401						return
1402					}
1403					s.Log.Errorf("unable to process menus in site config\n")
1404					s.Log.Errorln(err)
1405
1406				}
1407
1408				for _, entry := range m {
1409					s.Log.Debugf("found menu: %q, in site config\n", name)
1410
1411					menuEntry := navigation.MenuEntry{Menu: name}
1412					ime, err := maps.ToStringMapE(entry)
1413					handleErr(err)
1414
1415					err = menuEntry.MarshallMap(ime)
1416					handleErr(err)
1417
1418					// TODO(bep) clean up all of this
1419					menuEntry.ConfiguredURL = s.Info.createNodeMenuEntryURL(menuEntry.ConfiguredURL)
1420
1421					if ret[name] == nil {
1422						ret[name] = navigation.Menu{}
1423					}
1424					ret[name] = ret[name].Add(&menuEntry)
1425				}
1426			}
1427		}
1428		return ret
1429	}
1430	return ret
1431}
1432
1433func (s *SiteInfo) createNodeMenuEntryURL(in string) string {
1434	if !strings.HasPrefix(in, "/") {
1435		return in
1436	}
1437	// make it match the nodes
1438	menuEntryURL := in
1439	menuEntryURL = helpers.SanitizeURLKeepTrailingSlash(s.s.PathSpec.URLize(menuEntryURL))
1440	if !s.canonifyURLs {
1441		menuEntryURL = paths.AddContextRoot(s.s.PathSpec.BaseURL.String(), menuEntryURL)
1442	}
1443	return menuEntryURL
1444}
1445
1446func (s *Site) assembleMenus() {
1447	s.menus = make(navigation.Menus)
1448
1449	type twoD struct {
1450		MenuName, EntryName string
1451	}
1452	flat := map[twoD]*navigation.MenuEntry{}
1453	children := map[twoD]navigation.Menu{}
1454
1455	// add menu entries from config to flat hash
1456	menuConfig := s.getMenusFromConfig()
1457	for name, menu := range menuConfig {
1458		for _, me := range menu {
1459			if types.IsNil(me.Page) && me.PageRef != "" {
1460				// Try to resolve the page.
1461				me.Page, _ = s.getPageNew(nil, me.PageRef)
1462			}
1463			flat[twoD{name, me.KeyName()}] = me
1464		}
1465	}
1466
1467	sectionPagesMenu := s.Info.sectionPagesMenu
1468
1469	if sectionPagesMenu != "" {
1470		s.pageMap.sections.Walk(func(s string, v interface{}) bool {
1471			p := v.(*contentNode).p
1472			if p.IsHome() {
1473				return false
1474			}
1475			// From Hugo 0.22 we have nested sections, but until we get a
1476			// feel of how that would work in this setting, let us keep
1477			// this menu for the top level only.
1478			id := p.Section()
1479			if _, ok := flat[twoD{sectionPagesMenu, id}]; ok {
1480				return false
1481			}
1482
1483			me := navigation.MenuEntry{
1484				Identifier: id,
1485				Name:       p.LinkTitle(),
1486				Weight:     p.Weight(),
1487				Page:       p,
1488			}
1489			flat[twoD{sectionPagesMenu, me.KeyName()}] = &me
1490
1491			return false
1492		})
1493	}
1494
1495	// Add menu entries provided by pages
1496	s.pageMap.pageTrees.WalkRenderable(func(ss string, n *contentNode) bool {
1497		p := n.p
1498
1499		for name, me := range p.pageMenus.menus() {
1500			if _, ok := flat[twoD{name, me.KeyName()}]; ok {
1501				err := p.wrapError(errors.Errorf("duplicate menu entry with identifier %q in menu %q", me.KeyName(), name))
1502				s.Log.Warnln(err)
1503				continue
1504			}
1505			flat[twoD{name, me.KeyName()}] = me
1506		}
1507
1508		return false
1509	})
1510
1511	// Create Children Menus First
1512	for _, e := range flat {
1513		if e.Parent != "" {
1514			children[twoD{e.Menu, e.Parent}] = children[twoD{e.Menu, e.Parent}].Add(e)
1515		}
1516	}
1517
1518	// Placing Children in Parents (in flat)
1519	for p, childmenu := range children {
1520		_, ok := flat[twoD{p.MenuName, p.EntryName}]
1521		if !ok {
1522			// if parent does not exist, create one without a URL
1523			flat[twoD{p.MenuName, p.EntryName}] = &navigation.MenuEntry{Name: p.EntryName}
1524		}
1525		flat[twoD{p.MenuName, p.EntryName}].Children = childmenu
1526	}
1527
1528	// Assembling Top Level of Tree
1529	for menu, e := range flat {
1530		if e.Parent == "" {
1531			_, ok := s.menus[menu.MenuName]
1532			if !ok {
1533				s.menus[menu.MenuName] = navigation.Menu{}
1534			}
1535			s.menus[menu.MenuName] = s.menus[menu.MenuName].Add(e)
1536		}
1537	}
1538}
1539
1540// get any language code to prefix the target file path with.
1541func (s *Site) getLanguageTargetPathLang(alwaysInSubDir bool) string {
1542	if s.h.IsMultihost() {
1543		return s.Language().Lang
1544	}
1545
1546	return s.getLanguagePermalinkLang(alwaysInSubDir)
1547}
1548
1549// get any lanaguagecode to prefix the relative permalink with.
1550func (s *Site) getLanguagePermalinkLang(alwaysInSubDir bool) string {
1551	if !s.Info.IsMultiLingual() || s.h.IsMultihost() {
1552		return ""
1553	}
1554
1555	if alwaysInSubDir {
1556		return s.Language().Lang
1557	}
1558
1559	isDefault := s.Language().Lang == s.multilingual().DefaultLang.Lang
1560
1561	if !isDefault || s.Info.defaultContentLanguageInSubdir {
1562		return s.Language().Lang
1563	}
1564
1565	return ""
1566}
1567
1568func (s *Site) getTaxonomyKey(key string) string {
1569	if s.PathSpec.DisablePathToLower {
1570		return s.PathSpec.MakePath(key)
1571	}
1572	return strings.ToLower(s.PathSpec.MakePath(key))
1573}
1574
1575// Prepare site for a new full build.
1576func (s *Site) resetBuildState(sourceChanged bool) {
1577	s.relatedDocsHandler = s.relatedDocsHandler.Clone()
1578	s.init.Reset()
1579
1580	if sourceChanged {
1581		s.pageMap.contentMap.pageReverseIndex.Reset()
1582		s.PageCollections = newPageCollections(s.pageMap)
1583		s.pageMap.withEveryBundlePage(func(p *pageState) bool {
1584			p.pagePages = &pagePages{}
1585			if p.bucket != nil {
1586				p.bucket.pagesMapBucketPages = &pagesMapBucketPages{}
1587			}
1588			p.parent = nil
1589			p.Scratcher = maps.NewScratcher()
1590			return false
1591		})
1592	} else {
1593		s.pageMap.withEveryBundlePage(func(p *pageState) bool {
1594			p.Scratcher = maps.NewScratcher()
1595			return false
1596		})
1597	}
1598}
1599
1600func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
1601	var errors []error
1602	for e := range results {
1603		errors = append(errors, e)
1604	}
1605
1606	errs <- s.h.pickOneAndLogTheRest(errors)
1607
1608	close(errs)
1609}
1610
1611// GetPage looks up a page of a given type for the given ref.
1612// In Hugo <= 0.44 you had to add Page Kind (section, home) etc. as the first
1613// argument and then either a unix styled path (with or without a leading slash))
1614// or path elements separated.
1615// When we now remove the Kind from this API, we need to make the transition as painless
1616// as possible for existing sites. Most sites will use {{ .Site.GetPage "section" "my/section" }},
1617// i.e. 2 arguments, so we test for that.
1618func (s *SiteInfo) GetPage(ref ...string) (page.Page, error) {
1619	p, err := s.s.getPageOldVersion(ref...)
1620
1621	if p == nil {
1622		// The nil struct has meaning in some situations, mostly to avoid breaking
1623		// existing sites doing $nilpage.IsDescendant($p), which will always return
1624		// false.
1625		p = page.NilPage
1626	}
1627
1628	return p, err
1629}
1630
1631func (s *SiteInfo) GetPageWithTemplateInfo(info tpl.Info, ref ...string) (page.Page, error) {
1632	p, err := s.GetPage(ref...)
1633	if p != nil {
1634		// Track pages referenced by templates/shortcodes
1635		// when in server mode.
1636		if im, ok := info.(identity.Manager); ok {
1637			im.Add(p)
1638		}
1639	}
1640	return p, err
1641}
1642
1643func (s *Site) permalink(link string) string {
1644	return s.PathSpec.PermalinkForBaseURL(link, s.PathSpec.BaseURL.String())
1645}
1646
1647func (s *Site) absURLPath(targetPath string) string {
1648	var path string
1649	if s.Info.relativeURLs {
1650		path = helpers.GetDottedRelativePath(targetPath)
1651	} else {
1652		url := s.PathSpec.BaseURL.String()
1653		if !strings.HasSuffix(url, "/") {
1654			url += "/"
1655		}
1656		path = url
1657	}
1658
1659	return path
1660}
1661
1662func (s *Site) lookupLayouts(layouts ...string) tpl.Template {
1663	for _, l := range layouts {
1664		if templ, found := s.Tmpl().Lookup(l); found {
1665			return templ
1666		}
1667	}
1668
1669	return nil
1670}
1671
1672func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, templ tpl.Template) error {
1673	s.Log.Debugf("Render XML for %q to %q", name, targetPath)
1674	renderBuffer := bp.GetBuffer()
1675	defer bp.PutBuffer(renderBuffer)
1676
1677	if err := s.renderForTemplate(name, "", d, renderBuffer, templ); err != nil {
1678		return err
1679	}
1680
1681	pd := publisher.Descriptor{
1682		Src:         renderBuffer,
1683		TargetPath:  targetPath,
1684		StatCounter: statCounter,
1685		// For the minification part of XML,
1686		// we currently only use the MIME type.
1687		OutputFormat: output.RSSFormat,
1688		AbsURLPath:   s.absURLPath(targetPath),
1689	}
1690
1691	return s.publisher.Publish(pd)
1692}
1693
1694func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, templ tpl.Template) error {
1695	s.Log.Debugf("Render %s to %q", name, targetPath)
1696	renderBuffer := bp.GetBuffer()
1697	defer bp.PutBuffer(renderBuffer)
1698
1699	of := p.outputFormat()
1700
1701	if err := s.renderForTemplate(p.Kind(), of.Name, p, renderBuffer, templ); err != nil {
1702		return err
1703	}
1704
1705	if renderBuffer.Len() == 0 {
1706		return nil
1707	}
1708
1709	isHTML := of.IsHTML
1710	isRSS := of.Name == "RSS"
1711
1712	pd := publisher.Descriptor{
1713		Src:          renderBuffer,
1714		TargetPath:   targetPath,
1715		StatCounter:  statCounter,
1716		OutputFormat: p.outputFormat(),
1717	}
1718
1719	if isRSS {
1720		// Always canonify URLs in RSS
1721		pd.AbsURLPath = s.absURLPath(targetPath)
1722	} else if isHTML {
1723		if s.Info.relativeURLs || s.Info.canonifyURLs {
1724			pd.AbsURLPath = s.absURLPath(targetPath)
1725		}
1726
1727		if s.running() && s.Cfg.GetBool("watch") && !s.Cfg.GetBool("disableLiveReload") {
1728			pd.LiveReloadBaseURL = s.PathSpec.BaseURL.URL()
1729			if s.Cfg.GetInt("liveReloadPort") != -1 {
1730				pd.LiveReloadBaseURL.Host = fmt.Sprintf("%s:%d", pd.LiveReloadBaseURL.Hostname(), s.Cfg.GetInt("liveReloadPort"))
1731			}
1732		}
1733
1734		// For performance reasons we only inject the Hugo generator tag on the home page.
1735		if p.IsHome() {
1736			pd.AddHugoGeneratorTag = !s.Cfg.GetBool("disableHugoGeneratorInject")
1737		}
1738
1739	}
1740
1741	return s.publisher.Publish(pd)
1742}
1743
1744var infoOnMissingLayout = map[string]bool{
1745	// The 404 layout is very much optional in Hugo, but we do look for it.
1746	"404": true,
1747}
1748
1749// hookRenderer is the canonical implementation of all hooks.ITEMRenderer,
1750// where ITEM is the thing being hooked.
1751type hookRenderer struct {
1752	templateHandler tpl.TemplateHandler
1753	identity.SearchProvider
1754	templ tpl.Template
1755}
1756
1757func (hr hookRenderer) RenderLink(w io.Writer, ctx hooks.LinkContext) error {
1758	return hr.templateHandler.Execute(hr.templ, w, ctx)
1759}
1760
1761func (hr hookRenderer) RenderHeading(w io.Writer, ctx hooks.HeadingContext) error {
1762	return hr.templateHandler.Execute(hr.templ, w, ctx)
1763}
1764
1765func (s *Site) renderForTemplate(name, outputFormat string, d interface{}, w io.Writer, templ tpl.Template) (err error) {
1766	if templ == nil {
1767		s.logMissingLayout(name, "", "", outputFormat)
1768		return nil
1769	}
1770
1771	if err = s.Tmpl().Execute(templ, w, d); err != nil {
1772		return _errors.Wrapf(err, "render of %q failed", name)
1773	}
1774	return
1775}
1776
1777func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) {
1778	for _, l := range layouts {
1779		if templ, found := s.Tmpl().Lookup(l); found {
1780			return templ, true
1781		}
1782	}
1783
1784	return nil, false
1785}
1786
1787func (s *Site) publish(statCounter *uint64, path string, r io.Reader) (err error) {
1788	s.PathSpec.ProcessingStats.Incr(statCounter)
1789
1790	return helpers.WriteToDisk(filepath.Clean(path), r, s.BaseFs.PublishFs)
1791}
1792
1793func (s *Site) kindFromFileInfoOrSections(fi *fileInfo, sections []string) string {
1794	if fi.TranslationBaseName() == "_index" {
1795		if fi.Dir() == "" {
1796			return page.KindHome
1797		}
1798
1799		return s.kindFromSections(sections)
1800
1801	}
1802
1803	return page.KindPage
1804}
1805
1806func (s *Site) kindFromSections(sections []string) string {
1807	if len(sections) == 0 {
1808		return page.KindHome
1809	}
1810
1811	return s.kindFromSectionPath(path.Join(sections...))
1812}
1813
1814func (s *Site) kindFromSectionPath(sectionPath string) string {
1815	for _, plural := range s.siteCfg.taxonomiesConfig {
1816		if plural == sectionPath {
1817			return page.KindTaxonomy
1818		}
1819
1820		if strings.HasPrefix(sectionPath, plural) {
1821			return page.KindTerm
1822		}
1823
1824	}
1825
1826	return page.KindSection
1827}
1828
1829func (s *Site) newPage(
1830	n *contentNode,
1831	parentbBucket *pagesMapBucket,
1832	kind, title string,
1833	sections ...string) *pageState {
1834	m := map[string]interface{}{}
1835	if title != "" {
1836		m["title"] = title
1837	}
1838
1839	p, err := newPageFromMeta(
1840		n,
1841		parentbBucket,
1842		m,
1843		&pageMeta{
1844			s:        s,
1845			kind:     kind,
1846			sections: sections,
1847		})
1848	if err != nil {
1849		panic(err)
1850	}
1851
1852	return p
1853}
1854
1855func (s *Site) shouldBuild(p page.Page) bool {
1856	return shouldBuild(s.BuildFuture, s.BuildExpired,
1857		s.BuildDrafts, p.Draft(), p.PublishDate(), p.ExpiryDate())
1858}
1859
1860func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool,
1861	publishDate time.Time, expiryDate time.Time) bool {
1862	if !(buildDrafts || !Draft) {
1863		return false
1864	}
1865	if !buildFuture && !publishDate.IsZero() && publishDate.After(time.Now()) {
1866		return false
1867	}
1868	if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(time.Now()) {
1869		return false
1870	}
1871	return true
1872}
1873