module_blog.go 16 KB

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