module_blog.go 15 KB

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