module_blog.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  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. }
  171. // User error 404 page
  172. wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
  173. }, func(wrap *wrapper.Wrapper) (string, string, string) {
  174. content := ""
  175. sidebar := ""
  176. if wrap.CurrSubModule == "" || wrap.CurrSubModule == "default" {
  177. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  178. {Name: "List of posts"},
  179. })
  180. content += builder.DataTable(
  181. wrap,
  182. "blog_posts",
  183. "id",
  184. "DESC",
  185. &[]builder.DataTableRow{
  186. {
  187. DBField: "id",
  188. },
  189. {
  190. DBField: "name",
  191. NameInTable: "Post / URL",
  192. CallBack: func(values *[]string) string {
  193. name := `<a href="/cp/` + wrap.CurrModule + `/modify/` + (*values)[0] + `/">` + html.EscapeString((*values)[1]) + `</a>`
  194. alias := html.EscapeString((*values)[2])
  195. return `<div>` + name + `</div><div><small>/blog/` + alias + `/</small></div>`
  196. },
  197. },
  198. {
  199. DBField: "alias",
  200. },
  201. {
  202. DBField: "datetime",
  203. DBExp: "UNIX_TIMESTAMP(`datetime`)",
  204. NameInTable: "Date / Time",
  205. Classes: "d-none d-md-table-cell",
  206. CallBack: func(values *[]string) string {
  207. t := int64(utils.StrToInt((*values)[3]))
  208. return `<div>` + utils.UnixTimestampToFormat(t, "02.01.2006") + `</div>` +
  209. `<div><small>` + utils.UnixTimestampToFormat(t, "15:04:05") + `</small></div>`
  210. },
  211. },
  212. {
  213. DBField: "active",
  214. NameInTable: "Active",
  215. Classes: "d-none d-sm-table-cell",
  216. CallBack: func(values *[]string) string {
  217. return builder.CheckBox(utils.StrToInt((*values)[4]))
  218. },
  219. },
  220. },
  221. func(values *[]string) string {
  222. return builder.DataTableAction(&[]builder.DataTableActionRow{
  223. {
  224. Icon: assets.SysSvgIconView,
  225. Href: `/blog/` + (*values)[2] + `/`,
  226. Hint: "View",
  227. Target: "_blank",
  228. },
  229. {
  230. Icon: assets.SysSvgIconEdit,
  231. Href: "/cp/" + wrap.CurrModule + "/modify/" + (*values)[0] + "/",
  232. Hint: "Edit",
  233. },
  234. {
  235. Icon: assets.SysSvgIconRemove,
  236. Href: "javascript:fave.ActionDataTableDelete(this,'blog-delete','" +
  237. (*values)[0] + "','Are you sure want to delete post?');",
  238. Hint: "Delete",
  239. Classes: "delete",
  240. },
  241. })
  242. },
  243. "/cp/"+wrap.CurrModule+"/",
  244. nil,
  245. nil,
  246. true,
  247. )
  248. } else if wrap.CurrSubModule == "categories" {
  249. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  250. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/" + wrap.CurrSubModule + "/"},
  251. {Name: "List of categories"},
  252. })
  253. content += builder.DataTable(
  254. wrap,
  255. "blog_cats",
  256. "id",
  257. "ASC",
  258. &[]builder.DataTableRow{
  259. {
  260. DBField: "id",
  261. },
  262. {
  263. DBField: "user",
  264. },
  265. {
  266. DBField: "name",
  267. NameInTable: "Category",
  268. CallBack: func(values *[]string) string {
  269. depth := utils.StrToInt((*values)[4]) - 1
  270. if depth < 0 {
  271. depth = 0
  272. }
  273. sub := strings.Repeat("&mdash; ", depth)
  274. name := `<a href="/cp/` + wrap.CurrModule + `/categories-modify/` + (*values)[0] + `/">` + sub + html.EscapeString((*values)[2]) + `</a>`
  275. return `<div>` + name + `</div>`
  276. },
  277. },
  278. {
  279. DBField: "alias",
  280. },
  281. {
  282. DBField: "depth",
  283. },
  284. },
  285. func(values *[]string) string {
  286. return builder.DataTableAction(&[]builder.DataTableActionRow{
  287. {
  288. Icon: assets.SysSvgIconView,
  289. Href: `/blog/category/` + (*values)[3] + `/`,
  290. Hint: "View",
  291. Target: "_blank",
  292. },
  293. {
  294. Icon: assets.SysSvgIconEdit,
  295. Href: "/cp/" + wrap.CurrModule + "/categories-modify/" + (*values)[0] + "/",
  296. Hint: "Edit",
  297. },
  298. {
  299. Icon: assets.SysSvgIconRemove,
  300. Href: "javascript:fave.ActionDataTableDelete(this,'blog-categories-delete','" +
  301. (*values)[0] + "','Are you sure want to delete category?');",
  302. Hint: "Delete",
  303. Classes: "delete",
  304. },
  305. })
  306. },
  307. "/cp/"+wrap.CurrModule+"/"+wrap.CurrSubModule+"/",
  308. nil,
  309. func(limit_offset int, pear_page int) (*sqlw.Rows, error) {
  310. return wrap.DB.Query(
  311. `SELECT
  312. node.id,
  313. node.user,
  314. node.name,
  315. node.alias,
  316. (COUNT(parent.id) - 1) AS depth
  317. FROM
  318. blog_cats AS node,
  319. blog_cats AS parent
  320. WHERE
  321. node.lft BETWEEN parent.lft AND parent.rgt AND
  322. node.id > 1
  323. GROUP BY
  324. node.id
  325. ORDER BY
  326. node.lft ASC
  327. ;`,
  328. )
  329. },
  330. false,
  331. )
  332. } else if wrap.CurrSubModule == "add" || wrap.CurrSubModule == "modify" {
  333. if wrap.CurrSubModule == "add" {
  334. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  335. {Name: "Add new post"},
  336. })
  337. } else {
  338. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  339. {Name: "Modify post"},
  340. })
  341. }
  342. data := utils.MySql_blog_post{
  343. A_id: 0,
  344. A_user: 0,
  345. A_name: "",
  346. A_alias: "",
  347. A_content: "",
  348. A_datetime: 0,
  349. A_active: 0,
  350. }
  351. if wrap.CurrSubModule == "modify" {
  352. if len(wrap.UrlArgs) != 3 {
  353. return "", "", ""
  354. }
  355. if !utils.IsNumeric(wrap.UrlArgs[2]) {
  356. return "", "", ""
  357. }
  358. err := wrap.DB.QueryRow(`
  359. SELECT
  360. id,
  361. user,
  362. name,
  363. alias,
  364. briefly,
  365. content,
  366. active
  367. FROM
  368. blog_posts
  369. WHERE
  370. id = ?
  371. LIMIT 1;`,
  372. utils.StrToInt(wrap.UrlArgs[2]),
  373. ).Scan(
  374. &data.A_id,
  375. &data.A_user,
  376. &data.A_name,
  377. &data.A_alias,
  378. &data.A_briefly,
  379. &data.A_content,
  380. &data.A_active,
  381. )
  382. if err != nil {
  383. return "", "", ""
  384. }
  385. }
  386. // All post current categories
  387. var selids []int
  388. if data.A_id > 0 {
  389. rows, err := wrap.DB.Query("SELECT category_id FROM blog_cat_post_rel WHERE post_id = ?;", data.A_id)
  390. if err == nil {
  391. defer rows.Close()
  392. values := make([]int, 1)
  393. scan := make([]interface{}, len(values))
  394. for i := range values {
  395. scan[i] = &values[i]
  396. }
  397. for rows.Next() {
  398. err = rows.Scan(scan...)
  399. if err == nil {
  400. selids = append(selids, int(values[0]))
  401. }
  402. }
  403. }
  404. }
  405. btn_caption := "Add"
  406. if wrap.CurrSubModule == "modify" {
  407. btn_caption = "Save"
  408. }
  409. content += builder.DataForm(wrap, []builder.DataFormField{
  410. {
  411. Kind: builder.DFKHidden,
  412. Name: "action",
  413. Value: "blog-modify",
  414. },
  415. {
  416. Kind: builder.DFKHidden,
  417. Name: "id",
  418. Value: utils.IntToStr(data.A_id),
  419. },
  420. {
  421. Kind: builder.DFKText,
  422. Caption: "Post name",
  423. Name: "name",
  424. Value: data.A_name,
  425. Required: true,
  426. Min: "1",
  427. Max: "255",
  428. },
  429. {
  430. Kind: builder.DFKText,
  431. Caption: "Post alias",
  432. Name: "alias",
  433. Value: data.A_alias,
  434. Hint: "Example: our-news",
  435. Max: "255",
  436. },
  437. {
  438. Kind: builder.DFKText,
  439. Caption: "Categories",
  440. Name: "cats",
  441. Value: "0",
  442. CallBack: func(field *builder.DataFormField) string {
  443. return `<div class="form-group n4">` +
  444. `<div class="row">` +
  445. `<div class="col-md-3">` +
  446. `<label for="lbl_parent">Categories</label>` +
  447. `</div>` +
  448. `<div class="col-md-9">` +
  449. `<div>` +
  450. `<select class="selectpicker form-control" id="lbl_cats" name="cats[]" data-live-search="true" multiple>` +
  451. this.blog_GetCategorySelectOptions(wrap, 0, 0, selids) +
  452. `</select>` +
  453. `</div>` +
  454. `</div>` +
  455. `</div>` +
  456. `</div>`
  457. },
  458. },
  459. {
  460. Kind: builder.DFKTextArea,
  461. Caption: "Briefly",
  462. Name: "briefly",
  463. Value: data.A_briefly,
  464. Classes: "briefly wysiwyg",
  465. },
  466. {
  467. Kind: builder.DFKTextArea,
  468. Caption: "Post content",
  469. Name: "content",
  470. Value: data.A_content,
  471. Classes: "wysiwyg",
  472. },
  473. {
  474. Kind: builder.DFKCheckBox,
  475. Caption: "Active",
  476. Name: "active",
  477. Value: utils.IntToStr(data.A_active),
  478. },
  479. {
  480. Kind: builder.DFKSubmit,
  481. Value: btn_caption,
  482. Target: "add-edit-button",
  483. },
  484. })
  485. if wrap.CurrSubModule == "add" {
  486. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Add</button>`
  487. } else {
  488. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
  489. }
  490. } else if wrap.CurrSubModule == "categories-add" || wrap.CurrSubModule == "categories-modify" {
  491. if wrap.CurrSubModule == "categories-add" {
  492. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  493. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/categories/"},
  494. {Name: "Add new category"},
  495. })
  496. } else {
  497. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  498. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/categories/"},
  499. {Name: "Modify category"},
  500. })
  501. }
  502. data := utils.MySql_blog_category{
  503. A_id: 0,
  504. A_user: 0,
  505. A_name: "",
  506. A_alias: "",
  507. A_lft: 0,
  508. A_rgt: 0,
  509. }
  510. if wrap.CurrSubModule == "categories-modify" {
  511. if len(wrap.UrlArgs) != 3 {
  512. return "", "", ""
  513. }
  514. if !utils.IsNumeric(wrap.UrlArgs[2]) {
  515. return "", "", ""
  516. }
  517. err := wrap.DB.QueryRow(`
  518. SELECT
  519. id,
  520. user,
  521. name,
  522. alias,
  523. lft,
  524. rgt
  525. FROM
  526. blog_cats
  527. WHERE
  528. id = ?
  529. LIMIT 1;`,
  530. utils.StrToInt(wrap.UrlArgs[2]),
  531. ).Scan(
  532. &data.A_id,
  533. &data.A_user,
  534. &data.A_name,
  535. &data.A_alias,
  536. &data.A_lft,
  537. &data.A_rgt,
  538. )
  539. if err != nil {
  540. return "", "", ""
  541. }
  542. }
  543. btn_caption := "Add"
  544. if wrap.CurrSubModule == "categories-modify" {
  545. btn_caption = "Save"
  546. }
  547. parentId := 0
  548. if wrap.CurrSubModule == "categories-modify" {
  549. parentId = this.blog_GetCategoryParentId(wrap, data.A_id)
  550. }
  551. content += builder.DataForm(wrap, []builder.DataFormField{
  552. {
  553. Kind: builder.DFKHidden,
  554. Name: "action",
  555. Value: "blog-categories-modify",
  556. },
  557. {
  558. Kind: builder.DFKHidden,
  559. Name: "id",
  560. Value: utils.IntToStr(data.A_id),
  561. },
  562. {
  563. Kind: builder.DFKText,
  564. Caption: "Parent",
  565. Name: "parent",
  566. Value: "0",
  567. CallBack: func(field *builder.DataFormField) string {
  568. return `<div class="form-group n2">` +
  569. `<div class="row">` +
  570. `<div class="col-md-3">` +
  571. `<label for="lbl_parent">Parent</label>` +
  572. `</div>` +
  573. `<div class="col-md-9">` +
  574. `<div>` +
  575. `<select class="selectpicker form-control" id="lbl_parent" name="parent" data-live-search="true">` +
  576. `<option title="Nothing selected" value="0">&mdash;</option>` +
  577. this.blog_GetCategorySelectOptions(wrap, data.A_id, parentId, []int{}) +
  578. `</select>` +
  579. `</div>` +
  580. `</div>` +
  581. `</div>` +
  582. `</div>`
  583. },
  584. },
  585. {
  586. Kind: builder.DFKText,
  587. Caption: "Name",
  588. Name: "name",
  589. Value: data.A_name,
  590. Required: true,
  591. Min: "1",
  592. Max: "255",
  593. },
  594. {
  595. Kind: builder.DFKText,
  596. Caption: "Alias",
  597. Name: "alias",
  598. Value: data.A_alias,
  599. Hint: "Example: popular-posts",
  600. Max: "255",
  601. },
  602. {
  603. Kind: builder.DFKSubmit,
  604. Value: btn_caption,
  605. Target: "add-edit-button",
  606. },
  607. })
  608. if wrap.CurrSubModule == "categories-add" {
  609. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Add</button>`
  610. } else {
  611. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
  612. }
  613. }
  614. return this.getSidebarModules(wrap), content, sidebar
  615. })
  616. }