Completed
Push — master ( 1195ed...a8bb1b )
by greg
01:54
created

Page.js ➔ ... ➔ ???   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
c 3
b 0
f 0
nc 3
dl 0
loc 14
rs 8.8571
nop 1
1
import Handlebars from 'handlebars'
2
import path from 'path'
3
import fse from 'fs-extra'
4
5
import {
6
  cmsEditor,
7
  abeEngine,
8
  cmsData,
9
  cmsTemplates,
10
  config,
11
  Hooks,
12
  Manager
13
} from '../'
14
15
/**
16
 * Page class
17
 * manage HTML generation for page template
18
 */
19
export default class Page {
20
21
  /**
22
   * Create new page object
23
   * @param  {Object} params req.params from express route
0 ignored issues
show
Documentation introduced by
The parameter params does not exist. Did you maybe forget to remove this comment?
Loading history...
24
   * @param  {Object} i18n translation
0 ignored issues
show
Documentation introduced by
The parameter i18n does not exist. Did you maybe forget to remove this comment?
Loading history...
25
   * @param  {Function} callback 
0 ignored issues
show
Documentation introduced by
The parameter callback does not exist. Did you maybe forget to remove this comment?
Loading history...
26
   * @param  {Boolean} onlyHTML default = false, if true HTML content will contains abe attributes
27
   * @return {String} HTML page as string
28
   */
29
  constructor(templateId, template, json, onlyHTML = false) {
30
    // HOOKS beforePageJson
31
    json = Hooks.instance.trigger('beforePageJson', json)
32
33
    if(typeof Handlebars.templates[templateId] !== 'undefined' && 
34
        Handlebars.templates[templateId] !== null && 
35
        config.files.templates.precompile
36
      ){
37
38
      template = Handlebars.templates[templateId]
39
      this.html = template(json, {data: {intl: config.intlData}})
40
41
      //console.log('precompile')
42
43
    } else {
44
45
      this._onlyHTML = onlyHTML
46
      this.template = template
47
      this.HbsTemplatePath = path.join(config.root, config.templates.url, 'hbs/'+templateId+'.hbs')
48
49
      abeEngine.instance.content = json
50
      
51
      // This pattern finds all abe tags which are not enclosed in a html tag attribute
52
      // it finds this one: <title>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</title>
53
      // it excludes this one: <meta name="description" content='{{abe type="text" key="meta_description" desc="Meta description" tab="Meta" order="4100"}}"/> 
54
      this.abePattern = /[^"']({{abe.*?type=[\'|\"][text|rich|textarea]+[\'|\"][\s\S].*?}})/g
55
56
      // This pattern finds all abe tags enclosed in a HTML tag attribute
57
      this.abeAsAttributePattern = /( [A-Za-z0-9\-\_]+=["|']{1}{{abe.*?}})/g
58
59
      // This pattern finds all {{#each ...}}...{{/each}} blocks
60
      this.eachBlockPattern = />\s*(\{\{#each (\r|\t|\n|.)*?\/each\}\})/g
61
62
      // This pattern finds all {{#each ...}}...{{/each}} blocks
63
      this.blockPattern = /(\{\{#each.*\}\}[\s\S]*?\{\{\/each\}\})/g
64
65
      // Remove text with attribute "visible=false"
66
      this._removeHidden()
67
    
68
      if(!this._onlyHTML) {
69
70
        // Surrounds each Abe tag (which are text/rich/textarea and not in html attribute) with <abe> tag
71
        // ie. <title><abe>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</abe></title>
72
        this._encloseAbeTag()
73
      }
74
75
      // je rajoute les index pour chaque bloc lié à un each
76
      this._indexEachBlocks()
77
      
78
      if(!this._onlyHTML){
79
80
        // Je maj les attributs associés aux Abe qui sont dans des attributs de tag HTML
81
        this._updateAbeAsAttribute()
82
83
        // je rajoute les attributs pour les tags Abe (qui ne sont pas dans un attribut HTML)
84
        this._updateAbeAsTag()
85
86
        // Don't know what it does...
87
        var source = config.source.name
88
        if(typeof json[source] !== 'undefined' && json[source] !== null) {
89
          var keys = Object.keys(json[source])
90
          
91
          for(var i in keys) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
92
            var replaceEach = new RegExp(`<!-- \\[\\[${keys[i]}\\]\\][\\s\\S]*?-->`, 'g')
93
            this.template = this.template.replace(replaceEach, '')
94
95
            var patAttrSource = new RegExp(' ([A-Za-z0-9\-\_]+)=["|\'].*?({{' + keys[i] + '}}).*?["|\']', 'g')
96
            var patAttrSourceMatch = this.template.match(patAttrSource)
97
98
            if(typeof patAttrSourceMatch !== 'undefined' && patAttrSourceMatch !== null) {
99
              var patAttrSourceInside = new RegExp('(\\S+)=["\']?((?:.(?!["\']?\\s+(?:\\S+)=|[>"\']))+.)["\']?({{' + keys[i] + '}}).*?["|\']', 'g')
100
              Array.prototype.forEach.call(patAttrSourceMatch, (pat) => {
101
                var patAttrSourceCheck = patAttrSourceInside.exec(pat)
0 ignored issues
show
Bug introduced by
The variable patAttrSourceInside is changed as part of the for-each loop for example by new RegExp("(\S+)=["']?(...i + "}}).*?["|']", "g") on line 99. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
102
                if(typeof patAttrSourceCheck !== 'undefined' && patAttrSourceCheck !== null) {
103
                  var checkEscaped = /["|'](.*?)["|']/
104
                  checkEscaped = checkEscaped.exec(patAttrSourceCheck[0])
105
                  if(typeof checkEscaped !== 'undefined' && checkEscaped !== null && checkEscaped.length > 0) {
106
                    checkEscaped = escape(checkEscaped[1])
107
                    this.template = this.template.replace(
108
                      patAttrSourceCheck[0],
109
                      ` data-abe-attr="${patAttrSourceCheck[1]}" data-abe-attr-escaped="${checkEscaped}" data-abe="${keys[i]}" ${patAttrSourceCheck[0]}`
0 ignored issues
show
introduced by
The variable i is changed by the for-each loop on line 91. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
110
                    )
111
                  }
112
                }
113
              })
114
            }
115
116
            var eachSource = new RegExp(`({{#each ${keys[i]}}[\\s\\S a-z]*?{{\/each}})`, 'g')
117
            var matches = this.template.match(eachSource)
118
            if(typeof matches !== 'undefined' && matches !== null) {
119
              Array.prototype.forEach.call(matches, (match) => {
120
                this.template = this.template.replace(match, `${match}<!-- [[${keys[i]}]] ${cmsTemplates.encodeAbeTagAsComment(match)} -->`)
0 ignored issues
show
introduced by
The variable i is changed by the for-each loop on line 91. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
121
              })
122
            }
123
          }
124
        }
125
      }
126
     
127
      this._addSource(json)
128
129
      // We remove the {{abe type=data ...}} from the text 
130
      this.template = cmsData.source.removeDataList(this.template)
131
132
      // It's time to replace the [index] by {{@index}} (concerning each blocks)
133
      this.template = this.template.replace(/\[index\]\./g, '{{@index}}-')
134
135
      if(config.files.templates.precompile){
136
        // Let's persist the precompiled template for future use (kind of cache)
137
        fse.writeFileSync(this.HbsTemplatePath, Handlebars.precompile(this.template), 'utf8')
138
        Manager.instance.addHbsTemplate(templateId)
139
      }
140
141
      // I compile the text
142
      var compiledTemplate = Handlebars.compile((!this._onlyHTML) ? cmsTemplates.insertDebugtoolUtilities(this.template) : this.template)
143
144
      // I create the html page ! yeah !!!
145
      this.html = compiledTemplate(json, {data: {intl: config.intlData}})
146
    }
147
148
    if(this._onlyHTML) {
149
      this.html = Hooks.instance.trigger('afterPageSaveCompile', this.html, json)
150
    }else {
151
      this.html = Hooks.instance.trigger('afterPageEditorCompile', this.html, json)
152
    }
153
  }
154
155
  _updateAbeAsAttribute() {
156
    var match
157
    while (match = this.abeAsAttributePattern.exec(this.template)) { // While regexp match {{attribut}}, ex: link, image ...
158
      if(cmsData.regex.isSingleAbe(match[0], this.template)){
159
        var more_attr = ''
160
        var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
161
        this.template = this.template.replace(
162
          new RegExp(match[0]),
163
          ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + (match[0].split('=')[0]).trim() + '"' +
164
          ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '"' +
165
          more_attr + match[0].replace('}}', ' has-abe=1}}')
166
        )
167
      }
168
    }
169
170
    return this
171
  }
172
173
  _updateAbeAsTag() {
174
    var match
175
    while (match = this.abePattern.exec(this.template)) {
176
      var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
177
      this.template = this.template.replace(
178
        cmsData.regex.escapeTextToRegex(match[0], 'g'),
179
        ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '" ' + match[0]
180
      )
181
    }
182
183
    return this
184
  }
185
  
186
  /**
187
   * [_indexEachBlocks description]
188
   * @param  {[type]} text   [description]
0 ignored issues
show
Documentation introduced by
The parameter text does not exist. Did you maybe forget to remove this comment?
Loading history...
189
   * @param  {[type]} blocks [description]
0 ignored issues
show
Documentation introduced by
The parameter blocks does not exist. Did you maybe forget to remove this comment?
Loading history...
190
   * @return {[type]}        [description]
191
   */
192
  _indexEachBlocks() {
193
    // create an array of {{each}} blocks
194
    var blocks = this._splitEachBlocks()
195
196
    Array.prototype.forEach.call(blocks, (block) => {
197
      var key = block.match(/#each (.*)\}\}/)
198
      key = key[1]
199
      let util = new cmsEditor.form()
200
      var match
201
202
      if(!this._onlyHTML) {
203
204
        var voidData = {}
205
        voidData[key] = [{}]
206
        var blockCompiled = Handlebars.compile(block.replace(/{{abe (.*?)}}/g, '[[abe $1]]').replace(new RegExp(`\\.\\.\/${config.meta.name}`, 'g'), config.meta.name))
207
        var blockHtml = blockCompiled(voidData, {data: {intl: config.intlData}}).replace(/\[\[abe (.*?)\]\]/g, '{{abe $1}}')
208
209
        // je rajoute un data-abe-block avec index sur tous les tags html du bloc each
210
        var textEachWithIndex = block.replace(/(<(?![\/])[A-Za-z0-9!-]*)/g, '$1 data-abe-block="' + key + '{{@index}}"')
211
212
        // je remplace le block dans le texte par ça
213
        this.template = this.template.replace(block, textEachWithIndex + `<!-- [[${key}]] ${cmsTemplates.encodeAbeTagAsComment(blockHtml)} -->`)
214
      }
215
216
      // Pour chaque tag Abe, je mets en forme ce tag avec des data- supplémentaires
217
      while (match = this.abePattern.exec(block)) {
218
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length, util)
219
      }
220
221
      // Pour chaque tag Abe attribut de HTML, je mets en forme ce tag avec des data- supplémentaires sur le tag html parent
222
      while (match = this.abeAsAttributePattern.exec(block)) {
223
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length, util)
224
      }  
225
    })
226
227
    return this
228
  }
229
230
  /**
231
   * create an array of {{#each}} blocks from the html document
232
   * @param  {String} html the html document
0 ignored issues
show
Documentation introduced by
The parameter html does not exist. Did you maybe forget to remove this comment?
Loading history...
233
   * @return {Array}      the array of {{#each}} blocks
234
   */
235
  _splitEachBlocks() {
236
    var block
237
    var blocks = []
238
239
    while (block = this.blockPattern.exec(this.template)) {
240
      blocks.push(block[1])
241
    }
242
243
    return blocks
244
  }
245
246
  _insertAbeEach(theMatch, key, lastIndex) {
0 ignored issues
show
Unused Code introduced by
The parameter lastIndex is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
247
    var matchBlock = theMatch[0]
248
    if(cmsData.regex.isEachStatement(matchBlock)) return
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
249
    if(cmsData.regex.isBlockAbe(matchBlock)){
250
      var matchblockattr = (matchBlock.split('=')[0]).trim()
251
      var getattr = cmsData.regex.getAttr(matchBlock, 'key').replace('.', '[index].')
252
      var newMatchBlock = ((!this._onlyHTML) ?
253
                            (/=[\"\']\{\{(.*?)\}\}/g.test(matchBlock) ?
254
                                ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + matchblockattr + '"' :
255
                                '') +
256
                            ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="' + getattr + '" ' + matchBlock :
257
                            matchBlock)
258
          .replace(new RegExp('(key=[\'|"])' + key + '.', 'g'), '$1' + key + '[index].')
259
          .replace(/\{\{abe/, '{{abe dictionnary=\'' + key + '\'')
260
261
      this.template = this.template.replace(matchBlock, newMatchBlock)
262
    }
263
264
    return this
265
  }
266
267
  /**
268
   * add <abe> tag around html tag
269
   * @param {String} text html string
0 ignored issues
show
Documentation introduced by
The parameter text does not exist. Did you maybe forget to remove this comment?
Loading history...
270
   */
271
  _removeHidden() {
272
    this.template = this.template.replace(/(\{\{abe.*visible=[\'|\"]false.*\}\})/g, '')
273
274
    return this
275
  }
276
277
  /**
278
   * add <abe> tag around html tag
279
   * @param {String} text html string
0 ignored issues
show
Documentation introduced by
The parameter text does not exist. Did you maybe forget to remove this comment?
Loading history...
280
   */
281
  _encloseAbeTag() {
282
    var match
283
    while (match = this.abePattern.exec(this.template)) {
284
      this.template = this.template.replace(cmsData.regex.escapeTextToRegex(match[1], 'g'), '<abe>' + match[1].trim() + '</abe>')
285
    }
286
287
    return this
288
  }
289
290
  _addSource(json) {
291
    var listReg = /({{abe.*type=[\'|\"]data.*}})/g
292
    var match
293
294
    while (match = listReg.exec(this.template)) {
295
      var editable = cmsData.regex.getAttr(match[0], 'editable')
296
      var key = cmsData.regex.getAttr(match[0], 'key')
297
298
      if((typeof editable === 'undefined' || editable === null || editable === '' || editable === 'false')
299
        && typeof json[config.source.name] !== 'undefined' && json[config.source.name] !== null) {
300
        json[key] = json[config.source.name][key]
301
      }
302
303
      json = Hooks.instance.trigger('afterAddSourcePage', json, match[0])
304
    }
305
  }
306
}