Completed
Push — master ( 7b2ec6...a10c33 )
by greg
02:03
created

src/cli/cms/operations/save.js   D

Complexity

Total Complexity 65
Complexity/F 5.42

Size

Lines of Code 259
Function Count 12

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
wmc 65
c 1
b 0
f 0
nc 1
mnd 6
bc 43
fnc 12
dl 0
loc 259
rs 4.5
bpm 3.5833
cpm 5.4166
noi 6

6 Functions

Rating   Name   Duplication   Size   Complexity  
A save.js ➔ saveHtml 0 7 3
B save.js ➔ checkRequired 0 33 4
B save.js ➔ saveJson 0 25 3
B save.js ➔ save 0 123 1
A save.js ➔ saveJsonAndHtml 0 13 1
B save.js ➔ dateIso 0 31 5

How to fix   Complexity   

Complexity

Complex classes like src/cli/cms/operations/save.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import fse from 'fs-extra'
2
import extend from 'extend'
3
import mkdirp from 'mkdirp'
4
import xss from 'xss'
5
import {Promise} from 'es6-promise'
6
import path from 'path'
7
8
import {
9
  Util
10
  ,abeProcess
11
  ,FileParser
12
  ,getAttr
13
  ,config
14
  ,fileUtils
15
  ,fileAttr
16
  ,dateSlug
17
  ,Page
18
  ,getTemplate
19
  ,Hooks
20
  ,cleanSlug
21
} from '../../'
22
23
export function checkRequired(text, json) {
24
  var regAbe = /{{abe[\S\s].*?key=['|"]([\S\s].*?['|"| ]}})/g
25
  var matches = text.match(regAbe)
26
  var requiredValue = 0
27
  var complete = 0
28
  if(typeof matches !== 'undefined' && matches !== null){
29
    Array.prototype.forEach.call(matches, (match) => {
30
      if(typeof match !== 'undefined' && match !== null) {
31
        
32
        var keyAttr = getAttr(match, 'key')
33
        var requiredAttr = getAttr(match, 'required')
34
        if(requiredAttr === 'true') {
35
          requiredValue++
36
37
          var minAttr = getAttr(match, 'min-length')
38
          minAttr = (minAttr !== '') ? minAttr : 0
39
40
          if(typeof json[keyAttr] !== 'undefined' && json[keyAttr] !== null && json[keyAttr] !== '') {
41
            if(minAttr > 0) {
42
              if(json[keyAttr].length >= minAttr) {
43
                complete++
44
              }
45
            }else {
46
              complete++
47
            }
48
          }
49
        }
50
      }
51
    })
52
  }
53
54
  return Math.round((requiredValue > 0) ? complete * 100 / requiredValue : 100)
55
}
56
57
export function save(url, tplPath, json = null, text = '', type = '', previousSave = null, realType = 'draft', publishAll = false) {
58
  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...
59
60
  url = cleanSlug(url)
61
62
  var p = new Promise((resolve) => {
63
    var isRejectedDoc = false
64
    if(type === 'reject'){
65
      isRejectedDoc = true
66
      url = Hooks.instance.trigger('beforeReject', url)
67
      type = 'draft'
68
      realType = 'draft'
69
      url = Hooks.instance.trigger('afterReject', url)
70
    }
71
    var tplUrl = FileParser.getFileDataFromUrl(url)
72
    type = type || FileParser.getType(url)
73
    var pathIso = dateIso(tplUrl, type)
74
    if(typeof previousSave !== 'undefined' && previousSave !== null){
75
      pathIso.jsonPath = path.join(config.root, previousSave.jsonPath.replace(config.root, '')).replace(/-abe-d/, `-abe-${realType[0]}`)
76
      pathIso.htmlPath = path.join(config.root, previousSave.htmlPath.replace(config.root, '')).replace(/-abe-d/, `-abe-${realType[0]}`)
77
    }
78
79
    if (tplPath.indexOf('.') > -1) {
80
      tplPath = fileUtils.removeExtension(tplPath)
81
    }
82
    var tpl = tplPath.replace(config.root, '')
83
84
    var fullTpl = path.join(config.root, config.templates.url, tpl) + '.' + config.files.templates.extension
85
86
    if(typeof json === 'undefined' || json === null) {
87
      json = FileParser.getJson(tplUrl.json.path)
88
    }
89
90
    var ext = {
91
      template: tpl.replace(/^\/+/, ''),
92
      link: tplUrl.publish.link,
93
      complete: 0,
94
      type: type
95
    }
96
97
    let meta = config.meta.name
98
    json[meta] = extend(json[meta], ext)
99
    var date = fileAttr.get(pathIso.jsonPath).d
100
101
    if (publishAll) {
102
      if(typeof json[meta].publish !== 'undefined' && json[meta].publish !== null) {
103
        date = json[meta].publish.date
104
      }
105
    }else {
106
      if(typeof date === 'undefined' || date === null || date === '') {
107
        date = new Date()
108
      }else {
109
        date = new Date(date)
110
      }
111
    }
112
    Util.addMetas(tpl, json, type, {}, date, realType)
113
114
    if(typeof text === 'undefined' || text === null || text === '') {
115
      text = getTemplate(fullTpl)
116
    }
117
118
    Util.getDataList(fileUtils.removeLast(tplUrl.publish.link), text, json)
119
        .then(() => {
120
121
          json = Hooks.instance.trigger('afterGetDataListOnSave', json)
122
          for(var prop in json){
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...
123
            if(typeof json[prop] === 'object' && Array.isArray(json[prop]) && json[prop].length === 1){
124
              var valuesAreEmpty = true
125
              json[prop].forEach(function (element) {
126
                for(var p in element) {
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...
127
                  if(element[p] !== ''){
128
                    valuesAreEmpty = false
129
                  }
130
                }
131
              })
132
              if(valuesAreEmpty){
133
                delete json[prop]
134
              }
135
            }
136
          }
137
138
          var obj = {
139
            publishAll:publishAll,
140
            type:type,
141
            template:{
142
              path: fullTpl
143
            },
144
            html: {
145
              path:pathIso.htmlPath
146
            },
147
            json: {
148
              content: json,
149
              path: pathIso.jsonPath
150
            }
151
          }
152
153
          obj = Hooks.instance.trigger('beforeSave', obj)
154
155
          obj.json.content[meta].complete = checkRequired(text, obj.json.content)
156
157
          var res = saveJsonAndHtml(tpl.replace(/^\/+/, ''), obj, text)
158
          if (isRejectedDoc) {
159
            res.reject = fileAttr.delete(url).replace(path.join(config.root, config.draft.url), '')
160
          }
161
          
162
          Hooks.instance.trigger('afterSave', obj)
163
          
164
          FileParser.copySiteAssets()
165
166
          if(typeof config.publishAll !== 'undefined' && config.publishAll !== null && config.publishAll === true) {
167
            if(!publishAll && type === 'publish') {
168
              abeProcess('publish-all', [`FILEPATH=${json.abe_meta.link}`])
169
            }
170
          }
171
172
          resolve(res)
173
        }).catch(function(e) {
174
          console.error('Save.js', e)
175
        })
176
  })
177
178
  return p
179
}
180
181
export function saveJsonAndHtml(templateId, obj, html) {
182
  var page = new Page(templateId, html, obj.json.content, true)
183
184
  saveHtml(obj.html.path, page.html)
185
  saveJson(obj.json.path, obj.json.content)
186
187
  return {
188
    json: obj.json.content,
189
    jsonPath: obj.json.path,
190
    html: page.html,
191
    htmlPath: obj.html.path
192
  }
193
}
194
195
export function saveJson(url, json) {
196
  mkdirp.sync(fileUtils.removeLast(url))
197
198
  if(typeof json.abe_source !== 'undefined' && json.abe_source !== null) {
199
    delete json.abe_source
200
  }
201
202
  var eachRecursive = function (obj) {
203
    for (var k in obj) {
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...
204
      if (typeof obj[k] === 'object' && obj[k] !== null){
205
        eachRecursive(obj[k])
206
      } else if (typeof obj[k] !== 'undefined' && obj[k] !== null){
207
        obj[k] = xss(obj[k].toString().replace(/"/g, '"'), { 'whiteList': config.htmlWhiteList })
208
      }
209
    }
210
  }
211
212
  eachRecursive(json)
213
214
  fse.writeJsonSync(url, json, {
215
    space: 2,
216
    encoding: 'utf-8'
217
  })
218
  return true
219
}
220
221
export function saveHtml(url, html) {
222
  mkdirp.sync(fileUtils.removeLast(url))
223
  if(fileAttr.test(url) && fileAttr.get(url).s !== 'd'){
224
    fileUtils.deleteOlderRevisionByType(fileAttr.delete(url), fileAttr.get(url).s)
225
  }
226
  fse.writeFileSync(url, html)
227
}
228
229
export function dateIso(tplUrl, type = null) {
230
  var newDateISO
231
  var dateISO
232
  var saveJsonFile = tplUrl.json.path
0 ignored issues
show
Unused Code introduced by
The assignment to variable saveJsonFile seems to be never used. Consider removing it.
Loading history...
233
  var saveFile = tplUrl['draft'].path
0 ignored issues
show
Unused Code introduced by
The assignment to variable saveFile seems to be never used. Consider removing it.
Loading history...
234
  
235
  switch(type) {
236
  case 'draft':
237
    newDateISO = dateSlug((new Date().toISOString()))
238
    dateISO = 'd' + newDateISO
239
    break
240
  case 'publish':
241
    saveJsonFile = tplUrl.publish.json
242
    saveFile = tplUrl.publish.path
243
    break
244
  default:
245
    newDateISO = dateSlug((new Date().toISOString()))
246
    dateISO = type[0] + newDateISO
247
    break
248
  }
249
250
  if(dateISO) {
251
    saveJsonFile = fileAttr.add(saveJsonFile, dateISO)
252
    saveFile = fileAttr.add(saveFile, dateISO)
253
  }
254
255
  return {
256
    jsonPath: saveJsonFile,
257
    htmlPath: saveFile
258
  }
259
}