module_blog.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. package modules
  2. import (
  3. "html"
  4. "net/http"
  5. "strings"
  6. "golang-fave/assets"
  7. "golang-fave/consts"
  8. "golang-fave/engine/builder"
  9. "golang-fave/engine/fetdata"
  10. "golang-fave/engine/sqlw"
  11. "golang-fave/engine/wrapper"
  12. "golang-fave/utils"
  13. )
  14. func (this *Modules) RegisterModule_Blog() *Module {
  15. return this.newModule(MInfo{
  16. WantDB: true,
  17. Mount: "blog",
  18. Name: "Blog",
  19. Order: 1,
  20. System: false,
  21. Icon: assets.SysSvgIconList,
  22. Sub: &[]MISub{
  23. {Mount: "default", Name: "List of posts", Show: true, Icon: assets.SysSvgIconList},
  24. {Mount: "add", Name: "Add new post", Show: true, Icon: assets.SysSvgIconPlus},
  25. {Mount: "modify", Name: "Modify post", Show: false},
  26. {Sep: true, Show: true},
  27. {Mount: "categories", Name: "List of categories", Show: true, Icon: assets.SysSvgIconList},
  28. {Mount: "categories-add", Name: "Add new category", Show: true, Icon: assets.SysSvgIconPlus},
  29. {Mount: "categories-modify", Name: "Modify category", Show: false},
  30. },
  31. }, func(wrap *wrapper.Wrapper) {
  32. if len(wrap.UrlArgs) == 3 && wrap.UrlArgs[0] == "blog" && wrap.UrlArgs[1] == "category" && wrap.UrlArgs[2] != "" {
  33. // Blog category
  34. row := &utils.MySql_blog_category{}
  35. err := wrap.DB.QueryRow(`
  36. SELECT
  37. main.id,
  38. main.user,
  39. main.name,
  40. main.alias,
  41. main.lft,
  42. main.rgt,
  43. depth.depth,
  44. MAX(main.parent_id) AS parent_id
  45. FROM
  46. (
  47. SELECT
  48. node.id,
  49. node.user,
  50. node.name,
  51. node.alias,
  52. node.lft,
  53. node.rgt,
  54. parent.id AS parent_id
  55. FROM
  56. blog_cats AS node,
  57. blog_cats AS parent
  58. WHERE
  59. node.lft BETWEEN parent.lft AND parent.rgt AND
  60. node.id > 1
  61. ORDER BY
  62. node.lft ASC
  63. ) AS main
  64. LEFT JOIN (
  65. SELECT
  66. node.id,
  67. (COUNT(parent.id) - 1) AS depth
  68. FROM
  69. blog_cats AS node,
  70. blog_cats AS parent
  71. WHERE
  72. node.lft BETWEEN parent.lft AND parent.rgt
  73. GROUP BY
  74. node.id
  75. ORDER BY
  76. node.lft ASC
  77. ) AS depth ON depth.id = main.id
  78. WHERE
  79. main.id > 1 AND
  80. main.id <> main.parent_id AND
  81. main.alias = ?
  82. GROUP BY
  83. main.id
  84. ;`,
  85. wrap.UrlArgs[2],
  86. ).Scan(
  87. &row.A_id,
  88. &row.A_user,
  89. &row.A_name,
  90. &row.A_alias,
  91. &row.A_lft,
  92. &row.A_rgt,
  93. &row.A_depth,
  94. &row.A_parent,
  95. )
  96. if err != nil && err != wrapper.ErrNoRows {
  97. // System error 500
  98. utils.SystemErrorPageEngine(wrap.W, err)
  99. return
  100. } else if err == wrapper.ErrNoRows {
  101. // User error 404 page
  102. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  103. return
  104. }
  105. // Fix url
  106. if wrap.R.URL.Path[len(wrap.R.URL.Path)-1] != '/' {
  107. http.Redirect(wrap.W, wrap.R, wrap.R.URL.Path+"/"+utils.ExtractGetParams(wrap.R.RequestURI), 301)
  108. return
  109. }
  110. // Render template
  111. wrap.RenderFrontEnd("blog-category", fetdata.New(wrap, row, false), http.StatusOK)
  112. return
  113. } else if len(wrap.UrlArgs) == 2 && wrap.UrlArgs[0] == "blog" && wrap.UrlArgs[1] != "" {
  114. // Blog post
  115. row := &utils.MySql_blog_post{}
  116. err := wrap.DB.QueryRow(`
  117. SELECT
  118. id,
  119. user,
  120. name,
  121. alias,
  122. briefly,
  123. content,
  124. UNIX_TIMESTAMP(datetime) as datetime,
  125. active
  126. FROM
  127. blog_posts
  128. WHERE
  129. active = 1 and
  130. alias = ?
  131. LIMIT 1;`,
  132. wrap.UrlArgs[1],
  133. ).Scan(
  134. &row.A_id,
  135. &row.A_user,
  136. &row.A_name,
  137. &row.A_alias,
  138. &row.A_briefly,
  139. &row.A_content,
  140. &row.A_datetime,
  141. &row.A_active,
  142. )
  143. if err != nil && err != wrapper.ErrNoRows {
  144. // System error 500
  145. utils.SystemErrorPageEngine(wrap.W, err)
  146. return
  147. } else if err == wrapper.ErrNoRows {
  148. // User error 404 page
  149. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  150. return
  151. }
  152. // Fix url
  153. if wrap.R.URL.Path[len(wrap.R.URL.Path)-1] != '/' {
  154. http.Redirect(wrap.W, wrap.R, wrap.R.URL.Path+"/"+utils.ExtractGetParams(wrap.R.RequestURI), 301)
  155. return
  156. }
  157. // Render template
  158. wrap.RenderFrontEnd("blog-post", fetdata.New(wrap, row, false), http.StatusOK)
  159. return
  160. } else if len(wrap.UrlArgs) == 1 && wrap.UrlArgs[0] == "blog" {
  161. // Blog
  162. // Fix url
  163. if wrap.R.URL.Path[len(wrap.R.URL.Path)-1] != '/' {
  164. http.Redirect(wrap.W, wrap.R, wrap.R.URL.Path+"/"+utils.ExtractGetParams(wrap.R.RequestURI), 301)
  165. return
  166. }
  167. // Render template
  168. wrap.RenderFrontEnd("blog", fetdata.New(wrap, nil, false), http.StatusOK)
  169. return
  170. } else if (*wrap.Config).Engine.MainModule == 1 {
  171. // Render template
  172. wrap.RenderFrontEnd("blog", fetdata.New(wrap, nil, false), http.StatusOK)
  173. return
  174. }
  175. // User error 404 page
  176. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  177. }, func(wrap *wrapper.Wrapper) (string, string, string) {
  178. content := ""
  179. sidebar := ""
  180. if wrap.CurrSubModule == "" || wrap.CurrSubModule == "default" {
  181. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  182. {Name: "List of posts"},
  183. })
  184. content += builder.DataTable(
  185. wrap,
  186. "blog_posts",
  187. "id",
  188. "DESC",
  189. &[]builder.DataTableRow{
  190. {
  191. DBField: "id",
  192. },
  193. {
  194. DBField: "name",
  195. NameInTable: "Post / URL",
  196. CallBack: func(values *[]string) string {
  197. name := `<a href="/cp/` + wrap.CurrModule + `/modify/` + (*values)[0] + `/">` + html.EscapeString((*values)[1]) + `</a>`
  198. alias := html.EscapeString((*values)[2])
  199. return `<div>` + name + `</div><div><small>/blog/` + alias + `/</small></div>`
  200. },
  201. },
  202. {
  203. DBField: "alias",
  204. },
  205. {
  206. DBField: "datetime",
  207. DBExp: "UNIX_TIMESTAMP(`datetime`)",
  208. NameInTable: "Date / Time",
  209. Classes: "d-none d-md-table-cell",
  210. CallBack: func(values *[]string) string {
  211. t := int64(utils.StrToInt((*values)[3]))
  212. return `<div>` + utils.UnixTimestampToFormat(t, "02.01.2006") + `</div>` +
  213. `<div><small>` + utils.UnixTimestampToFormat(t, "15:04:05") + `</small></div>`
  214. },
  215. },
  216. {
  217. DBField: "active",
  218. NameInTable: "Active",
  219. Classes: "d-none d-sm-table-cell",
  220. CallBack: func(values *[]string) string {
  221. return builder.CheckBox(utils.StrToInt((*values)[4]))
  222. },
  223. },
  224. },
  225. func(values *[]string) string {
  226. return builder.DataTableAction(&[]builder.DataTableActionRow{
  227. {
  228. Icon: assets.SysSvgIconView,
  229. Href: `/blog/` + (*values)[2] + `/`,
  230. Hint: "View",
  231. Target: "_blank",
  232. },
  233. {
  234. Icon: assets.SysSvgIconEdit,
  235. Href: "/cp/" + wrap.CurrModule + "/modify/" + (*values)[0] + "/",
  236. Hint: "Edit",
  237. },
  238. {
  239. Icon: assets.SysSvgIconRemove,
  240. Href: "javascript:fave.ActionDataTableDelete(this,'blog-delete','" +
  241. (*values)[0] + "','Are you sure want to delete post?');",
  242. Hint: "Delete",
  243. Classes: "delete",
  244. },
  245. })
  246. },
  247. "/cp/"+wrap.CurrModule+"/",
  248. nil,
  249. nil,
  250. true,
  251. )
  252. } else if wrap.CurrSubModule == "categories" {
  253. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  254. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/" + wrap.CurrSubModule + "/"},
  255. {Name: "List of categories"},
  256. })
  257. content += builder.DataTable(
  258. wrap,
  259. "blog_cats",
  260. "id",
  261. "ASC",
  262. &[]builder.DataTableRow{
  263. {
  264. DBField: "id",
  265. },
  266. {
  267. DBField: "user",
  268. },
  269. {
  270. DBField: "name",
  271. NameInTable: "Category",
  272. CallBack: func(values *[]string) string {
  273. depth := utils.StrToInt((*values)[4]) - 1
  274. if depth < 0 {
  275. depth = 0
  276. }
  277. sub := strings.Repeat("&mdash; ", depth)
  278. name := `<a href="/cp/` + wrap.CurrModule + `/categories-modify/` + (*values)[0] + `/">` + sub + html.EscapeString((*values)[2]) + `</a>`
  279. return `<div>` + name + `</div>`
  280. },
  281. },
  282. {
  283. DBField: "alias",
  284. },
  285. {
  286. DBField: "depth",
  287. },
  288. },
  289. func(values *[]string) string {
  290. return builder.DataTableAction(&[]builder.DataTableActionRow{
  291. {
  292. Icon: assets.SysSvgIconView,
  293. Href: `/blog/category/` + (*values)[3] + `/`,
  294. Hint: "View",
  295. Target: "_blank",
  296. },
  297. {
  298. Icon: assets.SysSvgIconEdit,
  299. Href: "/cp/" + wrap.CurrModule + "/categories-modify/" + (*values)[0] + "/",
  300. Hint: "Edit",
  301. },
  302. {
  303. Icon: assets.SysSvgIconRemove,
  304. Href: "javascript:fave.ActionDataTableDelete(this,'blog-categories-delete','" +
  305. (*values)[0] + "','Are you sure want to delete category?');",
  306. Hint: "Delete",
  307. Classes: "delete",
  308. },
  309. })
  310. },
  311. "/cp/"+wrap.CurrModule+"/"+wrap.CurrSubModule+"/",
  312. nil,
  313. func(limit_offset int, pear_page int) (*sqlw.Rows, error) {
  314. return wrap.DB.Query(
  315. `SELECT
  316. node.id,
  317. node.user,
  318. node.name,
  319. node.alias,
  320. (COUNT(parent.id) - 1) AS depth
  321. FROM
  322. blog_cats AS node,
  323. blog_cats AS parent
  324. WHERE
  325. node.lft BETWEEN parent.lft AND parent.rgt AND
  326. node.id > 1
  327. GROUP BY
  328. node.id
  329. ORDER BY
  330. node.lft ASC
  331. ;`,
  332. )
  333. },
  334. false,
  335. )
  336. } else if wrap.CurrSubModule == "add" || wrap.CurrSubModule == "modify" {
  337. if wrap.CurrSubModule == "add" {
  338. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  339. {Name: "Add new post"},
  340. })
  341. } else {
  342. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  343. {Name: "Modify post"},
  344. })
  345. }
  346. data := utils.MySql_blog_post{
  347. A_id: 0,
  348. A_user: 0,
  349. A_name: "",
  350. A_alias: "",
  351. A_content: "",
  352. A_datetime: 0,
  353. A_active: 0,
  354. }
  355. if wrap.CurrSubModule == "modify" {
  356. if len(wrap.UrlArgs) != 3 {
  357. return "", "", ""
  358. }
  359. if !utils.IsNumeric(wrap.UrlArgs[2]) {
  360. return "", "", ""
  361. }
  362. err := wrap.DB.QueryRow(`
  363. SELECT
  364. id,
  365. user,
  366. name,
  367. alias,
  368. briefly,
  369. content,
  370. active
  371. FROM
  372. blog_posts
  373. WHERE
  374. id = ?
  375. LIMIT 1;`,
  376. utils.StrToInt(wrap.UrlArgs[2]),
  377. ).Scan(
  378. &data.A_id,
  379. &data.A_user,
  380. &data.A_name,
  381. &data.A_alias,
  382. &data.A_briefly,
  383. &data.A_content,
  384. &data.A_active,
  385. )
  386. if err != nil {
  387. return "", "", ""
  388. }
  389. }
  390. // All post current categories
  391. var selids []int
  392. if data.A_id > 0 {
  393. rows, err := wrap.DB.Query("SELECT category_id FROM blog_cat_post_rel WHERE post_id = ?;", data.A_id)
  394. if err == nil {
  395. defer rows.Close()
  396. values := make([]int, 1)
  397. scan := make([]interface{}, len(values))
  398. for i := range values {
  399. scan[i] = &values[i]
  400. }
  401. for rows.Next() {
  402. err = rows.Scan(scan...)
  403. if err == nil {
  404. selids = append(selids, int(values[0]))
  405. }
  406. }
  407. }
  408. }
  409. btn_caption := "Add"
  410. if wrap.CurrSubModule == "modify" {
  411. btn_caption = "Save"
  412. }
  413. content += builder.DataForm(wrap, []builder.DataFormField{
  414. {
  415. Kind: builder.DFKHidden,
  416. Name: "action",
  417. Value: "blog-modify",
  418. },
  419. {
  420. Kind: builder.DFKHidden,
  421. Name: "id",
  422. Value: utils.IntToStr(data.A_id),
  423. },
  424. {
  425. Kind: builder.DFKText,
  426. Caption: "Post name",
  427. Name: "name",
  428. Value: data.A_name,
  429. Required: true,
  430. Min: "1",
  431. Max: "255",
  432. },
  433. {
  434. Kind: builder.DFKText,
  435. Caption: "Post alias",
  436. Name: "alias",
  437. Value: data.A_alias,
  438. Hint: "Example: our-news",
  439. Max: "255",
  440. },
  441. {
  442. Kind: builder.DFKText,
  443. Caption: "Categories",
  444. Name: "cats",
  445. Value: "0",
  446. CallBack: func(field *builder.DataFormField) string {
  447. return `<div class="form-group n4">` +
  448. `<div class="row">` +
  449. `<div class="col-md-3">` +
  450. `<label for="lbl_parent">Categories</label>` +
  451. `</div>` +
  452. `<div class="col-md-9">` +
  453. `<div>` +
  454. `<select class="selectpicker form-control" id="lbl_cats" name="cats[]" data-live-search="true" multiple>` +
  455. this.blog_GetCategorySelectOptions(wrap, 0, 0, selids) +
  456. `</select>` +
  457. `</div>` +
  458. `</div>` +
  459. `</div>` +
  460. `</div>`
  461. },
  462. },
  463. {
  464. Kind: builder.DFKTextArea,
  465. Caption: "Briefly",
  466. Name: "briefly",
  467. Value: data.A_briefly,
  468. Classes: "briefly wysiwyg",
  469. },
  470. {
  471. Kind: builder.DFKTextArea,
  472. Caption: "Post content",
  473. Name: "content",
  474. Value: data.A_content,
  475. Classes: "wysiwyg",
  476. },
  477. {
  478. Kind: builder.DFKCheckBox,
  479. Caption: "Active",
  480. Name: "active",
  481. Value: utils.IntToStr(data.A_active),
  482. },
  483. {
  484. Kind: builder.DFKSubmit,
  485. Value: btn_caption,
  486. Target: "add-edit-button",
  487. },
  488. })
  489. if wrap.CurrSubModule == "add" {
  490. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Add</button>`
  491. } else {
  492. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
  493. }
  494. } else if wrap.CurrSubModule == "categories-add" || wrap.CurrSubModule == "categories-modify" {
  495. if wrap.CurrSubModule == "categories-add" {
  496. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  497. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/categories/"},
  498. {Name: "Add new category"},
  499. })
  500. } else {
  501. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  502. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/categories/"},
  503. {Name: "Modify category"},
  504. })
  505. }
  506. data := utils.MySql_blog_category{
  507. A_id: 0,
  508. A_user: 0,
  509. A_name: "",
  510. A_alias: "",
  511. A_lft: 0,
  512. A_rgt: 0,
  513. }
  514. if wrap.CurrSubModule == "categories-modify" {
  515. if len(wrap.UrlArgs) != 3 {
  516. return "", "", ""
  517. }
  518. if !utils.IsNumeric(wrap.UrlArgs[2]) {
  519. return "", "", ""
  520. }
  521. err := wrap.DB.QueryRow(`
  522. SELECT
  523. id,
  524. user,
  525. name,
  526. alias,
  527. lft,
  528. rgt
  529. FROM
  530. blog_cats
  531. WHERE
  532. id = ?
  533. LIMIT 1;`,
  534. utils.StrToInt(wrap.UrlArgs[2]),
  535. ).Scan(
  536. &data.A_id,
  537. &data.A_user,
  538. &data.A_name,
  539. &data.A_alias,
  540. &data.A_lft,
  541. &data.A_rgt,
  542. )
  543. if err != nil {
  544. return "", "", ""
  545. }
  546. }
  547. btn_caption := "Add"
  548. if wrap.CurrSubModule == "categories-modify" {
  549. btn_caption = "Save"
  550. }
  551. parentId := 0
  552. if wrap.CurrSubModule == "categories-modify" {
  553. parentId = this.blog_GetCategoryParentId(wrap, data.A_id)
  554. }
  555. content += builder.DataForm(wrap, []builder.DataFormField{
  556. {
  557. Kind: builder.DFKHidden,
  558. Name: "action",
  559. Value: "blog-categories-modify",
  560. },
  561. {
  562. Kind: builder.DFKHidden,
  563. Name: "id",
  564. Value: utils.IntToStr(data.A_id),
  565. },
  566. {
  567. Kind: builder.DFKText,
  568. Caption: "Parent",
  569. Name: "parent",
  570. Value: "0",
  571. CallBack: func(field *builder.DataFormField) string {
  572. return `<div class="form-group n2">` +
  573. `<div class="row">` +
  574. `<div class="col-md-3">` +
  575. `<label for="lbl_parent">Parent</label>` +
  576. `</div>` +
  577. `<div class="col-md-9">` +
  578. `<div>` +
  579. `<select class="selectpicker form-control" id="lbl_parent" name="parent" data-live-search="true">` +
  580. `<option title="Nothing selected" value="0">&mdash;</option>` +
  581. this.blog_GetCategorySelectOptions(wrap, data.A_id, parentId, []int{}) +
  582. `</select>` +
  583. `</div>` +
  584. `</div>` +
  585. `</div>` +
  586. `</div>`
  587. },
  588. },
  589. {
  590. Kind: builder.DFKText,
  591. Caption: "Name",
  592. Name: "name",
  593. Value: data.A_name,
  594. Required: true,
  595. Min: "1",
  596. Max: "255",
  597. },
  598. {
  599. Kind: builder.DFKText,
  600. Caption: "Alias",
  601. Name: "alias",
  602. Value: data.A_alias,
  603. Hint: "Example: popular-posts",
  604. Max: "255",
  605. },
  606. {
  607. Kind: builder.DFKSubmit,
  608. Value: btn_caption,
  609. Target: "add-edit-button",
  610. },
  611. })
  612. if wrap.CurrSubModule == "categories-add" {
  613. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Add</button>`
  614. } else {
  615. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
  616. }
  617. }
  618. return this.getSidebarModules(wrap), content, sidebar
  619. })
  620. }