module_api.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. package modules
  2. import (
  3. "bufio"
  4. "bytes"
  5. "html"
  6. "net/http"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. "time"
  11. "golang-fave/assets"
  12. "golang-fave/engine/fetdata"
  13. "golang-fave/engine/wrapper"
  14. "golang-fave/utils"
  15. "github.com/disintegration/imaging"
  16. )
  17. func (this *Modules) api_GenerateImage(wrap *wrapper.Wrapper, width, height int, resize bool, filename string) ([]byte, bool, string, error) {
  18. file_ext := ""
  19. if strings.ToLower(filepath.Ext(filename)) == ".png" {
  20. file_ext = "image/png"
  21. } else if strings.ToLower(filepath.Ext(filename)) == ".jpg" {
  22. file_ext = "image/jpeg"
  23. } else if strings.ToLower(filepath.Ext(filename)) == ".jpeg" {
  24. file_ext = "image/jpeg"
  25. }
  26. src, err := imaging.Open(filename)
  27. if err != nil {
  28. return []byte(""), false, file_ext, err
  29. }
  30. if !resize {
  31. src = imaging.Fill(src, width, height, imaging.Center, imaging.Lanczos)
  32. } else {
  33. src = imaging.Fit(src, width, height, imaging.Lanczos)
  34. }
  35. var out_bytes bytes.Buffer
  36. out := bufio.NewWriter(&out_bytes)
  37. if file_ext == "image/png" {
  38. imaging.Encode(out, src, imaging.PNG)
  39. } else if file_ext == "image/jpeg" {
  40. imaging.Encode(out, src, imaging.JPEG)
  41. } else {
  42. return []byte(""), false, file_ext, nil
  43. }
  44. return out_bytes.Bytes(), true, file_ext, nil
  45. }
  46. func (this *Modules) api_GenerateXmlCurrencies(wrap *wrapper.Wrapper) string {
  47. result := ``
  48. rows, err := wrap.DB.Query(
  49. `SELECT
  50. code,
  51. coefficient
  52. FROM
  53. shop_currencies
  54. ORDER BY
  55. id ASC
  56. ;`,
  57. )
  58. if err == nil {
  59. defer rows.Close()
  60. values := make([]string, 2)
  61. scan := make([]interface{}, len(values))
  62. for i := range values {
  63. scan[i] = &values[i]
  64. }
  65. for rows.Next() {
  66. err = rows.Scan(scan...)
  67. if err == nil {
  68. result += `<currency id="` + html.EscapeString(string(values[0])) + `" rate="` + html.EscapeString(string(values[1])) + `"/>`
  69. }
  70. }
  71. }
  72. return result
  73. }
  74. func (this *Modules) api_GenerateXmlCategories(wrap *wrapper.Wrapper) string {
  75. result := ``
  76. rows, err := wrap.DB.Query(
  77. `SELECT
  78. data.id,
  79. data.user,
  80. data.name,
  81. data.alias,
  82. data.lft,
  83. data.rgt,
  84. MAX(data.parent_id) AS parent_id
  85. FROM
  86. (
  87. SELECT
  88. node.id,
  89. node.user,
  90. node.name,
  91. node.alias,
  92. node.lft,
  93. node.rgt,
  94. parent.id AS parent_id
  95. FROM
  96. shop_cats AS node,
  97. shop_cats AS parent
  98. WHERE
  99. node.lft BETWEEN parent.lft AND parent.rgt AND
  100. node.id > 1
  101. ORDER BY
  102. node.lft ASC
  103. ) AS data
  104. WHERE
  105. data.id <> data.parent_id
  106. GROUP BY
  107. data.id
  108. ORDER BY
  109. data.lft ASC
  110. ;`,
  111. )
  112. if err == nil {
  113. defer rows.Close()
  114. values := make([]string, 7)
  115. scan := make([]interface{}, len(values))
  116. for i := range values {
  117. scan[i] = &values[i]
  118. }
  119. for rows.Next() {
  120. err = rows.Scan(scan...)
  121. if err == nil {
  122. if utils.StrToInt(string(values[6])) > 1 {
  123. result += `<category id="` + html.EscapeString(string(values[0])) + `" parentId="` + html.EscapeString(string(values[6])) + `">` + html.EscapeString(string(values[2])) + `</category>`
  124. } else {
  125. result += `<category id="` + html.EscapeString(string(values[0])) + `">` + html.EscapeString(string(values[2])) + `</category>`
  126. }
  127. }
  128. }
  129. }
  130. return result
  131. }
  132. func (this *Modules) api_GenerateXmlOfferPictures(wrap *wrapper.Wrapper, product_id int) string {
  133. result := ``
  134. rows, err := wrap.DB.Query(
  135. `SELECT
  136. shop_product_images.product_id,
  137. shop_product_images.filename
  138. FROM
  139. shop_product_images
  140. WHERE
  141. shop_product_images.product_id = ?
  142. ;`,
  143. product_id,
  144. )
  145. if err == nil {
  146. defer rows.Close()
  147. values := make([]string, 2)
  148. scan := make([]interface{}, len(values))
  149. for i := range values {
  150. scan[i] = &values[i]
  151. }
  152. for rows.Next() {
  153. err = rows.Scan(scan...)
  154. if err == nil {
  155. result += `<picture>` + html.EscapeString((*wrap.Config).API.XML.Url) + `products/images/` + html.EscapeString(string(values[0])) + `/` + html.EscapeString(string(values[1])) + `</picture>`
  156. }
  157. }
  158. }
  159. return result
  160. }
  161. func (this *Modules) api_GenerateXmlOfferAttributes(wrap *wrapper.Wrapper, product_id int) string {
  162. result := ``
  163. filter_ids := []int{}
  164. filter_names := map[int]string{}
  165. filter_values := map[int][]string{}
  166. rows, err := wrap.DB.Query(
  167. `SELECT
  168. shop_filters.id,
  169. shop_filters.filter,
  170. shop_filters_values.name
  171. FROM
  172. shop_filter_product_values
  173. LEFT JOIN shop_filters_values ON shop_filters_values.id = shop_filter_product_values.filter_value_id
  174. LEFT JOIN shop_filters ON shop_filters.id = shop_filters_values.filter_id
  175. WHERE
  176. shop_filter_product_values.product_id = ?
  177. ORDER BY
  178. shop_filters.filter ASC,
  179. shop_filters_values.name ASC
  180. ;`,
  181. product_id,
  182. )
  183. if err == nil {
  184. defer rows.Close()
  185. values := make([]string, 3)
  186. scan := make([]interface{}, len(values))
  187. for i := range values {
  188. scan[i] = &values[i]
  189. }
  190. for rows.Next() {
  191. err = rows.Scan(scan...)
  192. if err == nil {
  193. if !utils.InArrayInt(filter_ids, utils.StrToInt(string(values[0]))) {
  194. filter_ids = append(filter_ids, utils.StrToInt(string(values[0])))
  195. }
  196. filter_names[utils.StrToInt(string(values[0]))] = html.EscapeString(string(values[1]))
  197. filter_values[utils.StrToInt(string(values[0]))] = append(filter_values[utils.StrToInt(string(values[0]))], string(values[2]))
  198. }
  199. }
  200. }
  201. for _, filter_id := range filter_ids {
  202. result += `<param name="` + html.EscapeString(filter_names[filter_id]) + `">` + html.EscapeString(strings.Join(filter_values[filter_id], ", ")) + `</param>`
  203. }
  204. return result
  205. }
  206. func (this *Modules) api_GenerateXmlOffers(wrap *wrapper.Wrapper) string {
  207. result := ``
  208. rows, err := wrap.DB.Query(
  209. `SELECT
  210. shop_products.id,
  211. shop_currencies.code,
  212. shop_products.price,
  213. shop_products.name,
  214. shop_products.alias,
  215. shop_products.vendor,
  216. shop_products.quantity,
  217. shop_products.category,
  218. shop_products.content
  219. FROM
  220. shop_products
  221. LEFT JOIN shop_currencies ON shop_currencies.id = shop_products.currency
  222. WHERE
  223. shop_products.active = 1 AND
  224. shop_products.category > 1
  225. ORDER BY
  226. shop_products.id
  227. ;`,
  228. )
  229. if err == nil {
  230. defer rows.Close()
  231. values := make([]string, 9)
  232. scan := make([]interface{}, len(values))
  233. for i := range values {
  234. scan[i] = &values[i]
  235. }
  236. for rows.Next() {
  237. err = rows.Scan(scan...)
  238. if err == nil {
  239. result += `<offer id="` + html.EscapeString(string(values[0])) + `" available="true">`
  240. result += `<url>` + html.EscapeString((*wrap.Config).API.XML.Url) + `shop/` + html.EscapeString(string(values[4])) + `/</url>`
  241. result += `<price>` + utils.Float64ToStrF(utils.StrToFloat64(string(values[2])), "%.2f") + `</price>`
  242. result += `<currencyId>` + html.EscapeString(string(values[1])) + `</currencyId>`
  243. result += `<categoryId>` + html.EscapeString(string(values[7])) + `</categoryId>`
  244. result += this.api_GenerateXmlOfferPictures(wrap, utils.StrToInt(string(values[0])))
  245. result += `<vendor>` + html.EscapeString(string(values[5])) + `</vendor>`
  246. result += `<stock_quantity>` + html.EscapeString(string(values[6])) + `</stock_quantity>`
  247. result += `<name>` + html.EscapeString(string(values[3])) + `</name>`
  248. result += `<description><![CDATA[` + string(values[8]) + `]]></description>`
  249. result += this.api_GenerateXmlOfferAttributes(wrap, utils.StrToInt(string(values[0])))
  250. result += `</offer>`
  251. }
  252. }
  253. }
  254. return result
  255. }
  256. func (this *Modules) api_GenerateXml(wrap *wrapper.Wrapper) string {
  257. return `<?xml version="1.0" encoding="UTF-8"?>
  258. <!DOCTYPE yml_catalog SYSTEM "shops.dtd">
  259. <yml_catalog date="` + time.Unix(int64(time.Now().Unix()), 0).Format("2006-01-02 15:04") + `">
  260. <shop>
  261. <name>` + html.EscapeString((*wrap.Config).API.XML.Name) + `</name>
  262. <company>` + html.EscapeString((*wrap.Config).API.XML.Company) + `</company>
  263. <url>` + html.EscapeString((*wrap.Config).API.XML.Url) + `</url>
  264. <currencies>` + this.api_GenerateXmlCurrencies(wrap) + `</currencies>
  265. <categories>` + this.api_GenerateXmlCategories(wrap) + `</categories>
  266. <offers>` + this.api_GenerateXmlOffers(wrap) + `</offers>
  267. </shop>
  268. </yml_catalog>`
  269. }
  270. func (this *Modules) RegisterModule_Api() *Module {
  271. return this.newModule(MInfo{
  272. WantDB: true,
  273. Mount: "api",
  274. Name: "Api",
  275. Order: 803,
  276. System: true,
  277. Icon: assets.SysSvgIconPage,
  278. Sub: &[]MISub{},
  279. }, func(wrap *wrapper.Wrapper) {
  280. if len(wrap.UrlArgs) == 5 && wrap.UrlArgs[0] == "api" && wrap.UrlArgs[1] == "product-image" && (wrap.UrlArgs[2] == "thumb-0" || wrap.UrlArgs[2] == "thumb-1" || wrap.UrlArgs[2] == "thumb-2" || wrap.UrlArgs[2] == "thumb-3" || wrap.UrlArgs[2] == "thumb-full") {
  281. thumb_type := wrap.UrlArgs[2]
  282. product_id := wrap.UrlArgs[3]
  283. file_name := wrap.UrlArgs[4]
  284. original_file := wrap.DHtdocs + string(os.PathSeparator) + "products" + string(os.PathSeparator) + "images" + string(os.PathSeparator) + product_id + string(os.PathSeparator) + file_name
  285. if !utils.IsFileExists(original_file) {
  286. // User error 404 page
  287. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  288. return
  289. }
  290. width := (*wrap.Config).Shop.Thumbnails.Thumbnail0[0]
  291. height := (*wrap.Config).Shop.Thumbnails.Thumbnail0[1]
  292. resize := false
  293. if thumb_type == "thumb-1" {
  294. width = (*wrap.Config).Shop.Thumbnails.Thumbnail1[0]
  295. height = (*wrap.Config).Shop.Thumbnails.Thumbnail1[1]
  296. if (*wrap.Config).Shop.Thumbnails.Thumbnail1[2] == 1 {
  297. resize = true
  298. }
  299. } else if thumb_type == "thumb-2" {
  300. width = (*wrap.Config).Shop.Thumbnails.Thumbnail2[0]
  301. height = (*wrap.Config).Shop.Thumbnails.Thumbnail2[1]
  302. if (*wrap.Config).Shop.Thumbnails.Thumbnail2[2] == 1 {
  303. resize = true
  304. }
  305. } else if thumb_type == "thumb-3" {
  306. width = (*wrap.Config).Shop.Thumbnails.Thumbnail3[0]
  307. height = (*wrap.Config).Shop.Thumbnails.Thumbnail3[1]
  308. if (*wrap.Config).Shop.Thumbnails.Thumbnail3[2] == 1 {
  309. resize = true
  310. }
  311. } else if thumb_type == "thumb-full" {
  312. width = (*wrap.Config).Shop.Thumbnails.ThumbnailFull[0]
  313. height = (*wrap.Config).Shop.Thumbnails.ThumbnailFull[1]
  314. if (*wrap.Config).Shop.Thumbnails.ThumbnailFull[2] == 1 {
  315. resize = true
  316. }
  317. }
  318. target_file := wrap.DHtdocs + string(os.PathSeparator) + "products" + string(os.PathSeparator) + "images" + string(os.PathSeparator) + product_id + string(os.PathSeparator) + thumb_type + "-" + file_name
  319. if !utils.IsFileExists(target_file) {
  320. data, ok, ext, err := this.api_GenerateImage(wrap, width, height, resize, original_file)
  321. if err != nil {
  322. // System error 500
  323. utils.SystemErrorPageEngine(wrap.W, err)
  324. return
  325. }
  326. if !ok {
  327. // User error 404 page
  328. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  329. return
  330. }
  331. // Save file
  332. if file, err := os.Create(target_file); err == nil {
  333. file.Write(data)
  334. }
  335. wrap.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  336. wrap.W.Header().Set("Content-Type", ext)
  337. wrap.W.Write(data)
  338. } else {
  339. http.ServeFile(wrap.W, wrap.R, target_file)
  340. }
  341. } else if len(wrap.UrlArgs) == 2 && wrap.UrlArgs[0] == "api" && wrap.UrlArgs[1] == "products" {
  342. if (*wrap.Config).API.XML.Enabled == 1 {
  343. // Fix url
  344. if wrap.R.URL.Path[len(wrap.R.URL.Path)-1] != '/' {
  345. http.Redirect(wrap.W, wrap.R, wrap.R.URL.Path+"/"+utils.ExtractGetParams(wrap.R.RequestURI), 301)
  346. return
  347. }
  348. // XML
  349. wrap.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  350. wrap.W.Header().Set("Content-Type", "text/xml; charset=utf-8")
  351. wrap.W.WriteHeader(http.StatusOK)
  352. wrap.W.Write([]byte(this.api_GenerateXml(wrap)))
  353. } else {
  354. wrap.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  355. wrap.W.WriteHeader(http.StatusNotFound)
  356. wrap.W.Write([]byte("Disabled!"))
  357. }
  358. } else if len(wrap.UrlArgs) == 1 {
  359. // Fix url
  360. if wrap.R.URL.Path[len(wrap.R.URL.Path)-1] != '/' {
  361. http.Redirect(wrap.W, wrap.R, wrap.R.URL.Path+"/"+utils.ExtractGetParams(wrap.R.RequestURI), 301)
  362. return
  363. }
  364. // Some info
  365. wrap.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  366. wrap.W.WriteHeader(http.StatusOK)
  367. wrap.W.Write([]byte("Fave engine API mount point!"))
  368. } else {
  369. // User error 404 page
  370. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  371. return
  372. }
  373. }, func(wrap *wrapper.Wrapper) (string, string, string) {
  374. // No any page for back-end
  375. return "", "", ""
  376. })
  377. }