Completed
Push — master ( a10c33...e8fa11 )
by greg
01:59
created

src/cli/cms/Page.js   A

Size

Lines of Code 317

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
nc 1
dl 0
loc 317
rs 10
noi 15

1 Function

Rating   Name   Duplication   Size   Complexity  
D Page.js ➔ ??? 0 132 16
1
import Handlebars from 'handlebars'
2
import fse from 'fs-extra'
3
4
import {
5
  Util,
6
  fileUtils,
7
  abeEngine,
8
  getAttr,
9
  escapeTextToRegex,
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
    
31
    var dateStart = new Date()
0 ignored issues
show
Unused Code introduced by
The variable dateStart seems to be never used. Consider removing it.
Loading history...
32
33
    // HOOKS beforePageJson
34
    json = Hooks.instance.trigger('beforePageJson', json)
35
36
    if(typeof Handlebars.templates[templateId] !== 'undefined' && 
37
        Handlebars.templates[templateId] !== null && 
38
        config.files.templates.precompile
39
      ){
40
41
      var template = Handlebars.templates[templateId]
42
      this.html = template(json, {data: {intl: config.intlData}})
43
44
      //console.log('precompile')
45
46
    } else {
47
48
      this._onlyHTML = onlyHTML
49
      this.template = template
50
      this.HbsTemplatePath = fileUtils.getTemplatePath('hbs/'+templateId+'.hbs')
51
52
      let util = new Util()
53
54
      abeEngine.instance.content = json
55
      
56
      // This pattern finds all abe tags which are not enclosed in a html tag attribute
57
      // it finds this one: <title>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</title>
58
      // it excludes this one: <meta name="description" content='{{abe type="text" key="meta_description" desc="Meta description" tab="Meta" order="4100"}}"/> 
59
      this.abePattern = /[^"']({{abe.*?type=[\'|\"][text|rich|textarea]+[\'|\"][\s\S].*?}})/g
60
61
      // This pattern finds all abe tags enclosed in a HTML tag attribute
62
      this.abeAsAttributePattern = /( [A-Za-z0-9\-\_]+=["|']{1}{{abe.*?}})/g
63
64
      // This pattern finds all {{#each ...}}...{{/each}} blocks
65
      this.eachBlockPattern = />\s*(\{\{#each (\r|\t|\n|.)*?\/each\}\})/g
66
67
      // This pattern finds all {{#each ...}}...{{/each}} blocks
68
      this.blockPattern = /(\{\{#each.*\}\}[\s\S]*?\{\{\/each\}\})/g
69
70
      // Remove text with attribute "visible=false"
71
      this._removeHidden()
72
    
73
      if(!this._onlyHTML) {
74
75
        // Surrounds each Abe tag (which are text/rich/textarea and not in html attribute) with <abe> tag
76
        // ie. <title><abe>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</abe></title>
77
        this._encloseAbeTag()
78
      }
79
80
      // je rajoute les index pour chaque bloc lié à un each
81
      this._indexEachBlocks()
82
      
83
      if(!this._onlyHTML){
84
85
        // Je maj les attributs associés aux Abe qui sont dans des attributs de tag HTML
86
        this._updateAbeAsAttribute()
87
88
        // je rajoute les attributs pour les tags Abe (qui ne sont pas dans un attribut HTML)
89
        this._updateAbeAsTag()
90
91
        // Don't know what it does...
92
        var source = config.source.name
93
        if(typeof json[source] !== 'undefined' && json[source] !== null) {
94
          var keys = Object.keys(json[source])
95
          
96
          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...
97
            var replaceEach = new RegExp(`<!-- \\[\\[${keys[i]}\\]\\][\\s\\S]*?-->`, 'g')
98
            this.template = this.template.replace(replaceEach, '')
99
100
            var patAttrSource = new RegExp(' ([A-Za-z0-9\-\_]+)=["|\'].*?({{' + keys[i] + '}}).*?["|\']', 'g')
101
            var patAttrSourceMatch = this.template.match(patAttrSource)
102
103
            if(typeof patAttrSourceMatch !== 'undefined' && patAttrSourceMatch !== null) {
104
              var patAttrSourceInside = new RegExp('(\\S+)=["\']?((?:.(?!["\']?\\s+(?:\\S+)=|[>"\']))+.)["\']?({{' + keys[i] + '}}).*?["|\']', 'g')
105
              Array.prototype.forEach.call(patAttrSourceMatch, (pat) => {
106
                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 104. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
107
                if(typeof patAttrSourceCheck !== 'undefined' && patAttrSourceCheck !== null) {
108
                  var checkEscaped = /["|'](.*?)["|']/
109
                  checkEscaped = checkEscaped.exec(patAttrSourceCheck[0])
110
                  if(typeof checkEscaped !== 'undefined' && checkEscaped !== null && checkEscaped.length > 0) {
111
                    checkEscaped = escape(checkEscaped[1])
112
                    this.template = this.template.replace(
113
                      patAttrSourceCheck[0],
114
                      ` 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 96. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
115
                    )
116
                  }
117
                }
118
              })
119
            }
120
121
            var eachSource = new RegExp(`({{#each ${keys[i]}}[\\s\\S a-z]*?{{\/each}})`, 'g')
122
            var matches = this.template.match(eachSource)
123
            if(typeof matches !== 'undefined' && matches !== null) {
124
              Array.prototype.forEach.call(matches, (match) => {
125
                this.template = this.template.replace(match, `${match}<!-- [[${keys[i]}]] ${util.encodeAbe(match)} -->`)
0 ignored issues
show
introduced by
The variable i is changed by the for-each loop on line 96. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
126
              })
127
            }
128
          }
129
        }
130
      }
131
     
132
      this._addSource(json)
133
134
      // We remove the {{abe type=data ...}} from the text 
135
      this.template = Util.removeDataList(this.template)
136
137
      // It's time to replace the [index] by {{@index}} (concerning each blocks)
138
      this.template = this.template.replace(/\[index\]\./g, '{{@index}}-')
139
140
      if(config.files.templates.precompile){
141
        // Let's persist the precompiled template for future use (kind of cache)
142
        fse.writeFileSync(this.HbsTemplatePath, Handlebars.precompile(this.template), 'utf8')
143
        Manager.instance.addHbsTemplate(templateId)
144
      }
145
146
      // I compile the text
147
      var compiledTemplate = Handlebars.compile((!this._onlyHTML) ? util.insertDebugtoolUtilities(this.template) : this.template)
148
149
      // I create the html page ! yeah !!!
150
      this.html = compiledTemplate(json, {data: {intl: config.intlData}})
151
    }
152
153
    if(this._onlyHTML) {
154
      this.html = Hooks.instance.trigger('afterPageSaveCompile', this.html, json)
155
    }else {
156
      this.html = Hooks.instance.trigger('afterPageEditorCompile', this.html, json)
157
    }
158
159
    //console.log('result: ' + ((new Date().getTime() - dateStart.getTime()) / 1000))
160
  }
161
162
  _updateAbeAsAttribute() {
163
    var match
164
    let util = new Util()
165
166
    while (match = this.abeAsAttributePattern.exec(this.template)) { // While regexp match {{attribut}}, ex: link, image ...
167
      if(util.isSingleAbe(match[0], this.template)){
168
        var more_attr = ''
169
        var getattr = getAttr(match, 'key').replace(/\./g, '-')
170
        this.template = this.template.replace(
171
          new RegExp(match[0]),
172
          ' data-abe-attr-' + util.validDataAbe(getattr) + '="'  + (match[0].split('=')[0]).trim() + '"' +
173
          ' data-abe-' + util.validDataAbe(getattr) + '="'  + getattr + '"' +
174
          more_attr + match[0].replace('}}', ' has-abe=1}}')
175
        )
176
      }
177
    }
178
179
    return this
180
  }
181
182
  _updateAbeAsTag() {
183
    var match
184
    let util = new Util()
185
186
    while (match = this.abePattern.exec(this.template)) {
187
      var getattr = getAttr(match, 'key').replace(/\./g, '-')
188
      this.template = this.template.replace(
189
        escapeTextToRegex(match[0], 'g'),
190
        ' data-abe-' + util.validDataAbe(getattr) + '="'  + getattr + '" ' + match[0]
191
      )
192
    }
193
194
    return this
195
  }
196
  
197
  /**
198
   * [_indexEachBlocks description]
199
   * @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...
200
   * @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...
201
   * @return {[type]}        [description]
202
   */
203
  _indexEachBlocks() {
204
    // create an array of {{each}} blocks
205
    var blocks = this._splitEachBlocks()
206
207
    Array.prototype.forEach.call(blocks, (block) => {
208
      var key = block.match(/#each (.*)\}\}/)
209
      key = key[1]
210
      let util = new Util()
211
      var match
212
213
      if(!this._onlyHTML) {
214
215
        var voidData = {}
216
        voidData[key] = [{}]
217
        var blockCompiled = Handlebars.compile(block.replace(/{{abe (.*?)}}/g, '[[abe $1]]').replace(new RegExp(`\\.\\.\/${config.meta.name}`, 'g'), config.meta.name))
218
        var blockHtml = blockCompiled(voidData, {data: {intl: config.intlData}}).replace(/\[\[abe (.*?)\]\]/g, '{{abe $1}}')
219
220
        // je rajoute un data-abe-block avec index sur tous les tags html du bloc each
221
        var textEachWithIndex = block.replace(/(<(?![\/])[A-Za-z0-9!-]*)/g, '$1 data-abe-block="' + key + '{{@index}}"')
222
223
        // je remplace le block dans le texte par ça
224
        this.template = this.template.replace(block, textEachWithIndex + `<!-- [[${key}]] ${util.encodeAbe(blockHtml)} -->`)
225
      }
226
227
      // Pour chaque tag Abe, je mets en forme ce tag avec des data- supplémentaires
228
      while (match = this.abePattern.exec(block)) {
229
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length, util)
230
      }
231
232
      // Pour chaque tag Abe attribut de HTML, je mets en forme ce tag avec des data- supplémentaires sur le tag html parent
233
      while (match = this.abeAsAttributePattern.exec(block)) {
234
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length, util)
235
      }  
236
    })
237
238
    return this
239
  }
240
241
  /**
242
   * create an array of {{#each}} blocks from the html document
243
   * @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...
244
   * @return {Array}      the array of {{#each}} blocks
245
   */
246
  _splitEachBlocks() {
247
    var block
248
    var blocks = []
249
250
    while (block = this.blockPattern.exec(this.template)) {
251
      blocks.push(block[1])
252
    }
253
254
    return blocks
255
  }
256
257
  _insertAbeEach(theMatch, key, lastIndex, util) {
258
    var matchBlock = theMatch[0]
259
    if(util.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...
260
    if(util.isBlockAbe(matchBlock)){
261
      var matchblockattr = (matchBlock.split('=')[0]).trim()
262
      var getattr = getAttr(matchBlock, 'key').replace('.', '[index].')
263
      var newMatchBlock = ((!this._onlyHTML) ?
264
                            (/=[\"\']\{\{(.*?)\}\}/g.test(matchBlock) ?
265
                                ' data-abe-attr-' + util.validDataAbe(getattr) + '="'  + matchblockattr + '"' :
266
                                '') +
267
                            ' data-abe-' + util.validDataAbe(getattr) + '="' + getattr + '" ' + matchBlock :
268
                            matchBlock)
269
          .replace(new RegExp('(key=[\'|"])' + key + '.', 'g'), '$1' + key + '[index].')
270
          .replace(/\{\{abe/, '{{abe dictionnary=\'' + key + '\'')
271
272
      this.template = this.template.replace(matchBlock, newMatchBlock)
273
    }
274
275
    return this
276
  }
277
278
  /**
279
   * add <abe> tag around html tag
280
   * @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...
281
   */
282
  _removeHidden() {
283
    this.template = this.template.replace(/(\{\{abe.*visible=[\'|\"]false.*\}\})/g, '')
284
285
    return this
286
  }
287
288
  /**
289
   * add <abe> tag around html tag
290
   * @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...
291
   */
292
  _encloseAbeTag() {
293
    var match
294
    while (match = this.abePattern.exec(this.template)) {
295
      this.template = this.template.replace(escapeTextToRegex(match[1], 'g'), '<abe>' + match[1].trim() + '</abe>')
296
    }
297
298
    return this
299
  }
300
301
  _addSource(json) {
302
    var listReg = /({{abe.*type=[\'|\"]data.*}})/g
303
    var match
304
    var limit = 0
0 ignored issues
show
Unused Code introduced by
The variable limit seems to be never used. Consider removing it.
Loading history...
305
306
    while (match = listReg.exec(this.template)) {
307
      var editable = getAttr(match[0], 'editable')
308
      var key = getAttr(match[0], 'key')
309
310
      if(typeof editable === 'undefined' || editable === null || editable === '' || editable === 'false') {
311
        json[key] = json[config.source.name][key]
312
      }
313
314
      json = Hooks.instance.trigger('afterAddSourcePage', json, match[0])
315
    }
316
  }
317
}