├── root ├── themes │ └── default │ │ ├── page.html │ │ ├── 404.html │ │ ├── author.html │ │ ├── blog.html │ │ ├── posts.html │ │ └── base.html ├── admin │ ├── wrapper.html │ ├── index.html │ ├── 404.html │ ├── appearance │ │ ├── index.html │ │ ├── menus.html │ │ ├── create.html │ │ └── menus_new.html │ ├── media │ │ └── index.html │ ├── settings │ │ ├── code_injection.html │ │ ├── user_new.html │ │ ├── database.html │ │ ├── users.html │ │ ├── user.html │ │ └── general.html │ ├── setup.html │ ├── posts │ │ ├── index.html │ │ └── create.html │ ├── login.html │ └── base.html └── static │ ├── dist │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ └── config.json │ ├── admin │ ├── dashboard.js │ └── dashboard.css │ └── themes │ ├── default │ └── blog.css │ └── default-dark │ └── blog.css ├── TODO ├── src ├── cmengine.cpp ├── cmengine.h ├── sqluserstore.h ├── cmdispatcher.h ├── main.cpp ├── libCMS │ ├── engine_p.h │ ├── menu_p.h │ ├── page_p.h │ ├── fileengine_p.h │ ├── menu.h │ ├── menu.cpp │ ├── fileengine.h │ ├── sqlengine.h │ ├── page.h │ ├── engine.cpp │ ├── page.cpp │ ├── engine.h │ └── fileengine.cpp ├── CMakeLists.txt ├── cmlyst.h ├── adminposts.h ├── adminsetup.h ├── adminmedia.h ├── adminposts.cpp ├── admin.h ├── root.h ├── adminappearance.h ├── adminpages.h ├── adminsettings.h ├── cmdispatcher.cpp ├── rsswriter.h ├── sqluserstore.cpp ├── adminsetup.cpp ├── admin.cpp ├── adminappearance.cpp ├── cmlyst.cpp ├── adminmedia.cpp ├── adminpages.cpp ├── rsswriter.cpp └── root.cpp ├── config.h.in ├── README.md └── CMakeLists.txt /root/themes/default/page.html: -------------------------------------------------------------------------------- 1 | {{page.content}} 2 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | *** Timeframe: Everytime *** 2 | 3 | * Fix all bugs, implement new features :-) 4 | -------------------------------------------------------------------------------- /root/admin/wrapper.html: -------------------------------------------------------------------------------- 1 | {% if no_wrapper %}{{ content }}{% else %}{% include "base.html" %}{% endif %} 2 | -------------------------------------------------------------------------------- /root/admin/index.html: -------------------------------------------------------------------------------- 1 | {% if admin %} 2 | Admin 3 | {% endif %} 4 | -------------------------------------------------------------------------------- /src/cmengine.cpp: -------------------------------------------------------------------------------- 1 | #include "cmengine.h" 2 | 3 | CMEngine::CMEngine() 4 | { 5 | 6 | } 7 | 8 | CMEngine::~CMEngine() 9 | { 10 | } 11 | 12 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | /* Version number of the software */ 5 | #define VERSION "@VERSION@" 6 | 7 | #endif /*CONFIG_H*/ 8 | -------------------------------------------------------------------------------- /root/static/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutelyst/CMlyst/HEAD/root/static/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /root/static/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutelyst/CMlyst/HEAD/root/static/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /root/static/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cutelyst/CMlyst/HEAD/root/static/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /root/admin/404.html: -------------------------------------------------------------------------------- 1 |
2 |

Page not found!

3 | Please verify the address and try again. 4 |
5 | -------------------------------------------------------------------------------- /root/themes/default/404.html: -------------------------------------------------------------------------------- 1 |
2 |

Page not found!

3 | Please verify the address and try again. 4 |
5 | -------------------------------------------------------------------------------- /src/cmengine.h: -------------------------------------------------------------------------------- 1 | #ifndef CMENGINE_H 2 | #define CMENGINE_H 3 | 4 | #include "libCMS/engine.h" 5 | 6 | class CMEngine 7 | { 8 | public: 9 | CMEngine(); 10 | virtual ~CMEngine(); 11 | 12 | CMS::Engine *engine = 0; 13 | }; 14 | 15 | #endif // CMENGINE_H 16 | -------------------------------------------------------------------------------- /src/sqluserstore.h: -------------------------------------------------------------------------------- 1 | #ifndef SQLUSERSTORE_H 2 | #define SQLUSERSTORE_H 3 | 4 | #include 5 | #include 6 | 7 | class SqlUserStore : public Cutelyst::AuthenticationStore 8 | { 9 | public: 10 | explicit SqlUserStore(); 11 | 12 | Cutelyst::AuthenticationUser findUser(Cutelyst::Context *c, const Cutelyst::ParamsMultiMap &userinfo) override; 13 | 14 | QString addUser(const Cutelyst::ParamsMultiMap &user, bool replace); 15 | }; 16 | 17 | #endif // SQLUSERSTORE_H 18 | -------------------------------------------------------------------------------- /root/themes/default/author.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ author.name }}

3 |

{{ author.bio }}

4 | 5 | {% if author.location %} {{author.location}} {% endif %} 6 | {% if author.website %} {{author.website}}{% endif %} 7 | {% if posts_count %} {{ posts_count }} Posts{% endif %} 8 |
9 |
10 | 11 | {% include "posts.html" %} 12 | -------------------------------------------------------------------------------- /root/themes/default/blog.html: -------------------------------------------------------------------------------- 1 | {% if listing %} 2 |
3 |

{{ cms.settings.title }}

4 |

{{ cms.settings.tagline }}

5 |
6 | {% endif %} 7 | 8 |
9 | 10 |
11 | 12 |
13 |

{{ page.name }}

14 | 15 | 16 |

{{page.content}}

17 |
18 | 19 |
20 | 21 |
22 | -------------------------------------------------------------------------------- /root/static/admin/dashboard.js: -------------------------------------------------------------------------------- 1 | $('#myModal').on('show', function() { 2 | var id = $(this).data('id'), removeBtn = $(this).find('.danger'); 3 | }) 4 | 5 | $('.confirm-delete').on('click', function(e) { 6 | e.preventDefault(); 7 | 8 | var id = $(this).data('id'); 9 | var path = $(this).data('path'); 10 | $('#myModal').data('path', path); 11 | $('#myModal').data('id', id).modal('show'); 12 | }); 13 | 14 | $('#btnYes').click(function() { 15 | // handle deletion here 16 | var $modalDiv = $('#myModal'); 17 | var id = $modalDiv.data('id'); 18 | var path = $modalDiv.data('path'); 19 | 20 | $modalDiv.addClass('loading'); 21 | $.post(path + id).then(function() { 22 | $('#row-id-'+id).remove(); 23 | $modalDiv.modal('hide').removeClass('loading'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /root/admin/appearance/index.html: -------------------------------------------------------------------------------- 1 |

{% if post_type == "page" %}Pages{% else %}Posts{% endif %}

2 | 3 | Add New 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for post in posts %} 16 | 17 | 21 | 22 | 23 | 24 | {% endfor %} 25 | 26 |
TitleAuthorDate
18 | {{ post.name }} 19 | /{{ post.path }} 20 | {{ post.author }}{{ post.modified }}
27 |
28 | -------------------------------------------------------------------------------- /root/admin/appearance/menus.html: -------------------------------------------------------------------------------- 1 |

Menus

2 | 3 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /src/cmdispatcher.h: -------------------------------------------------------------------------------- 1 | #ifndef CMDISPATCHER_H 2 | #define CMDISPATCHER_H 3 | 4 | #include 5 | 6 | #include "cmengine.h" 7 | 8 | using namespace Cutelyst; 9 | 10 | class CMDispatcher : public DispatchType, public CMEngine 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit CMDispatcher(QObject *parent = nullptr); 15 | ~CMDispatcher(); 16 | 17 | /** 18 | * @brief list the registered actions 19 | * To be implemented by subclasses 20 | */ 21 | virtual QByteArray list() const final; 22 | 23 | /** 24 | * Return true if the dispatchType matches the given path 25 | */ 26 | DispatchType::MatchType match(Context *c, QStringView pathView, const QStringList &args) const override final; 27 | 28 | /** 29 | * Returns an uri for an action 30 | */ 31 | virtual QString uriForAction(Action *action, const QStringList &captures) const final; 32 | 33 | virtual bool registerAction(Action *action) final; 34 | 35 | virtual bool inUse() final; 36 | 37 | private: 38 | Action *m_pageAction = nullptr; 39 | Action *m_latestPostsAction = nullptr; 40 | }; 41 | 42 | #endif // CMDISPATCHER_H 43 | -------------------------------------------------------------------------------- /root/admin/media/index.html: -------------------------------------------------------------------------------- 1 |

Media Library

2 | 3 |
4 |
5 | 6 | 7 |
8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for file in files %} 21 | 22 | 28 | 29 | 32 | 33 | {% endfor %} 34 | 35 |
FileDateActions
23 | 24 | {{ file.name }} 25 | 26 | {{ file.name }} 27 | {{ file.modified|date:"hh:mm dd/MM/yyyy" }} 30 | Delete 31 |
36 |
37 | -------------------------------------------------------------------------------- /root/static/dist/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "vars": {}, 3 | "css": [ 4 | "print.less", 5 | "type.less", 6 | "code.less", 7 | "grid.less", 8 | "tables.less", 9 | "forms.less", 10 | "buttons.less", 11 | "glyphicons.less", 12 | "button-groups.less", 13 | "input-groups.less", 14 | "navs.less", 15 | "navbar.less", 16 | "breadcrumbs.less", 17 | "pagination.less", 18 | "pager.less", 19 | "labels.less", 20 | "badges.less", 21 | "jumbotron.less", 22 | "thumbnails.less", 23 | "alerts.less", 24 | "progress-bars.less", 25 | "media.less", 26 | "list-group.less", 27 | "panels.less", 28 | "wells.less", 29 | "close.less", 30 | "dropdowns.less", 31 | "tooltip.less", 32 | "popovers.less", 33 | "modals.less", 34 | "carousel.less", 35 | "utilities.less", 36 | "responsive-utilities.less", 37 | "component-animations.less" 38 | ], 39 | "js": [ 40 | "alert.js", 41 | "button.js", 42 | "carousel.js", 43 | "dropdown.js", 44 | "modal.js", 45 | "tooltip.js", 46 | "popover.js", 47 | "tab.js", 48 | "affix.js", 49 | "collapse.js", 50 | "scrollspy.js", 51 | "transition.js" 52 | ] 53 | } -------------------------------------------------------------------------------- /root/admin/settings/code_injection.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Code Injection

4 | 5 |

6 | CMlyst allows you to inject code into the top and bottom of your theme files without editing them. This allows for quick modifications to insert useful things like tracking codes and meta tags. 7 |

8 | 9 |
10 | 11 |
12 | 13 |

Code here will be injected into the {{cms_head}} tag on every page of your site

14 | 15 |
16 | 17 |
18 | 19 |

Code here will be injected into the {{cms_foot}} tag on every page of your site

20 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /root/admin/setup.html: -------------------------------------------------------------------------------- 1 |
2 |

Configure an User

3 | 4 |
5 | 6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 | 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /root/admin/settings/user_new.html: -------------------------------------------------------------------------------- 1 |
2 |

Create an User

3 | 4 |
5 | 6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 | 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /root/admin/appearance/create.html: -------------------------------------------------------------------------------- 1 |

{% if post_type == "page" %}Add New Page{% else %}Add New Post{% endif %}

2 | 3 |
4 |
5 | 6 | 7 |
8 |
9 | {{ c.req.base }} 10 | 11 |
12 |
13 | 14 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /root/themes/default/posts.html: -------------------------------------------------------------------------------- 1 | {% if listing %} 2 |
3 |

{{ cms.settings.title }}

4 |

{{ cms.settings.tagline }}

5 |
6 | {% endif %} 7 | 8 | {% for page in posts %} 9 |
10 | 11 |
12 | 13 |
14 |

{{ page.name }}

15 | 16 | 17 |

{{page.content}}

18 |
19 | 20 |
21 | 22 |
23 | {% endfor %} 24 | 25 |
    26 |
  • «
  • 27 | {% for page in pagination.pages %} 28 |
  • {{ page }}
  • 29 | {% endfor %} 30 |
  • »
  • 31 |
32 | -------------------------------------------------------------------------------- /root/admin/settings/database.html: -------------------------------------------------------------------------------- 1 | 2 |

Database

3 | 4 | 5 |

Export data

6 |
7 |
8 | 11 |
12 |
13 | 16 |
17 |
18 | 21 |
22 | 23 |
24 | 25 |
26 | 27 |

Import data

28 |
29 |
30 | 31 | 32 |

Import data from another installation overwriting confliction data.

33 |
34 | 35 |
36 | 37 |
38 | 39 |

Delete all content

40 |
41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CMlyst 2 | CMlyst is a Content Management application built upon Cutelyst with support for Menus, Pages, Blogs and Feeds 3 | 4 | Help is welcome :) 5 | 6 | ## Dependencies 7 | * Cutelee 8 | * Cutelyst 2.11.0 with CuteleeView plugin enabled 9 | 10 | ## Configuration 11 | Create an INI file like cmlyst.conf with: 12 | 13 | [Cutelyst] 14 | DataLocation = /var/tmp/my_site_data 15 | production = true 16 | 17 | Where: 18 | * DataLocation is the place where images uploads and sqlite database will be placed 19 | * production when true will preload the theme templates, which is a lot faster but if you are customizing the theme you will need to reload the process 20 | 21 | ## Setup 22 | To create the first admin user set the SETUP enviroment variable, run the server and point your browser to http://localhost:3000/setup 23 | 24 | SETUP=1 cmlystd --http-socket :3000 --ini cmlyst.conf 25 | 26 | ## Running 27 | After the setup is done and you have your first admin using created unset the SETUP enviroment variable and run: 28 | 29 | cmlystd --http-socket :3000 --ini cmlyst.conf 30 | 31 | Now point your browser to http://localhost:3000/.admin configure and create your first pages/posts 32 | 33 | ## Paths 34 | * http://localhost:3000/.admin Admin interface 35 | * http://localhost:3000/.feed RSS feed 36 | * http://localhost:3000/.author/slug Author page 37 | 38 | -------------------------------------------------------------------------------- /root/static/admin/dashboard.css: -------------------------------------------------------------------------------- 1 | .sub-header { 2 | padding-bottom: 10px; 3 | border-bottom: 1px solid #eee; 4 | } 5 | 6 | /* Hide for mobile, show later */ 7 | .sidebar { 8 | display: none; 9 | } 10 | @media (min-width: 768px) { 11 | .sidebar { 12 | position: fixed; 13 | top: 0; 14 | bottom: 0; 15 | left: 0; 16 | z-index: 1000; 17 | display: block; 18 | padding: 20px; 19 | overflow-x: hidden; 20 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 21 | background-color: #f5f5f5; 22 | border-right: 1px solid #eee; 23 | } 24 | } 25 | 26 | /* Sidebar navigation */ 27 | .nav-sidebar { 28 | margin-right: -21px; /* 20px padding + 1px border */ 29 | margin-bottom: 20px; 30 | margin-left: -20px; 31 | } 32 | .nav-sidebar > li > a { 33 | padding-right: 20px; 34 | padding-left: 20px; 35 | } 36 | .nav-sidebar > .active > a { 37 | color: #fff; 38 | background-color: #428bca; 39 | } 40 | 41 | 42 | /* 43 | * Main content 44 | */ 45 | 46 | .main { 47 | padding: 20px; 48 | } 49 | @media (min-width: 768px) { 50 | .main { 51 | padding-right: 40px; 52 | padding-left: 40px; 53 | } 54 | } 55 | .main .page-header { 56 | margin-top: 0; 57 | } 58 | 59 | 60 | /* 61 | * Placeholder dashboard ideas 62 | */ 63 | 64 | .placeholders { 65 | margin-bottom: 30px; 66 | text-align: center; 67 | } 68 | .placeholders h4 { 69 | margin-bottom: 0; 70 | } 71 | .placeholder { 72 | margin-bottom: 20px; 73 | } 74 | .placeholder img { 75 | display: inline-block; 76 | border-radius: 50%; 77 | } 78 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021-2022 Daniel Nicoletti 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | */ 18 | #include 19 | 20 | #include 21 | 22 | #include "cmlyst.h" 23 | 24 | int main(int argc, char *argv[]) 25 | { 26 | QCoreApplication::setOrganizationName(QStringLiteral("Cutelyst")); 27 | QCoreApplication::setOrganizationDomain(QStringLiteral("cutelyst.org")); 28 | QCoreApplication::setApplicationName(QStringLiteral("cutelyst-wsgi")); 29 | QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0")); 30 | 31 | Cutelyst::Server server; 32 | server.setStaticMap({ 33 | QStringLiteral("/static=root/static") 34 | }); 35 | server.setChdir2(QStringLiteral(CMLYST_ROOT)); 36 | 37 | QCoreApplication app(argc, argv); 38 | 39 | server.parseCommandLine(app.arguments()); 40 | 41 | return server.exec(new CMlyst{}); 42 | } 43 | -------------------------------------------------------------------------------- /src/libCMS/engine_p.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ENGINE_P_H 21 | #define ENGINE_P_H 22 | 23 | #include 24 | #include 25 | 26 | #include "page.h" 27 | 28 | namespace CMS { 29 | 30 | class EnginePrivate 31 | { 32 | public: 33 | QHash pages; 34 | }; 35 | 36 | } 37 | 38 | #endif // ENGINE_P_H 39 | -------------------------------------------------------------------------------- /root/admin/settings/users.html: -------------------------------------------------------------------------------- 1 |

Users 2 | Add New

3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for user in users %} 15 | 16 | 19 | 20 | 21 | 22 | {% endfor %} 23 | 24 |
NameEmailActions
17 | {{ user.name }} 18 | {{ user.email }}Delete
25 |
26 | 27 | 28 | 46 | -------------------------------------------------------------------------------- /src/libCMS/menu_p.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef MENU_P_H 21 | #define MENU_P_H 22 | 23 | #include "menu.h" 24 | 25 | namespace CMS { 26 | 27 | class MenuPrivate 28 | { 29 | public: 30 | bool autoAddPages = false; 31 | QString id; 32 | QString name; 33 | QStringList locations; 34 | QList urls; 35 | }; 36 | 37 | } 38 | 39 | #endif // MENU_P_H 40 | 41 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Our sources 2 | set(cmlyst_SRCS 3 | ${TEMPLATES_SRC} 4 | libCMS/page.cpp 5 | libCMS/page_p.h 6 | libCMS/engine.cpp 7 | libCMS/engine_p.h 8 | # libCMS/fileengine.cpp 9 | # libCMS/fileengine_p.h 10 | libCMS/menu.cpp 11 | libCMS/menu_p.h 12 | libCMS/sqlengine.cpp 13 | sqluserstore.cpp 14 | cmengine.cpp 15 | cmdispatcher.cpp 16 | root.cpp 17 | admin.cpp 18 | adminsetup.cpp 19 | adminappearance.cpp 20 | adminpages.cpp 21 | adminposts.cpp 22 | adminmedia.cpp 23 | adminsettings.cpp 24 | cmlyst.cpp 25 | rsswriter.cpp 26 | ) 27 | 28 | # Create the application 29 | add_library(cmlyst SHARED ${cmlyst_SRCS}) 30 | 31 | # Link to Cutelyst 32 | target_link_libraries(cmlyst 33 | Cutelyst::Core 34 | Cutelyst::View::Cutelee 35 | Cutelyst::Utils::Sql 36 | Cutelyst::Authentication 37 | Cutelyst::StatusMessage 38 | Cutelyst::Utils::Pagination 39 | Cutelyst::Session 40 | Cutelee::Templates 41 | Qt::Core 42 | Qt::Network 43 | Qt::Sql 44 | ) 45 | 46 | add_executable(cmlystd ${cmlyst_SRCS} main.cpp) 47 | target_link_libraries(cmlystd 48 | Cutelyst::Core 49 | Cutelyst::View::Cutelee 50 | Cutelyst::Utils::Sql 51 | Cutelyst::Authentication 52 | Cutelyst::StatusMessage 53 | Cutelyst::Utils::Pagination 54 | Cutelyst::Session 55 | Cutelyst::Server 56 | Cutelee::Templates 57 | Qt::Core 58 | Qt::Network 59 | Qt::Sql 60 | ) 61 | add_compile_definitions(CMLYST_ROOT=\"${CMAKE_INSTALL_FULL_DATADIR}/cmlyst\") 62 | 63 | install(TARGETS cmlyst cmlystd 64 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime 65 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel 66 | ) 67 | 68 | install(DIRECTORY ../root 69 | DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/cmlyst 70 | ) 71 | -------------------------------------------------------------------------------- /src/cmlyst.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef CMLYST_H 21 | #define CMLYST_H 22 | 23 | #include 24 | 25 | class CMlyst : public Cutelyst::Application 26 | { 27 | Q_OBJECT 28 | CUTELYST_APPLICATION(IID "org.cutelyst.CMlyst") 29 | public: 30 | Q_INVOKABLE explicit CMlyst(QObject *parent = 0); 31 | ~CMlyst(); 32 | 33 | bool init() override; 34 | 35 | virtual bool postFork() override; 36 | }; 37 | 38 | #endif // CMLYST_H 39 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28 FATAL_ERROR) 2 | 3 | project(CMlyst VERSION 0.8.0 LANGUAGES CXX) 4 | 5 | include(GNUInstallDirs) 6 | 7 | find_package(Qt6 6.4 COMPONENTS 8 | Core 9 | Network 10 | Sql 11 | ) 12 | find_package(Cutelyst5Qt6 5.0.0 REQUIRED) 13 | find_package(Cutelee6Qt6 REQUIRED) 14 | 15 | # Auto generate moc files 16 | set(CMAKE_AUTOMOC ON) 17 | # As moc files are generated in the binary dir, tell CMake 18 | # to always look for includes there: 19 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 20 | 21 | set(CMAKE_CXX_STANDARD 20) 22 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 23 | set(CMAKE_CXX_EXTENSIONS OFF) 24 | 25 | set(CMLYST_APP_DIR "${LIB_INSTALL_DIR}/cutelyst-apps" CACHE PATH "Installation directory for the Cutelyst app") 26 | set(CMLYST_HTML_DIR "${SHARE_INSTALL_PREFIX}/cmlyst" CACHE PATH "Target directory for the Cutelee/HTML files") 27 | 28 | # Build time config definitions 29 | configure_file(config.h.in ${CMAKE_BINARY_DIR}/config.h) 30 | 31 | file(GLOB_RECURSE TEMPLATES_SRC root/*) 32 | 33 | install(DIRECTORY root DESTINATION ${CMLYST_HTML_DIR}) 34 | 35 | add_definitions( 36 | -DQT_NO_CAST_TO_ASCII 37 | -DQT_NO_CAST_FROM_ASCII 38 | -DQT_STRICT_ITERATORS 39 | -DQT_NO_URL_CAST_FROM_STRING 40 | -DQT_NO_CAST_FROM_BYTEARRAY 41 | -DQT_USE_QSTRINGBUILDER 42 | -DQT_USE_FAST_OPERATOR_PLUS 43 | -DQT_DISABLE_DEPRECATED_BEFORE=0x060000 44 | ) 45 | 46 | add_subdirectory(src) 47 | 48 | set(CPACK_PACKAGE_VENDOR "Cutelyst") 49 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "CMlyst.") 50 | set(CPACK_PACKAGE_CONTACT "dantti12@gmail.com.br") 51 | 52 | if(UNIX) 53 | if(NOT CPACK_GENERATOR) 54 | set(CPACK_GENERATOR "DEB") 55 | endif() 56 | 57 | set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) 58 | set(CPACK_STRIP_FILES 1) 59 | set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) 60 | if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") 61 | set(CPACK_DEBIAN_DEBUGINFO_PACKAGE ON) 62 | endif() 63 | endif() 64 | 65 | include(CPack) 66 | -------------------------------------------------------------------------------- /src/adminposts.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ADMINPOSTS_H 21 | #define ADMINPOSTS_H 22 | 23 | #include 24 | 25 | #include "cmengine.h" 26 | #include "adminpages.h" 27 | 28 | using namespace Cutelyst; 29 | 30 | class AdminPosts : public AdminPages 31 | { 32 | Q_OBJECT 33 | C_NAMESPACE(".admin/posts") 34 | public: 35 | explicit AdminPosts(Application *app = 0); 36 | 37 | virtual void index(Context *c) override; 38 | 39 | virtual void create(Context *c) override; 40 | 41 | virtual void edit(Context *c, const QString &id) override; 42 | }; 43 | 44 | #endif // ADMINPOSTS_H 45 | -------------------------------------------------------------------------------- /src/libCMS/page_p.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef PAGE_P_H 21 | #define PAGE_P_H 22 | 23 | #include 24 | #include 25 | 26 | #include "page.h" 27 | 28 | namespace CMS { 29 | 30 | class PagePrivate 31 | { 32 | public: 33 | QString uuid; 34 | QString title; 35 | QString path; 36 | Author author; 37 | Cutelee::SafeString content; 38 | QDateTime publishedAt; 39 | QDateTime updatedAt; 40 | QDateTime createdAt; 41 | int id = 0; 42 | bool page = false; 43 | bool published = false; 44 | bool allowComments = false; 45 | }; 46 | 47 | } 48 | 49 | #endif // PAGE_P_H 50 | 51 | -------------------------------------------------------------------------------- /src/adminsetup.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ADMINSETUP_H 21 | #define ADMINSETUP_H 22 | 23 | #include 24 | 25 | #include "cmengine.h" 26 | 27 | using namespace Cutelyst; 28 | 29 | class AdminSetup : public Controller, public CMEngine 30 | { 31 | Q_OBJECT 32 | C_NAMESPACE("") 33 | public: 34 | explicit AdminSetup(QObject *app = nullptr); 35 | 36 | C_ATTR(setup, :Local :AutoArgs) 37 | void setup(Context *c); 38 | 39 | C_ATTR(notFound, :Path) 40 | void notFound(Context *c); 41 | 42 | C_ATTR(End, :Private :ActionClass(RenderView) :View(admin)) 43 | bool End(Context *c); 44 | }; 45 | 46 | #endif // ADMINSETUP_H 47 | -------------------------------------------------------------------------------- /src/adminmedia.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2017 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ADMINMEDIA_H 21 | #define ADMINMEDIA_H 22 | 23 | #include 24 | #include 25 | 26 | using namespace Cutelyst; 27 | 28 | class AdminMedia : public Controller 29 | { 30 | Q_OBJECT 31 | C_NAMESPACE(".admin/media") 32 | public: 33 | explicit AdminMedia(QObject *app = 0); 34 | ~AdminMedia(); 35 | 36 | C_ATTR(index, :Path :AutoArgs) 37 | void index(Context *c); 38 | 39 | C_ATTR(upload, :Local :AutoArgs) 40 | void upload(Context *c); 41 | 42 | C_ATTR(remove, :Local :AutoArgs) 43 | void remove(Context *c, const QStringList &path); 44 | }; 45 | 46 | #endif // ADMINMEDIA_H 47 | -------------------------------------------------------------------------------- /root/admin/posts/index.html: -------------------------------------------------------------------------------- 1 |

{% if post_type == "page" %}Pages{% else %}Posts{% endif %} 2 | Add New

3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for post in posts %} 16 | 17 | 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 | 27 |
TitleAuthorUpdated atActions
18 | {{ post.name }} 19 | {% if post_type == "page" %}/{{ post.path }}{% endif %}{% if not post.published %} Draft{% endif %} 20 | {{ post.author.name }}{{ post.updated_at|date:"yyyy/MM/dd HH:mm" }}Delete
28 |
29 | 30 | 31 | 49 | -------------------------------------------------------------------------------- /src/adminposts.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "adminposts.h" 21 | 22 | #include "root.h" 23 | 24 | #include 25 | #include 26 | 27 | #include "libCMS/page.h" 28 | 29 | #include 30 | 31 | AdminPosts::AdminPosts(Application *app) : AdminPages(app) 32 | { 33 | } 34 | 35 | void AdminPosts::index(Context *c) 36 | { 37 | AdminPages::index(c, QStringLiteral("post"), CMS::Engine::Posts); 38 | } 39 | 40 | void AdminPosts::create(Context *c) 41 | { 42 | AdminPages::create(c, QStringLiteral("post"), false); 43 | } 44 | 45 | void AdminPosts::edit(Context *c, const QString &id) 46 | { 47 | AdminPages::edit(c, id, QStringLiteral("post"), false); 48 | } 49 | -------------------------------------------------------------------------------- /src/admin.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ADMIN_H 21 | #define ADMIN_H 22 | 23 | #include 24 | 25 | #include "cmengine.h" 26 | 27 | using namespace Cutelyst; 28 | 29 | class Admin : public Controller, public CMEngine 30 | { 31 | Q_OBJECT 32 | C_NAMESPACE(".admin") 33 | public: 34 | Admin(QObject *app); 35 | 36 | C_ATTR(notFound, :Path) 37 | void notFound(Context *c); 38 | 39 | C_ATTR(logout, :Local :AutoArgs) 40 | void logout(Context *c); 41 | 42 | C_ATTR(login, :Local :AutoArgs) 43 | void login(Context *c); 44 | 45 | private Q_SLOTS: 46 | bool Auto(Context *c); 47 | 48 | private: 49 | C_ATTR(End, :ActionClass(RenderView) :View(admin)) 50 | bool End(Context *c); 51 | }; 52 | 53 | #endif // ADMIN_H 54 | -------------------------------------------------------------------------------- /root/themes/default/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{meta_title}} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | {{cms_head}} 30 | 31 | 32 | 33 | 34 |
35 |
36 | 41 |
42 |
43 | 44 |
45 | 46 |
47 | {% include template %} 48 |
49 | 50 |
51 | 52 | 54 | 55 | 56 | 57 | {{cms_foot}} 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/libCMS/fileengine_p.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef FILEENGINE_P_H 21 | #define FILEENGINE_P_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "fileengine.h" 30 | 31 | namespace CMS { 32 | 33 | class FileEnginePrivate 34 | { 35 | public: 36 | Menu *createMenu(const QString &name, QObject *parent); 37 | 38 | QDir rootPath; 39 | QDir pagesPath; 40 | QSettings *settings; 41 | QFileInfo settingsInfo; 42 | QDateTime mainSettingsDT; 43 | QHash mainSettings; 44 | QHash pathPages; 45 | QList pages; 46 | QList posts; 47 | QList menus; 48 | QHash menuLocations; 49 | }; 50 | 51 | } 52 | 53 | #endif // FILEENGINE_P_H 54 | 55 | -------------------------------------------------------------------------------- /src/root.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2017 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ROOT_H 21 | #define ROOT_H 22 | 23 | #include 24 | #include 25 | 26 | #include "cmengine.h" 27 | 28 | using namespace Cutelyst; 29 | 30 | namespace CMS { 31 | class Engine; 32 | } 33 | 34 | class Root : public Controller, public CMEngine 35 | { 36 | Q_OBJECT 37 | C_NAMESPACE("") 38 | public: 39 | Root(QObject *app); 40 | ~Root(); 41 | 42 | public: 43 | C_ATTR(notFound, :Path) 44 | void notFound(Context *c); 45 | 46 | C_ATTR(page, :Page) 47 | void page(Cutelyst::Context *c); 48 | 49 | C_ATTR(lastPosts, :LatestPosts) 50 | void lastPosts(Cutelyst::Context *c); 51 | 52 | C_ATTR(feed, :Path(.feed)) 53 | void feed(Cutelyst::Context *c); 54 | 55 | C_ATTR(author, :Path(.author) :AutoArgs) 56 | void author(Cutelyst::Context *c, const QString &slug); 57 | 58 | private: 59 | C_ATTR(End, :ActionClass(RenderView)) 60 | bool End(Context *c); 61 | }; 62 | 63 | #endif // ROOT_H 64 | -------------------------------------------------------------------------------- /src/adminappearance.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ADMINAPPEARANCE_H 21 | #define ADMINAPPEARANCE_H 22 | 23 | #include 24 | #include "cmengine.h" 25 | 26 | using namespace Cutelyst; 27 | 28 | class AdminAppearance : public Controller, public CMEngine 29 | { 30 | Q_OBJECT 31 | C_NAMESPACE(".admin/appearance") 32 | public: 33 | explicit AdminAppearance(QObject *app = 0); 34 | ~AdminAppearance(); 35 | 36 | C_ATTR(index, :Path :AutoArgs) 37 | void index(Context *c); 38 | 39 | C_ATTR(menus, :Local :AutoArgs) 40 | void menus(Context *c); 41 | 42 | C_ATTR(menus_remove, :Path("menus/remove") :AutoArgs) 43 | void menus_remove(Context *c, const QString &id); 44 | 45 | C_ATTR(menus_new, :Path("menus/new") :AutoArgs) 46 | void menus_new(Context *c); 47 | 48 | C_ATTR(menus_edit, :Path("menus/edit") :AutoArgs) 49 | void menus_edit(Context *c, const QString &id); 50 | 51 | private: 52 | bool saveMenu(Context *c, CMS::Menu *menu, const ParamsMultiMap ¶ms, bool replace); 53 | }; 54 | 55 | #endif // ADMINAPPEARANCE_H 56 | -------------------------------------------------------------------------------- /src/adminpages.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ADMINPAGES_H 21 | #define ADMINPAGES_H 22 | 23 | #include 24 | #include 25 | 26 | #include "cmengine.h" 27 | 28 | using namespace Cutelyst; 29 | 30 | class AdminPages : public Controller, public CMEngine 31 | { 32 | Q_OBJECT 33 | C_NAMESPACE(".admin/pages") 34 | public: 35 | AdminPages(Application *app); 36 | ~AdminPages(); 37 | 38 | C_ATTR(index, :Path :AutoArgs) 39 | virtual void index(Context *c); 40 | 41 | C_ATTR(create, :Path("new") :Local :AutoArgs) 42 | virtual void create(Context *c); 43 | 44 | C_ATTR(edit, :Local :AutoArgs) 45 | virtual void edit(Context *c, const QString &id); 46 | 47 | C_ATTR(remove, :Path('delete') :AutoArgs) 48 | void remove(Context *c, const QString &id); 49 | 50 | protected: 51 | void index(Context *c, const QString &postType, CMS::Engine::Filter filters); 52 | void create(Context *c, const QString &postType, bool isPage); 53 | void edit(Context *c, const QString &id, const QString &postType, bool isPage); 54 | }; 55 | 56 | #endif // ADMINPAGES_H 57 | -------------------------------------------------------------------------------- /src/libCMS/menu.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef MENU_H 21 | #define MENU_H 22 | 23 | #include 24 | #include 25 | 26 | namespace CMS { 27 | 28 | class MenuPrivate; 29 | class Menu : public QObject 30 | { 31 | Q_OBJECT 32 | Q_DECLARE_PRIVATE(Menu) 33 | Q_PROPERTY(QString id READ id) 34 | Q_PROPERTY(QString name READ name) 35 | Q_PROPERTY(QStringList locations READ locations WRITE setLocations) 36 | Q_PROPERTY(QList entries READ entries) 37 | public: 38 | explicit Menu(const QString &name, QObject *parent = 0); 39 | ~Menu(); 40 | 41 | QString id() const; 42 | 43 | QString name() const; 44 | void setName(const QString &name); 45 | 46 | bool autoAddPages() const; 47 | void setAutoAddPages(bool enable); 48 | 49 | QStringList locations() const; 50 | void setLocations(const QStringList &locations); 51 | 52 | void appendEntry(const QString &text, const QString &url, const QString &attr = QString()); 53 | virtual QList entries() const; 54 | virtual void setEntries(const QList &entries); 55 | 56 | protected: 57 | MenuPrivate *d_ptr; 58 | }; 59 | 60 | } 61 | 62 | Q_DECLARE_METATYPE(CMS::Menu *) 63 | 64 | #endif // MENU_H 65 | -------------------------------------------------------------------------------- /root/admin/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CMlyst 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 55 | 56 | 57 | 58 |
59 | 74 | 75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /src/adminsettings.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ADMINSETTINGS_H 21 | #define ADMINSETTINGS_H 22 | 23 | #include 24 | 25 | #include "cmengine.h" 26 | 27 | using namespace Cutelyst; 28 | 29 | class AdminSettings : public Controller, public CMEngine 30 | { 31 | Q_OBJECT 32 | C_NAMESPACE(".admin/settings") 33 | public: 34 | explicit AdminSettings(Application *app = 0); 35 | 36 | C_ATTR(general, :Local :AutoArgs) 37 | void general(Context *c); 38 | 39 | C_ATTR(code_injection, :Path('code-injection') :AutoArgs) 40 | void code_injection(Context *c); 41 | 42 | C_ATTR(users, :Local :AutoArgs) 43 | void users(Context *c); 44 | 45 | C_ATTR(user, :Path('users') :AutoArgs) 46 | void user(Context *c, const QString &id); 47 | 48 | void updateUserData(Context *c, const QString &id, const ParamsMultiMap params); 49 | void changePassword(Context *c, const QString &id, const ParamsMultiMap params); 50 | 51 | C_ATTR(users_new, :Local :AutoArgs) 52 | void users_new(Context *c); 53 | 54 | C_ATTR(users_delete, :Local :AutoArgs) 55 | void users_delete(Context *c, const QString &id); 56 | 57 | C_ATTR(database, :Local :AutoArgs) 58 | void database(Context *c); 59 | 60 | C_ATTR(json_data, :Local :AutoArgs) 61 | void json_data(Context *c); 62 | 63 | void json_import(Context *c); 64 | void json_export(Context *c); 65 | 66 | C_ATTR(db_clean, :Local :AutoArgs) 67 | void db_clean(Context *c); 68 | }; 69 | 70 | #endif // ADMINSETTINGS_H 71 | -------------------------------------------------------------------------------- /root/admin/appearance/menus_new.html: -------------------------------------------------------------------------------- 1 |

{% if editing %}Edit Menu 2 |
3 | 5 |
6 | {% else %}Add New Menu{% endif %} 7 |

8 | 9 |
10 |
11 | 12 |
13 | 14 |
15 | {% for entry in menu.entries %} 16 |
17 | 22 |
23 |
24 | 25 | 26 |
27 |
28 |
29 | {% endfor %} 30 | 31 |
32 | 37 |
38 |
39 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 | 48 |
49 |
50 | 51 | 52 | 53 | 63 | -------------------------------------------------------------------------------- /root/static/themes/default/blog.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | body { 6 | font-family: Georgia, "Times New Roman", Times, serif; 7 | color: #555; 8 | } 9 | 10 | h1, .h1, 11 | h2, .h2, 12 | h3, .h3, 13 | h4, .h4, 14 | h5, .h5, 15 | h6, .h6 { 16 | margin-top: 0; 17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 18 | font-weight: normal; 19 | color: #333; 20 | } 21 | 22 | 23 | /* 24 | * Override Bootstrap's default container. 25 | */ 26 | 27 | @media (min-width: 1200px) { 28 | .container { 29 | width: 970px; 30 | } 31 | } 32 | 33 | 34 | /* 35 | * Masthead for nav 36 | */ 37 | 38 | .blog-masthead { 39 | background-color: #428bca; 40 | box-shadow: inset 0 -2px 5px rgba(0,0,0,.1); 41 | } 42 | 43 | /* Nav links */ 44 | .blog-nav-item { 45 | position: relative; 46 | display: inline-block; 47 | padding: 10px; 48 | font-weight: 500; 49 | color: #cdddeb; 50 | } 51 | .blog-nav-item:hover, 52 | .blog-nav-item:focus { 53 | color: #fff; 54 | text-decoration: none; 55 | } 56 | 57 | /* Active state gets a caret at the bottom */ 58 | .blog-nav .active { 59 | color: #fff; 60 | } 61 | .blog-nav .active:after { 62 | position: absolute; 63 | bottom: 0; 64 | left: 50%; 65 | width: 0; 66 | height: 0; 67 | margin-left: -5px; 68 | vertical-align: middle; 69 | content: " "; 70 | border-right: 5px solid transparent; 71 | border-bottom: 5px solid; 72 | border-left: 5px solid transparent; 73 | } 74 | 75 | 76 | /* 77 | * Blog name and description 78 | */ 79 | 80 | .blog-header { 81 | padding-top: 20px; 82 | padding-bottom: 20px; 83 | } 84 | .blog-title { 85 | margin-top: 30px; 86 | margin-bottom: 0; 87 | font-size: 60px; 88 | font-weight: normal; 89 | } 90 | .blog-description { 91 | font-size: 20px; 92 | color: #999; 93 | } 94 | 95 | 96 | /* 97 | * Main column and sidebar layout 98 | */ 99 | 100 | .blog-main { 101 | font-size: 18px; 102 | line-height: 1.5; 103 | } 104 | 105 | /* Sidebar modules for boxing content */ 106 | .sidebar-module { 107 | padding: 15px; 108 | margin: 0 -15px 15px; 109 | } 110 | .sidebar-module-inset { 111 | padding: 15px; 112 | background-color: #f5f5f5; 113 | border-radius: 4px; 114 | } 115 | .sidebar-module-inset p:last-child, 116 | .sidebar-module-inset ul:last-child, 117 | .sidebar-module-inset ol:last-child { 118 | margin-bottom: 0; 119 | } 120 | 121 | 122 | 123 | /* Pagination */ 124 | .pager { 125 | margin-bottom: 60px; 126 | text-align: left; 127 | } 128 | .pager > li > a { 129 | width: 140px; 130 | padding: 10px 20px; 131 | text-align: center; 132 | border-radius: 30px; 133 | } 134 | 135 | 136 | /* 137 | * Blog posts 138 | */ 139 | 140 | .blog-post { 141 | margin-bottom: 60px; 142 | } 143 | .blog-post-title { 144 | margin-bottom: 5px; 145 | font-size: 40px; 146 | } 147 | .blog-post-meta { 148 | margin-bottom: 20px; 149 | color: #999; 150 | } 151 | 152 | 153 | /* 154 | * Footer 155 | */ 156 | 157 | .blog-footer { 158 | padding: 40px 0; 159 | color: #999; 160 | text-align: center; 161 | background-color: #f9f9f9; 162 | border-top: 1px solid #e5e5e5; 163 | } -------------------------------------------------------------------------------- /root/static/themes/default-dark/blog.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | body { 6 | font-family: Georgia, "Times New Roman", Times, serif; 7 | color: #555; 8 | } 9 | 10 | h1, .h1, 11 | h2, .h2, 12 | h3, .h3, 13 | h4, .h4, 14 | h5, .h5, 15 | h6, .h6 { 16 | margin-top: 0; 17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 18 | font-weight: normal; 19 | color: #333; 20 | } 21 | 22 | 23 | /* 24 | * Override Bootstrap's default container. 25 | */ 26 | 27 | @media (min-width: 1200px) { 28 | .container { 29 | width: 970px; 30 | } 31 | } 32 | 33 | 34 | /* 35 | * Masthead for nav 36 | */ 37 | 38 | .blog-masthead { 39 | background-color: #000; 40 | box-shadow: inset 0 -2px 5px rgba(0,0,0,.1); 41 | } 42 | 43 | /* Nav links */ 44 | .blog-nav-item { 45 | position: relative; 46 | display: inline-block; 47 | padding: 10px; 48 | font-weight: 500; 49 | color: #cdddeb; 50 | } 51 | .blog-nav-item:hover, 52 | .blog-nav-item:focus { 53 | color: #fff; 54 | text-decoration: none; 55 | } 56 | 57 | /* Active state gets a caret at the bottom */ 58 | .blog-nav .active { 59 | color: #fff; 60 | } 61 | .blog-nav .active:after { 62 | position: absolute; 63 | bottom: 0; 64 | left: 50%; 65 | width: 0; 66 | height: 0; 67 | margin-left: -5px; 68 | vertical-align: middle; 69 | content: " "; 70 | border-right: 5px solid transparent; 71 | border-bottom: 5px solid; 72 | border-left: 5px solid transparent; 73 | } 74 | 75 | 76 | /* 77 | * Blog name and description 78 | */ 79 | 80 | .blog-header { 81 | padding-top: 20px; 82 | padding-bottom: 20px; 83 | } 84 | .blog-title { 85 | margin-top: 30px; 86 | margin-bottom: 0; 87 | font-size: 60px; 88 | font-weight: normal; 89 | } 90 | .blog-description { 91 | font-size: 20px; 92 | color: #999; 93 | } 94 | 95 | 96 | /* 97 | * Main column and sidebar layout 98 | */ 99 | 100 | .blog-main { 101 | font-size: 18px; 102 | line-height: 1.5; 103 | } 104 | 105 | /* Sidebar modules for boxing content */ 106 | .sidebar-module { 107 | padding: 15px; 108 | margin: 0 -15px 15px; 109 | } 110 | .sidebar-module-inset { 111 | padding: 15px; 112 | background-color: #f5f5f5; 113 | border-radius: 4px; 114 | } 115 | .sidebar-module-inset p:last-child, 116 | .sidebar-module-inset ul:last-child, 117 | .sidebar-module-inset ol:last-child { 118 | margin-bottom: 0; 119 | } 120 | 121 | 122 | 123 | /* Pagination */ 124 | .pager { 125 | margin-bottom: 60px; 126 | text-align: left; 127 | } 128 | .pager > li > a { 129 | width: 140px; 130 | padding: 10px 20px; 131 | text-align: center; 132 | border-radius: 30px; 133 | } 134 | 135 | 136 | /* 137 | * Blog posts 138 | */ 139 | 140 | .blog-post { 141 | margin-bottom: 60px; 142 | } 143 | .blog-post-title { 144 | margin-bottom: 5px; 145 | font-size: 40px; 146 | } 147 | .blog-post-meta { 148 | margin-bottom: 20px; 149 | color: #999; 150 | } 151 | 152 | 153 | /* 154 | * Footer 155 | */ 156 | 157 | .blog-footer { 158 | padding: 40px 0; 159 | color: #999; 160 | text-align: center; 161 | background-color: #f9f9f9; 162 | border-top: 1px solid #e5e5e5; 163 | } 164 | -------------------------------------------------------------------------------- /src/libCMS/menu.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "menu_p.h" 21 | 22 | #include 23 | #include 24 | 25 | using namespace CMS; 26 | 27 | Menu::Menu(const QString &id, QObject *parent) : QObject(parent) 28 | , d_ptr(new MenuPrivate) 29 | { 30 | Q_D(Menu); 31 | d->id = id; 32 | } 33 | 34 | Menu::~Menu() 35 | { 36 | delete d_ptr; 37 | } 38 | 39 | QString Menu::id() const 40 | { 41 | Q_D(const Menu); 42 | return d->id; 43 | } 44 | 45 | QString Menu::name() const 46 | { 47 | Q_D(const Menu); 48 | return d->name; 49 | } 50 | 51 | void Menu::setName(const QString &name) 52 | { 53 | Q_D(Menu); 54 | d->name = name; 55 | } 56 | 57 | bool Menu::autoAddPages() const 58 | { 59 | Q_D(const Menu); 60 | return d->autoAddPages; 61 | } 62 | 63 | void Menu::setAutoAddPages(bool enable) 64 | { 65 | Q_D(Menu); 66 | d->autoAddPages = enable; 67 | } 68 | 69 | QStringList Menu::locations() const 70 | { 71 | Q_D(const Menu); 72 | return d->locations; 73 | } 74 | 75 | void Menu::setLocations(const QStringList &locations) 76 | { 77 | Q_D(Menu); 78 | d->locations = locations; 79 | } 80 | 81 | void Menu::appendEntry(const QString &text, const QString &url, const QString &attr) 82 | { 83 | Q_D(Menu); 84 | d->urls.append({ 85 | {QStringLiteral("text"), text}, 86 | {QStringLiteral("url"), url}, 87 | {QStringLiteral("attr"), attr} 88 | }); 89 | } 90 | 91 | QList Menu::entries() const 92 | { 93 | Q_D(const Menu); 94 | return d->urls; 95 | } 96 | 97 | void Menu::setEntries(const QList &entries) 98 | { 99 | Q_D(Menu); 100 | d->urls = entries; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/libCMS/fileengine.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef FILEENGINE_H 21 | #define FILEENGINE_H 22 | 23 | #include 24 | #include 25 | 26 | #include "engine.h" 27 | 28 | namespace CMS { 29 | 30 | class FileEnginePrivate; 31 | class FileEngine : public Engine 32 | { 33 | Q_OBJECT 34 | Q_DECLARE_PRIVATE(FileEngine) 35 | public: 36 | explicit FileEngine(QObject *parent = 0); 37 | ~FileEngine(); 38 | 39 | virtual bool init(const QHash &settings) override; 40 | 41 | virtual Page *getPage(const QString &path, QObject *parent) override; 42 | 43 | virtual Page *loadPage(const QString &filename); 44 | 45 | virtual bool savePageBackend(Page *page) override; 46 | 47 | virtual QList listPages(QObject *parent, 48 | Filters filters = NoFilter, 49 | SortFlags sort = SortFlags(Date | Reversed), 50 | int depth = -1, 51 | int limit = -1) override; 52 | 53 | virtual QList menus() override; 54 | virtual QHash menuLocations() override; 55 | 56 | virtual bool saveMenu(Menu *menu, bool replace) override; 57 | virtual bool removeMenu(const QString &name) override; 58 | 59 | virtual QDateTime lastModified(); 60 | 61 | virtual bool settingsIsWritable() const override; 62 | virtual QHash settings(); 63 | virtual QString settingsValue(Cutelyst::Context *c, const QString &key, const QString &defaultValue = QString()) const override; 64 | virtual bool setSettingsValue(Cutelyst::Context *c, const QString &key, const QString &value); 65 | 66 | protected: 67 | FileEnginePrivate *d_ptr; 68 | 69 | void loadPages(); 70 | void settingsDirChanged(const QString &path); 71 | void loadSettings(); 72 | }; 73 | 74 | } 75 | 76 | #endif // FILEENGINE_H 77 | -------------------------------------------------------------------------------- /src/cmdispatcher.cpp: -------------------------------------------------------------------------------- 1 | #include "cmdispatcher.h" 2 | 3 | #include "libCMS/page.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | CMDispatcher::CMDispatcher(QObject *parent) : DispatchType(parent) 13 | { 14 | 15 | } 16 | 17 | CMDispatcher::~CMDispatcher() 18 | { 19 | 20 | } 21 | 22 | QByteArray CMDispatcher::list() const 23 | { 24 | if (!m_pageAction) { 25 | return QByteArray(); 26 | } 27 | 28 | QStringList page = { 29 | QStringLiteral("Page"), 30 | QLatin1Char('/') + m_pageAction->reverse() 31 | }; 32 | 33 | return Utils::buildTable({ page }, 34 | { QStringLiteral("Handle"), QStringLiteral("Private") }, 35 | QStringLiteral("Loaded Content Manager actions:")); 36 | } 37 | 38 | DispatchType::MatchType CMDispatcher::match(Context *c, QStringView pathView, const QStringList &args) const 39 | { 40 | // we only match absolute paths 41 | // Cutelyst v4 changed path to start with / 42 | const QString path = pathView.sliced(pathView.size() > 0 ? 1 : 0).toString(); 43 | if (!args.isEmpty() || path.endsWith(u'/')) { 44 | return NoMatch; 45 | } 46 | 47 | auto settings = engine->loadSettings(c); 48 | 49 | // See if we are on front page path and the settings says 50 | // it should show the latest posts, or if the desired page path is set 51 | // to show the latest posts 52 | bool showPostsOnFront = settings.value(QStringLiteral("show_on_front"), QStringLiteral("posts")) == QLatin1String("posts"); 53 | 54 | Request *req = c->request(); 55 | if ((path.isEmpty() && showPostsOnFront) || 56 | (!showPostsOnFront && settings.value(QStringLiteral("page_for_posts")) == path)) { 57 | req->setArguments(args); 58 | req->setMatch(path); 59 | setupMatchedAction(c, m_latestPostsAction); 60 | return ExactMatch; 61 | } 62 | 63 | CMS::Page *page; 64 | if (path.isEmpty() && !showPostsOnFront) { 65 | page = engine->getPage(settings.value(QStringLiteral("page_on_front")), c); 66 | } else { 67 | page = engine->getPage(path, c); 68 | } 69 | 70 | if (page && page->published()) { 71 | c->setStash(QStringLiteral("page"), QVariant::fromValue(page)); 72 | req->setArguments(args); 73 | req->setMatch(path); 74 | setupMatchedAction(c, m_pageAction); 75 | return ExactMatch; 76 | } 77 | 78 | return NoMatch; 79 | } 80 | 81 | QString CMDispatcher::uriForAction(Action *action, const QStringList &captures) const 82 | { 83 | return QString(); 84 | } 85 | 86 | bool CMDispatcher::registerAction(Action *action) 87 | { 88 | // qDebug() << action->attributes(); 89 | 90 | if (action->attributes().contains(QLatin1String("Page")) && !m_pageAction) { 91 | m_pageAction = action; 92 | return true; 93 | } 94 | 95 | if (action->attributes().contains(QLatin1String("LatestPosts")) && !m_latestPostsAction) { 96 | m_latestPostsAction = action; 97 | return true; 98 | } 99 | 100 | return false; 101 | } 102 | 103 | bool CMDispatcher::inUse() 104 | { 105 | return m_pageAction && m_latestPostsAction; 106 | } 107 | -------------------------------------------------------------------------------- /src/rsswriter.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef RSSWRITER_H 21 | #define RSSWRITER_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | class RSSWriter : public QObject 28 | { 29 | Q_OBJECT 30 | public: 31 | explicit RSSWriter(QIODevice *device, QObject *parent = 0); 32 | ~RSSWriter(); 33 | 34 | void startRSS(); 35 | 36 | void writeStartChannel(); 37 | void writeChannelTitle(const QString &title); 38 | void writeChannelLink(const QString &link); 39 | void writeChannelFeedLink(const QString &link, const QString &mimeType = QString(), const QString &rel = QString()); 40 | void writeChannelDescription(const QString &description); 41 | void writeChannelLastBuildDate(const QDateTime &lastBuildDate); 42 | void writeChannelLanguage(const QString &language); 43 | 44 | // Image of the feed 45 | void writeStartImage(); 46 | void writeImageUrl(const QString &url); 47 | void writeImageTitle(const QString &title); 48 | void writeImageLink(const QString &link); 49 | void writeEndImage(); 50 | 51 | void writeStartItem(); 52 | void writeItemTitle(const QString &title); 53 | /** 54 | * Item link http://foo.com/2015/12/31/bar 55 | */ 56 | void writeItemLink(const QString &link); 57 | /** 58 | * Item Comments link http://foo.com/2015/12/31/bar#comments 59 | */ 60 | void writeItemCommentsLink(const QString &link); 61 | void writeItemNumberOfComments(int number); 62 | void writeItemCreator(const QString &creator); 63 | void writeItemCategory(const QString &category); 64 | void writeItemPubDate(const QDateTime &pubDate); 65 | void writeItemDescription(const QString &description); 66 | void writeItemContent(const QString &content); 67 | 68 | void writeEndItem(); 69 | 70 | 71 | void writeEndChannel(); 72 | 73 | void endRSS(); 74 | 75 | private: 76 | QXmlStreamWriter m_stream; 77 | }; 78 | 79 | #endif // RSSWRITER_H 80 | -------------------------------------------------------------------------------- /src/sqluserstore.cpp: -------------------------------------------------------------------------------- 1 | #include "sqluserstore.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | using namespace Cutelyst; 18 | 19 | SqlUserStore::SqlUserStore() 20 | { 21 | 22 | } 23 | 24 | Cutelyst::AuthenticationUser SqlUserStore::findUser(Cutelyst::Context *c, const Cutelyst::ParamsMultiMap &userinfo) 25 | { 26 | Q_UNUSED(c) 27 | QSqlQuery query = CPreparedSqlQueryThreadForDB( 28 | QStringLiteral("SELECT * FROM users WHERE email = :email"), 29 | QStringLiteral("cmlyst")); 30 | query.bindValue(QStringLiteral(":email"), userinfo.value(QStringLiteral("email"))); 31 | if (query.exec() && query.next()) { 32 | QVariant userId = query.value(QStringLiteral("id")); 33 | 34 | AuthenticationUser user(userId.toString()); 35 | 36 | int columns = query.record().count(); 37 | // send column headers 38 | QStringList cols; 39 | const QSqlRecord record = query.record(); 40 | for (int j = 0; j < columns; ++j) { 41 | cols.append(record.fieldName(j)); 42 | } 43 | 44 | for (int j = 0; j < columns; ++j) { 45 | user.insert(cols.at(j), query.value(j)); 46 | } 47 | 48 | return user; 49 | } 50 | 51 | return AuthenticationUser(); 52 | } 53 | 54 | QString SqlUserStore::addUser(const ParamsMultiMap &user, bool replace) 55 | { 56 | QSqlQuery query; 57 | if (replace) { 58 | query = CPreparedSqlQueryThreadForDB( 59 | QStringLiteral("INSERT OR REPLACE INTO users " 60 | "(slug, email, password, json) " 61 | "VALUES " 62 | "(:slug, :email, :password, :json)"), 63 | QStringLiteral("cmlyst")); 64 | } else { 65 | query = CPreparedSqlQueryThreadForDB( 66 | QStringLiteral("INSERT INTO users " 67 | "(slug, email, password, json) " 68 | "VALUES " 69 | "(:slug, :email, :password, :json)"), 70 | QStringLiteral("cmlyst")); 71 | } 72 | 73 | const QString name = user.value(QStringLiteral("name")); 74 | QString slug = name; 75 | if (slug.isEmpty()) { 76 | slug = name.section(QLatin1Char(' '), 0, 0); 77 | } 78 | slug.remove(QRegularExpression(QStringLiteral("[^\\w]"))); 79 | slug = slug.left(50).toLower().toHtmlEscaped(); 80 | query.bindValue(QStringLiteral(":slug"), slug); 81 | 82 | query.bindValue(QStringLiteral(":email"), user.value(QStringLiteral("email"))); 83 | query.bindValue(QStringLiteral(":password"), user.value(QStringLiteral("password"))); 84 | 85 | QJsonObject obj; 86 | obj.insert(QStringLiteral("name"), 87 | name.left(150).toHtmlEscaped()); 88 | query.bindValue(QStringLiteral(":json"), QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact))); 89 | 90 | if (!query.exec()) { 91 | qDebug() << "Failed to add new user:" << query.lastError().databaseText() << user; 92 | return QString(); 93 | } 94 | return slug; 95 | } 96 | -------------------------------------------------------------------------------- /root/admin/settings/user.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{user.name}}

4 | 5 |
6 |
7 | 8 | 9 |

Use your real name so people can recognise you

10 |
11 | 12 |
13 | 14 | 15 |

{{author_url}}/{{user.slug}}

16 |
17 | 18 |
19 | 20 | 21 |

Used for login and notifications

22 |
23 |
24 | 25 | 26 |

Where in the world do you live?

27 |
28 |
29 | 30 | 31 |

Have a website or blog other than this one? Link it!

32 |
33 |
34 | 35 | 36 |

URL of your personal Facebook Profile

37 |
38 |
39 | 40 | 41 |

URL of your personal Twitter profile

42 |
43 |
44 | 45 | 46 |

Write about you, in 200 characters or less.

47 |
48 | 49 |
50 | 51 |
52 | 53 | 54 |
55 |
56 | 57 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 |
66 |
67 |
68 | -------------------------------------------------------------------------------- /root/admin/posts/create.html: -------------------------------------------------------------------------------- 1 |
2 |

{% if post_type == "page" %}{% if editting %}Edit Page{% else %}Add New Page{% endif %}{% else %}{% if editting %}Edit Post{% else %}Add New Post{% endif %}{% endif %} 3 | 4 | {% if published and editting %} 5 | {% elif editting %}{% endif %} 6 |

7 | 8 |
9 | 10 |
11 |
12 |
13 |
{{ c.req.base }}{% if post_type == "post" and not editting %}{% now "yyyy/MM/dd" %}{% endif %}
14 | 15 |
16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 | 24 | 25 | 57 | -------------------------------------------------------------------------------- /src/libCMS/sqlengine.h: -------------------------------------------------------------------------------- 1 | #ifndef SQLENGINE_H 2 | #define SQLENGINE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "engine.h" 9 | 10 | class QSqlQuery; 11 | 12 | namespace Cutelyst { 13 | class Context; 14 | } 15 | 16 | namespace CMS { 17 | 18 | class FileEnginePrivate; 19 | class SqlEngine : public Engine 20 | { 21 | Q_OBJECT 22 | public: 23 | explicit SqlEngine(QObject *parent = 0); 24 | 25 | bool init(const QHash &settings) override; 26 | 27 | Page *getPage(const QString &path, QObject *parent) override; 28 | 29 | Page *getPageById(const QString &id, QObject *parent) override; 30 | 31 | bool removePage(int id) override; 32 | 33 | /** 34 | * Returns the available pages, 35 | * when depth is -1 all pages are listed 36 | */ 37 | QList listPages(QObject *parent, 38 | int offset, 39 | int limit) override; 40 | 41 | QList listPagesPublished(QObject *parent, 42 | int offset, 43 | int limit) override; 44 | 45 | QList listPosts(QObject *parent, 46 | int offset, 47 | int limit) override; 48 | 49 | QList listPostsPublished(QObject *parent, 50 | int offset, 51 | int limit) override; 52 | 53 | QList listAuthorPostsPublished(QObject *parent, 54 | int authorId, 55 | int offset, 56 | int limit) override; 57 | 58 | QHash settings() const override; 59 | 60 | QString settingsValue(const QString &key, const QString &defaultValue = QString()) const override; 61 | bool setSettingsValue(Cutelyst::Context *c, const QString &key, const QString &value) override; 62 | 63 | QList menus() override; 64 | 65 | bool saveMenu(Cutelyst::Context *c, Menu *menu, bool replace) override; 66 | bool removeMenu(Cutelyst::Context *c, const QString &name) override; 67 | 68 | QHash menuLocations() override; 69 | 70 | bool settingsIsWritable() const override; 71 | 72 | QHash loadSettings(Cutelyst::Context *c) override; 73 | 74 | QDateTime lastModified() override; 75 | 76 | QString addUser(Cutelyst::Context *c, const Cutelyst::ParamsMultiMap &user, bool replace) override; 77 | bool removeUser(Cutelyst::Context *c, int id) override; 78 | QVariantList users() override; 79 | QHash user(const QString &slug) override; 80 | QHash user(int id) override; 81 | 82 | private: 83 | int savePageBackend(Page *page) override; 84 | 85 | void loadMenus(); 86 | void loadUsers(); 87 | void configureView(Cutelyst::Context *c); 88 | void createDb(); 89 | Page *createPageObj(const QSqlQuery &query, QObject *parent); 90 | 91 | QString m_theme; 92 | QVariantList m_users; 93 | QHash > m_usersSlug; 94 | QHash > m_usersId; 95 | QHash m_settings; 96 | QDateTime m_settingsDateTime; 97 | QTimeZone m_timezone; 98 | qint64 m_settingsDate = -1; 99 | QList m_menus; 100 | QHash m_menuLocations; 101 | }; 102 | 103 | } 104 | 105 | #endif // SQLENGINE_H 106 | -------------------------------------------------------------------------------- /root/admin/settings/general.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

General

4 | 5 |
6 | 7 |
8 | 9 |
10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 | 24 |
25 |
26 |
27 | 28 |
29 | 33 |
34 |
35 | 36 |
37 | 38 |
39 |
40 | 42 |
43 |
44 | 46 |
47 |
48 | 49 |
50 | 56 |
57 |
58 |
59 | 60 |
61 | 67 |
68 |
69 |
70 |
71 | 72 |
73 | 74 |

How many posts should be displayed on each page

75 |
76 |
77 | 78 | -------------------------------------------------------------------------------- /src/libCMS/page.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef CMS_PAGE_H 21 | #define CMS_PAGE_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | typedef QHash Author; 29 | 30 | namespace CMS { 31 | 32 | class PagePrivate; 33 | class Page : public QObject 34 | { 35 | Q_OBJECT 36 | Q_DECLARE_PRIVATE(Page) 37 | Q_PROPERTY(int id READ id WRITE setId) 38 | Q_PROPERTY(QString uuid READ uuid WRITE setUuid) 39 | Q_PROPERTY(QString name READ title WRITE setTitle) 40 | Q_PROPERTY(QString path READ path WRITE setPath) 41 | Q_PROPERTY(Author author READ author WRITE setAuthor) 42 | Q_PROPERTY(Cutelee::SafeString content READ content) 43 | Q_PROPERTY(QDateTime published_at READ publishedAt WRITE setPublishedAt) 44 | Q_PROPERTY(QDateTime updated_at READ updated WRITE setUpdated) 45 | Q_PROPERTY(QDateTime created_at READ created WRITE setCreated) 46 | Q_PROPERTY(bool published READ published WRITE setPublished) 47 | Q_PROPERTY(bool page READ page WRITE setPage) 48 | Q_PROPERTY(bool allowComments READ allowComments WRITE setAllowComments) 49 | public: 50 | Page(QObject *parent); 51 | virtual ~Page(); 52 | 53 | int id() const; 54 | void setId(int id); 55 | 56 | QString uuid() const; 57 | void setUuid(const QString &uuid); 58 | 59 | QString title() const; 60 | void setTitle(const QString &title); 61 | 62 | QString path() const; 63 | void setPath(const QString &path); 64 | 65 | Author author() const; 66 | void setAuthor(const Author &author); 67 | 68 | Cutelee::SafeString content() const; 69 | void setContent(const QString &body, bool safe); 70 | void updateContent(const Cutelee::SafeString &body); 71 | 72 | bool published() const; 73 | void setPublished(bool enable); 74 | 75 | QDateTime publishedAt() const; 76 | void setPublishedAt(const QDateTime &dateTime); 77 | 78 | QDateTime updated() const; 79 | void setUpdated(const QDateTime &dateTime); 80 | 81 | QDateTime created() const; 82 | void setCreated(const QDateTime &dateTime); 83 | 84 | bool page() const; 85 | void setPage(bool enable); 86 | 87 | bool allowComments() const; 88 | void setAllowComments(bool allow); 89 | 90 | protected: 91 | PagePrivate *d_ptr; 92 | }; 93 | 94 | } 95 | 96 | #endif // CMS_PAGE_H 97 | -------------------------------------------------------------------------------- /src/adminsetup.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2017 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "adminsetup.h" 21 | 22 | #include "root.h" 23 | #include "sqluserstore.h" 24 | 25 | #include 26 | 27 | #include 28 | 29 | AdminSetup::AdminSetup(QObject *app) : Controller(app) 30 | { 31 | } 32 | 33 | void AdminSetup::setup(Context *c) 34 | { 35 | qDebug() << Q_FUNC_INFO; 36 | if (c->req()->isPost()) { 37 | ParamsMultiMap params = c->req()->bodyParameters(); 38 | QString username = params.value(QLatin1String("username")); 39 | QString email = params.value(QLatin1String("email")); 40 | QString password = params.value(QLatin1String("password")); 41 | QString password2 = params.value(QLatin1String("password2")); 42 | c->setStash(QStringLiteral("usernname"), username); 43 | c->setStash(QStringLiteral("email"), email); 44 | 45 | if (password == password2) { 46 | if (password.size() < 10) { 47 | c->setStash(QStringLiteral("error_msg"), QStringLiteral("Password must be longer than 10 characters")); 48 | } else { 49 | password = QString::fromLatin1(CredentialPassword::createPassword(password.toUtf8(), 50 | QCryptographicHash::Sha256, 51 | 1000, 24, 24)); 52 | const QString slug = engine->addUser(c, { 53 | {QStringLiteral("name"), username}, 54 | {QStringLiteral("email"), email}, 55 | {QStringLiteral("password"), password}, 56 | }, 57 | true); 58 | if (!slug.isEmpty()) { 59 | c->setStash(QStringLiteral("status_msg"), QStringLiteral("User successfuly added, restart the application without SETUP environment set")); 60 | } else { 61 | c->setStash(QStringLiteral("error_msg"), QStringLiteral("Failed to add user, check application logs")); 62 | } 63 | } 64 | } else { 65 | c->setStash(QStringLiteral("error_msg"), QStringLiteral("The two password didn't match")); 66 | } 67 | } 68 | 69 | c->setStash(QStringLiteral("template"), QStringLiteral("setup.html")); 70 | } 71 | 72 | void AdminSetup::notFound(Context *c) 73 | { 74 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("setup")))); 75 | } 76 | 77 | bool AdminSetup::End(Context *c) 78 | { 79 | Q_UNUSED(c); 80 | return true; 81 | } 82 | -------------------------------------------------------------------------------- /src/admin.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "admin.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | Admin::Admin(QObject *app) : Controller(app) 30 | { 31 | } 32 | 33 | bool Admin::Auto(Context *c) 34 | { 35 | engine->loadSettings(c); 36 | 37 | StatusMessage::load(c); 38 | 39 | if (c->action() == CActionFor(u"login") || 40 | c->action() == CActionFor(u"logout")) { 41 | return true; 42 | } 43 | 44 | if (!Authentication::userExists(c)) { 45 | qDebug() << "*** Admin::Auto() User not found forwarding to /.admin/login"; 46 | c->res()->redirect(c->uriFor(CActionFor(u"login"))); 47 | return false; 48 | } 49 | 50 | c->setObjectName(QStringLiteral("CMlyst")); 51 | 52 | c->setStash(QStringLiteral("adminbase"), true); 53 | c->setStash(QStringLiteral("user"), Authentication::user(c)); 54 | 55 | return true; 56 | } 57 | 58 | void Admin::notFound(Context *c) 59 | { 60 | c->setStash(QStringLiteral("template"), QStringLiteral("404.html")); 61 | c->res()->setStatus(404); 62 | } 63 | 64 | bool Admin::End(Context *c) 65 | { 66 | Q_UNUSED(c) 67 | return true; 68 | } 69 | 70 | void Admin::logout(Cutelyst::Context *c) 71 | { 72 | Authentication::logout(c); 73 | c->res()->redirect(c->uriFor(CActionFor(u"login"))); 74 | } 75 | 76 | void Admin::login(Context *c) 77 | { 78 | Request *req = c->request(); 79 | const ParamsMultiMap params = req->bodyParams(); 80 | const QString username = params.value(QStringLiteral("email")); 81 | if (req->isPost()) { 82 | const QString password = params.value(QStringLiteral("password")); 83 | if (!username.isEmpty() && !password.isEmpty()) { 84 | 85 | // Authenticate 86 | if (Authentication::authenticate(c, params)) { 87 | qDebug() << Q_FUNC_INFO << username << "is now Logged in"; 88 | c->res()->redirect(c->uriFor(QStringLiteral("/.admin/posts"))); 89 | return; 90 | } else { 91 | c->setStash(QStringLiteral("error_msg"), tr("Wrong password or username")); 92 | qDebug() << Q_FUNC_INFO << username << "user or password invalid"; 93 | } 94 | } else { 95 | qWarning() << "Empty username and password"; 96 | } 97 | c->res()->setStatus(Response::Forbidden); 98 | } else { 99 | qWarning() << "Non POST method"; 100 | } 101 | 102 | c->setStash(QStringLiteral("username"), username); 103 | c->setStash(QStringLiteral("no_wrapper"), true); 104 | c->setStash(QStringLiteral("template"), QStringLiteral("login.html")); 105 | } 106 | -------------------------------------------------------------------------------- /src/libCMS/engine.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "engine.h" 21 | #include "menu.h" 22 | #include "page.h" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | using namespace CMS; 30 | 31 | Engine::Engine(QObject *parent) : QObject(parent) 32 | { 33 | 34 | } 35 | 36 | Engine::~Engine() 37 | { 38 | 39 | } 40 | 41 | int Engine::savePage(Cutelyst::Context *c, Page *page) 42 | { 43 | int ret = savePageBackend(page); 44 | if (ret) { 45 | QList autoMenus = menus(); 46 | Q_FOREACH (Menu *menu, autoMenus) { 47 | if (menu->autoAddPages()) { 48 | menu->appendEntry(page->title(), page->path()); 49 | saveMenu(c, menu, true); 50 | } 51 | } 52 | } 53 | return ret; 54 | } 55 | 56 | Menu *Engine::menu(const QString &id) 57 | { 58 | Q_FOREACH (Menu *menu, menus()) { 59 | if (menu->id() == id) { 60 | return menu; 61 | } 62 | } 63 | return 0; 64 | } 65 | 66 | QVariant Engine::menusProperty() 67 | { 68 | return QVariant::fromValue(menuLocations()); 69 | } 70 | 71 | bool Engine::saveMenu(Cutelyst::Context *c, Menu *menu, bool replace) 72 | { 73 | Q_UNUSED(c) 74 | Q_UNUSED(menu) 75 | Q_UNUSED(replace) 76 | return false; 77 | } 78 | 79 | bool Engine::removeMenu(Cutelyst::Context *c, const QString &name) 80 | { 81 | Q_UNUSED(c) 82 | Q_UNUSED(name) 83 | return false; 84 | } 85 | 86 | bool Engine::saveMenus(Cutelyst::Context *c, const QList &menus) 87 | { 88 | Q_FOREACH (Menu *menu, menus) { 89 | if (!saveMenu(c, menu, true)) { 90 | return false; 91 | } 92 | } 93 | 94 | return true; 95 | } 96 | 97 | QDateTime Engine::lastModified() 98 | { 99 | return QDateTime(); 100 | } 101 | 102 | QVariant Engine::settingsProperty() 103 | { 104 | return QVariant::fromValue(settings()); 105 | } 106 | 107 | QString Engine::normalizePath(const QString &path) 108 | { 109 | QString ret; 110 | // "/foo/bar/Iam a big...path" turns into 111 | // "/foo/bar/iam-a-big-path" 112 | QStringList parts = path.split(u'/', Qt::SkipEmptyParts); 113 | for (int i = 0; i < parts.size(); ++i) { 114 | parts.replace(i, normalizeTitle(parts.at(i))); 115 | } 116 | ret = parts.join(u'/'); 117 | if (ret.isNull()) { 118 | ret = QLatin1String(""); 119 | } 120 | return ret; 121 | } 122 | 123 | QString Engine::normalizeTitle(const QString &title) 124 | { 125 | // "Iam a big/small...path" turns into 126 | // "/foo/bar/iam-a-bigsmall-path" 127 | QString ret = title.toLower(); 128 | 129 | // turn separators into space 130 | ret.replace(u'.', QChar::Space); 131 | ret.replace(u'-', QChar::Space); 132 | 133 | // remove everything that is not a word or space 134 | static QRegularExpression re(QStringLiteral("[^\\w\\s]")); 135 | ret.remove(re); 136 | 137 | // remove abused space 138 | ret = ret.simplified(); 139 | 140 | // turn space into dashes 141 | ret.replace(QChar::Space, QLatin1Char('-')); 142 | 143 | return ret; 144 | } 145 | -------------------------------------------------------------------------------- /src/libCMS/page.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "page_p.h" 21 | #include "engine.h" 22 | 23 | #include 24 | #include 25 | 26 | using namespace CMS; 27 | 28 | Page::Page(QObject *parent) : QObject(parent) 29 | , d_ptr(new PagePrivate) 30 | { 31 | // qDebug() << Q_FUNC_INFO; 32 | } 33 | 34 | Page::~Page() 35 | { 36 | delete d_ptr; 37 | // qDebug() << Q_FUNC_INFO; 38 | } 39 | 40 | int Page::id() const 41 | { 42 | Q_D(const Page); 43 | return d->id; 44 | } 45 | 46 | void Page::setId(int id) 47 | { 48 | Q_D(Page); 49 | d->id = id; 50 | } 51 | 52 | QString Page::uuid() const 53 | { 54 | Q_D(const Page); 55 | return d->uuid; 56 | } 57 | 58 | void Page::setUuid(const QString &uuid) 59 | { 60 | Q_D(Page); 61 | if (!uuid.isEmpty()) { 62 | d->uuid = uuid; 63 | } else { 64 | d->uuid = QUuid::createUuid().toString().remove(QLatin1Char('{')).remove(QLatin1Char('}')); 65 | } 66 | } 67 | 68 | QString Page::title() const 69 | { 70 | Q_D(const Page); 71 | return d->title; 72 | } 73 | 74 | void Page::setTitle(const QString &title) 75 | { 76 | Q_D(Page); 77 | d->title = title; 78 | } 79 | 80 | QString Page::path() const 81 | { 82 | Q_D(const Page); 83 | return d->path; 84 | } 85 | 86 | void Page::setPath(const QString &path) 87 | { 88 | Q_D(Page); 89 | d->path = path; 90 | } 91 | 92 | Author Page::author() const 93 | { 94 | Q_D(const Page); 95 | return d->author; 96 | } 97 | 98 | void Page::setAuthor(const Author &author) 99 | { 100 | Q_D(Page); 101 | d->author = author; 102 | } 103 | 104 | Cutelee::SafeString Page::content() const 105 | { 106 | Q_D(const Page); 107 | return d->content; 108 | } 109 | 110 | void Page::setContent(const QString &body, bool safe) 111 | { 112 | Q_D(Page); 113 | d->content = Cutelee::SafeString(body, safe); 114 | } 115 | 116 | void Page::updateContent(const Cutelee::SafeString &body) 117 | { 118 | Q_D(Page); 119 | d->content = body; 120 | } 121 | 122 | bool Page::published() const 123 | { 124 | Q_D(const Page); 125 | return d->published; 126 | } 127 | 128 | void Page::setPublished(bool enable) 129 | { 130 | Q_D(Page); 131 | d->published = enable; 132 | } 133 | 134 | QDateTime Page::publishedAt() const 135 | { 136 | Q_D(const Page); 137 | return d->publishedAt; 138 | } 139 | 140 | void Page::setPublishedAt(const QDateTime &dateTime) 141 | { 142 | Q_D(Page); 143 | d->publishedAt = dateTime; 144 | } 145 | 146 | QDateTime Page::updated() const 147 | { 148 | Q_D(const Page); 149 | return d->updatedAt; 150 | } 151 | 152 | void Page::setUpdated(const QDateTime &dateTime) 153 | { 154 | Q_D(Page); 155 | d->updatedAt = dateTime; 156 | } 157 | 158 | QDateTime Page::created() const 159 | { 160 | Q_D(const Page); 161 | // TODO fix Grantlee 162 | return d->createdAt.toLocalTime(); 163 | } 164 | 165 | void Page::setCreated(const QDateTime &dateTime) 166 | { 167 | Q_D(Page); 168 | d->createdAt = dateTime; 169 | } 170 | 171 | bool Page::page() const 172 | { 173 | Q_D(const Page); 174 | return d->page; 175 | } 176 | 177 | void Page::setPage(bool enable) 178 | { 179 | Q_D(Page); 180 | d->page = enable; 181 | } 182 | 183 | bool Page::allowComments() const 184 | { 185 | Q_D(const Page); 186 | return d->allowComments; 187 | } 188 | 189 | void Page::setAllowComments(bool allow) 190 | { 191 | Q_D(Page); 192 | d->allowComments = allow; 193 | } 194 | -------------------------------------------------------------------------------- /root/admin/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | CMlyst 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 |
32 |
33 | 68 |
69 | 70 |
71 | 72 | {% if status_msg %}{% endif %} 73 | {% if error_msg %}{% endif %} 74 |
75 | {{ content }} 76 |
77 | 78 |
79 | 80 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/adminappearance.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "adminappearance.h" 21 | 22 | #include "libCMS/menu.h" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | AdminAppearance::AdminAppearance(QObject *app) : Controller(app) 30 | { 31 | 32 | } 33 | 34 | AdminAppearance::~AdminAppearance() 35 | { 36 | 37 | } 38 | 39 | void AdminAppearance::index(Context *c) 40 | { 41 | c->response()->redirect(c->uriFor(CActionFor(u"menus"))); 42 | } 43 | 44 | void AdminAppearance::menus(Context *c) 45 | { 46 | c->stash({ 47 | {QStringLiteral("template"), QStringLiteral("appearance/menus.html")}, 48 | {QStringLiteral("menus"), QVariant::fromValue(engine->menus())} 49 | }); 50 | qDebug() << engine << engine->menus(); 51 | } 52 | 53 | void AdminAppearance::menus_remove(Context *c, const QString &id) 54 | { 55 | if (c->req()->isPost()) { 56 | engine->removeMenu(c, id); 57 | } 58 | 59 | c->response()->redirect(c->uriFor(CActionFor(u"menus"))); 60 | } 61 | 62 | void AdminAppearance::menus_new(Context *c) 63 | { 64 | if (c->req()->isPost()) { 65 | ParamsMultiMap params = c->req()->bodyParams(); 66 | 67 | QString id = QUuid::createUuid().toString().remove(QLatin1Char('{')).remove(QLatin1Char('}')); 68 | CMS::Menu *menu = new CMS::Menu(id, c); 69 | if (saveMenu(c, menu, params, false)) { 70 | c->response()->redirect(c->uriFor(CActionFor(u"menus"))); 71 | return; 72 | } 73 | 74 | c->stash({ 75 | {QStringLiteral("error_msg"), tr("Could not save menu")} 76 | }); 77 | 78 | } else { 79 | c->setStash(QStringLiteral("no_wrapper"), true); 80 | } 81 | c->setStash(QStringLiteral("template"), QStringLiteral("appearance/menus_new.html")); 82 | } 83 | 84 | void AdminAppearance::menus_edit(Context *c, const QString &id) 85 | { 86 | c->setStash(QStringLiteral("editing"), true); 87 | 88 | CMS::Menu *menu = engine->menu(id.toHtmlEscaped()); 89 | if (!menu) { 90 | qWarning() << "menu not found" << id; 91 | c->response()->redirect(c->uriFor(CActionFor(u"menus"))); 92 | return; 93 | } 94 | 95 | qWarning() << "params" << c->req()->method(); 96 | if (c->req()->isPost()) { 97 | if (saveMenu(c, menu, c->req()->bodyParams(), true)) { 98 | c->response()->redirect(c->uriFor(CActionFor(u"menus"))); 99 | return; 100 | } else { 101 | c->stash({ 102 | {QStringLiteral("error_msg"), tr("Could not save menu")} 103 | }); 104 | } 105 | } 106 | 107 | c->stash({ 108 | {QStringLiteral("template"), QStringLiteral("appearance/menus_new.html")}, 109 | {QStringLiteral("menu"), QVariant::fromValue(menu)}, 110 | {QStringLiteral("no_wrapper"), true}, 111 | }); 112 | } 113 | 114 | bool AdminAppearance::saveMenu(Context *c, CMS::Menu *menu, const ParamsMultiMap ¶ms, bool replace) 115 | { 116 | qDebug() << "saving menu id" << menu->id(); 117 | menu->setName(params.value(QStringLiteral("name")).toHtmlEscaped()); 118 | qDebug() << "saving menu name" << menu->name(); 119 | 120 | // TODO remove this hack 121 | menu->setLocations({ QStringLiteral("main") }); 122 | 123 | QStringList menuNames = params.values(QStringLiteral("menuName")); 124 | QStringList menuUrl = params.values(QStringLiteral("menuUrl")); 125 | // QStringList menuExternal = params.values(QStringLiteral("menuExternal")); 126 | 127 | qDebug() << "params" << params << menuNames.size() << menuUrl.size(); 128 | if (menuNames.size() == menuUrl.size()) { 129 | menu->setEntries(QList()); 130 | for (int i = 0; i < menuNames.size(); ++i) { 131 | qDebug() << "appending" << menuNames.at(i) << menuUrl.at(i); 132 | QString name = menuNames.at(i).toHtmlEscaped(); 133 | QString url = menuUrl.at(i).toHtmlEscaped(); 134 | if (!name.isEmpty() && !url.isEmpty()) { 135 | menu->appendEntry(name, url); 136 | } 137 | } 138 | } 139 | 140 | return engine->saveMenu(c, menu, replace); 141 | } 142 | -------------------------------------------------------------------------------- /src/libCMS/engine.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #ifndef ENGINE_H 21 | #define ENGINE_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | namespace Cutelyst { 30 | class Context; 31 | } 32 | 33 | namespace CMS { 34 | 35 | class Page; 36 | class Menu; 37 | class EnginePrivate; 38 | class Engine : public QObject 39 | { 40 | Q_OBJECT 41 | Q_DECLARE_PRIVATE(Engine) 42 | Q_PROPERTY(QVariant settings READ settingsProperty) 43 | Q_PROPERTY(QVariant menus READ menusProperty) 44 | public: 45 | enum Filter { 46 | Pages = 0x1, 47 | Posts = 0x2, 48 | OnlyPublished = 0x4, 49 | 50 | NoFilter = -1 51 | }; 52 | Q_DECLARE_FLAGS(Filters, Filter) 53 | 54 | explicit Engine(QObject *parent = 0); 55 | virtual ~Engine(); 56 | 57 | /** 58 | * ALWAYS init the engine on post fork, otherwise 59 | * engines might not perform correctly depending on 60 | * their implementation 61 | */ 62 | virtual bool init(const QHash &settings) = 0; 63 | 64 | virtual Page *getPage(const QString &path, QObject *parent) = 0; 65 | 66 | virtual Page *getPageById(const QString &id, QObject *parent) = 0; 67 | 68 | int savePage(Cutelyst::Context *c, Page *page); 69 | 70 | virtual bool removePage(int id) = 0; 71 | 72 | /** 73 | * Returns the available pages, 74 | * when depth is -1 all pages are listed 75 | */ 76 | virtual QList listPages(QObject *parent, 77 | int offset, 78 | int limit) = 0; 79 | 80 | virtual QList listPagesPublished(QObject *parent, 81 | int offset, 82 | int limit) = 0; 83 | 84 | virtual QList listPosts(QObject *parent, 85 | int offset, 86 | int limit) = 0; 87 | 88 | virtual QList listPostsPublished(QObject *parent, 89 | int offset, 90 | int limit) = 0; 91 | 92 | virtual QList listAuthorPostsPublished(QObject *parent, 93 | int authorId, 94 | int offset, 95 | int limit) = 0; 96 | 97 | virtual QList menus() = 0; 98 | 99 | virtual Menu *menu(const QString &id); 100 | 101 | virtual QHash menuLocations() = 0; 102 | 103 | QVariant menusProperty(); 104 | 105 | virtual bool saveMenu(Cutelyst::Context *c, Menu *menu, bool replace); 106 | virtual bool removeMenu(Cutelyst::Context *c, const QString &name); 107 | bool saveMenus(Cutelyst::Context *c, const QList &menus); 108 | 109 | virtual QDateTime lastModified(); 110 | 111 | virtual bool settingsIsWritable() const = 0; 112 | virtual QHash settings() const = 0; 113 | virtual QVariant settingsProperty(); 114 | virtual QString settingsValue(const QString &key, const QString &defaultValue = QString()) const = 0; 115 | virtual bool setSettingsValue(Cutelyst::Context *c, const QString &key, const QString &value) = 0; 116 | 117 | static QString normalizePath(const QString &path); 118 | static QString normalizeTitle(const QString &path); 119 | 120 | virtual QHash loadSettings(Cutelyst::Context *c) = 0; 121 | 122 | /** 123 | * returns slug 124 | */ 125 | virtual QString addUser(Cutelyst::Context *c, const Cutelyst::ParamsMultiMap &user, bool replace) = 0; 126 | virtual bool removeUser(Cutelyst::Context *c, int id) = 0; 127 | 128 | 129 | virtual QVariantList users() = 0; 130 | virtual QHash user(const QString &slug) = 0; 131 | virtual QHash user(int id) = 0; 132 | 133 | protected: 134 | virtual int savePageBackend(Page *page) = 0; 135 | 136 | EnginePrivate *d_ptr; 137 | }; 138 | 139 | typedef QHash StringHash; 140 | 141 | } 142 | 143 | Q_DECLARE_OPERATORS_FOR_FLAGS(CMS::Engine::Filters) 144 | 145 | #endif // ENGINE_H 146 | -------------------------------------------------------------------------------- /src/cmlyst.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2017 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | #include "cmlyst.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include "root.h" 35 | #include "admin.h" 36 | #include "adminappearance.h" 37 | #include "adminposts.h" 38 | #include "adminpages.h" 39 | #include "adminmedia.h" 40 | #include "adminsettings.h" 41 | #include "adminsetup.h" 42 | 43 | #include "cmdispatcher.h" 44 | #include "sqluserstore.h" 45 | 46 | #include "libCMS/sqlengine.h" 47 | #include "libCMS/page.h" 48 | #include "libCMS/menu.h" 49 | 50 | #include 51 | 52 | CMlyst::CMlyst(QObject *parent) : 53 | Cutelyst::Application(parent) 54 | { 55 | QCoreApplication::setApplicationName(QStringLiteral("cmlyst")); 56 | QCoreApplication::setOrganizationName(QStringLiteral("cutelyst")); 57 | 58 | qRegisterMetaType(); 59 | } 60 | 61 | CMlyst::~CMlyst() 62 | { 63 | } 64 | 65 | bool CMlyst::init() 66 | { 67 | bool production = config(QStringLiteral("production")).toBool(); 68 | qDebug() << "Production" << production; 69 | 70 | auto view = new CuteleeView(this); 71 | view->setTemplateExtension(QStringLiteral(".html")); 72 | view->setWrapper(QStringLiteral("base.html")); 73 | view->setCache(production); 74 | 75 | const QDir dataDir = config(QStringLiteral("DataLocation"), QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).toString(); 76 | if (!dataDir.exists() && !dataDir.mkpath(dataDir.absolutePath())) { 77 | qCritical() << "Could not create DataLocation" << dataDir.absolutePath(); 78 | return false; 79 | } 80 | setConfig(QStringLiteral("DataLocation"), dataDir.absolutePath()); 81 | 82 | view->setIncludePaths({ pathTo(QStringLiteral("root/themes/default")) }); 83 | 84 | auto adminView = new CuteleeView(this, QStringLiteral("admin")); 85 | adminView->setTemplateExtension(QStringLiteral(".html")); 86 | adminView->setWrapper(QStringLiteral("wrapper.html")); 87 | adminView->setIncludePaths({ pathTo(QStringLiteral("root/admin")) }); 88 | adminView->setCache(production); 89 | 90 | if (qEnvironmentVariableIsSet("SETUP")) { 91 | new AdminSetup(this); 92 | } else { 93 | new Root(this); 94 | new Admin(this); 95 | new AdminAppearance(this); 96 | new AdminPosts(this); 97 | new AdminPages(this); 98 | new AdminMedia(this); 99 | new AdminSettings(this); 100 | } 101 | 102 | new CMDispatcher(this); 103 | 104 | auto store = std::make_shared(); 105 | 106 | auto password = std::make_shared(); 107 | password->setPasswordField(QStringLiteral("password")); 108 | password->setPasswordType(CredentialPassword::Hashed); 109 | 110 | new Session(this); 111 | 112 | auto auth = new Authentication(this); 113 | auth->addRealm(store, password); 114 | 115 | new StatusMessage(this); 116 | 117 | qDebug() << "Root location" << pathTo(QStringLiteral("root")); 118 | qDebug() << "Root Admin location" << pathTo(QStringLiteral("root/src/admin")); 119 | qDebug() << "Data location" << dataDir.absolutePath(); 120 | 121 | return true; 122 | } 123 | 124 | bool CMlyst::postFork() 125 | { 126 | QDir dataDir = config(QStringLiteral("DataLocation")).toString(); 127 | 128 | auto engine = new CMS::SqlEngine(this); 129 | engine->init({ 130 | {QStringLiteral("root"), dataDir.absolutePath()} 131 | }); 132 | 133 | Q_FOREACH (Controller *controller, controllers()) { 134 | auto cmengine = dynamic_cast(controller); 135 | if (cmengine) { 136 | cmengine->engine = engine; 137 | } 138 | } 139 | 140 | Q_FOREACH (DispatchType *type, dispatchers()) { 141 | auto cmengine = dynamic_cast(type); 142 | if (cmengine) { 143 | cmengine->engine = engine; 144 | } 145 | } 146 | 147 | return true; 148 | } 149 | -------------------------------------------------------------------------------- /src/adminmedia.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "adminmedia.h" 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | AdminMedia::AdminMedia(QObject *app) : Controller(app) 30 | { 31 | 32 | } 33 | 34 | AdminMedia::~AdminMedia() 35 | { 36 | 37 | } 38 | 39 | void AdminMedia::index(Context *c) 40 | { 41 | const static QDir mediaDir(c->config(QStringLiteral("DataLocation")).toString() + QLatin1String("/media")); 42 | 43 | QStringList files; 44 | QDirIterator it(mediaDir.absolutePath(), 45 | QDir::Files | QDir::NoDotAndDotDot, 46 | QDirIterator::Subdirectories); 47 | while (it.hasNext()) { 48 | files.append(it.next()); 49 | } 50 | files.sort(Qt::CaseInsensitive); 51 | 52 | int removeSize = mediaDir.absolutePath().size(); 53 | QVariantList filesHash; 54 | for (const QString &file : files) { 55 | QFileInfo fileInfo(file); 56 | 57 | QString id = file.mid(removeSize); 58 | 59 | QString urlPath = QLatin1String("/.media") + id; 60 | 61 | QVariantHash hash; 62 | hash.insert(QStringLiteral("id"), id); 63 | hash.insert(QStringLiteral("name"), fileInfo.fileName()); 64 | hash.insert(QStringLiteral("modified"), fileInfo.lastModified()); 65 | hash.insert(QStringLiteral("url"), c->uriFor(urlPath).toString()); 66 | filesHash.push_back(hash); 67 | } 68 | 69 | c->stash({ 70 | {QStringLiteral("template"), QStringLiteral("media/index.html")}, 71 | {QStringLiteral("files"), filesHash} 72 | }); 73 | } 74 | 75 | void AdminMedia::upload(Context *c) 76 | { 77 | const static QDir mediaDir(c->config(QStringLiteral("DataLocation")).toString() + QLatin1String("/media")); 78 | 79 | if (!mediaDir.exists() && !mediaDir.mkpath(mediaDir.absolutePath())) { 80 | qWarning() << "Could not create media directory" << mediaDir.absolutePath(); 81 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("index")), 82 | ParamsMultiMap({ 83 | {QStringLiteral("error_msg"), QStringLiteral("Failed to save file")} 84 | }))); 85 | return; 86 | } 87 | 88 | // TODO this is NOT working... 89 | QFile link(c->config(QStringLiteral("DataLocation")).toString() + QLatin1String("/.media")); 90 | if (!link.exists() && !QFile::link(QStringLiteral("media"), link.fileName())) { 91 | qWarning() << "Could not create link media directory" << mediaDir.absolutePath() << link.fileName(); 92 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("index")), 93 | ParamsMultiMap({ 94 | {QStringLiteral("error_msg"), QStringLiteral("Failed to save file")} 95 | }))); 96 | return; 97 | } 98 | 99 | QDir fileDir(mediaDir.absolutePath() + QDateTime::currentDateTimeUtc().toString(QStringLiteral("/yyyy/MM"))); 100 | if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) { 101 | qWarning() << "Could not create media directory" << fileDir.absolutePath(); 102 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("index")), 103 | ParamsMultiMap({ 104 | {QStringLiteral("error_msg"), QStringLiteral("Failed to save file")} 105 | }))); 106 | return; 107 | } 108 | 109 | Request *request = c->request(); 110 | Upload *upload = request->upload(QStringLiteral("file")); 111 | if (!upload) { 112 | qWarning() << "Could not find upload"; 113 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("index")), 114 | ParamsMultiMap({ 115 | {QStringLiteral("error_msg"), QStringLiteral("Failed to save file")} 116 | }))); 117 | return; 118 | } 119 | 120 | QString filepath = fileDir.absoluteFilePath(upload->filename()); 121 | if (!upload->save(filepath)) { 122 | qWarning() << "Could not save upload" << filepath; 123 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("index")), 124 | ParamsMultiMap({ 125 | {QStringLiteral("error_msg"), QStringLiteral("Failed to save file")} 126 | }))); 127 | return; 128 | } 129 | 130 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("index")))); 131 | } 132 | 133 | void AdminMedia::remove(Context *c, const QStringList &path) 134 | { 135 | const static QDir mediaDir(c->config(QStringLiteral("DataLocation")).toString() + QLatin1String("/media")); 136 | 137 | QString file; 138 | 139 | for (const QString &part : path) { 140 | if (part.isEmpty() || part == QLatin1String("..")) { 141 | continue; 142 | } 143 | 144 | if (!file.isEmpty()) { 145 | file.append(QLatin1Char('/') + part); 146 | } else { 147 | file = part; 148 | } 149 | } 150 | 151 | QFile removeFile(mediaDir.absoluteFilePath(file)); 152 | if (!removeFile.remove()) { 153 | qDebug() << "Failed to remove media file" << removeFile.fileName() << removeFile.errorString(); 154 | } 155 | 156 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("index")))); 157 | } 158 | 159 | -------------------------------------------------------------------------------- /src/adminpages.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2017 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "adminpages.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "libCMS/page.h" 28 | 29 | AdminPages::AdminPages(Application *app) : Controller(app) 30 | { 31 | 32 | } 33 | 34 | AdminPages::~AdminPages() 35 | { 36 | 37 | } 38 | 39 | void AdminPages::index(Context *c) 40 | { 41 | index(c, QStringLiteral("page"), CMS::Engine::Pages); 42 | } 43 | 44 | void AdminPages::create(Context *c) 45 | { 46 | create(c, QStringLiteral("page"), true); 47 | } 48 | 49 | void AdminPages::edit(Context *c, const QString &id) 50 | { 51 | edit(c, id, QStringLiteral("page"), true); 52 | } 53 | 54 | void AdminPages::remove(Context *c, const QString &id) 55 | { 56 | if (!c->request()->isPost()) { 57 | c->response()->setStatus(Response::BadRequest); 58 | return; 59 | } 60 | 61 | engine->removePage(id.toInt()); 62 | c->response()->setBody(QStringLiteral("ok")); 63 | } 64 | 65 | void AdminPages::index(Context *c, const QString &postType, CMS::Engine::Filter filters) 66 | { 67 | c->setStash(QStringLiteral("post_type"), postType); 68 | 69 | QList pages; 70 | if (filters == CMS::Engine::Pages) { 71 | pages = engine->listPages(c, -1, -1); 72 | } else { 73 | pages = engine->listPosts(c, -1, -1); 74 | } 75 | c->setStash(QStringLiteral("posts"), QVariant::fromValue(pages)); 76 | 77 | c->setStash(QStringLiteral("template"), QStringLiteral("posts/index.html")); 78 | } 79 | 80 | void AdminPages::create(Context *c, const QString &postType, bool isPage) 81 | { 82 | const ParamsMultiMap params = c->request()->bodyParams(); 83 | QString title = params.value(QStringLiteral("title")); 84 | QString path = params.value(QStringLiteral("path")); 85 | QString content = params.value(QStringLiteral("edit-content")); 86 | if (c->req()->isPost()) { 87 | QString savePath; 88 | if (isPage) { 89 | savePath = CMS::Engine::normalizePath(path); 90 | } else { 91 | const QString date = QDate::currentDate().toString(QStringLiteral("yyyy/MM/dd/")); 92 | if (path.isEmpty()) { 93 | savePath = date + CMS::Engine::normalizeTitle(title); 94 | } else { 95 | savePath = date + CMS::Engine::normalizeTitle(path); 96 | } 97 | } 98 | 99 | auto page = new CMS::Page(c); 100 | page->setPath(savePath); 101 | page->setUuid(QString()); 102 | page->setContent(content, true); 103 | page->setTitle(title); 104 | page->setPage(isPage); 105 | 106 | QDateTime dt = QDateTime::currentDateTimeUtc(); 107 | page->setCreated(dt); 108 | page->setUpdated(dt); 109 | 110 | Author author = engine->user(Authentication::user(c).id().toInt()); 111 | page->setAuthor(author); 112 | 113 | int id = engine->savePage(c, page); 114 | if (id) { 115 | c->res()->redirect(c->uriFor(actionFor(QStringLiteral("edit")), QStringList{ QString::number(id)})); 116 | return; 117 | } else { 118 | if (isPage && savePath.isEmpty()) { 119 | c->setStash(QStringLiteral("error_msg"), QStringLiteral("Path can not be empty")); 120 | } else { 121 | c->setStash(QStringLiteral("error_msg"), QStringLiteral("Failed to save page")); 122 | } 123 | } 124 | } 125 | 126 | c->setStash(QStringLiteral("post_type"), postType); 127 | c->setStash(QStringLiteral("title"), title); 128 | c->setStash(QStringLiteral("path"), path); 129 | c->setStash(QStringLiteral("edit_content"), content); 130 | c->setStash(QStringLiteral("template"), QStringLiteral("posts/create.html")); 131 | } 132 | 133 | void AdminPages::edit(Context *c, const QString &id, const QString &postType, bool isPage) 134 | { 135 | CMS::Page *page = engine->getPageById(id, c); 136 | if (!page || page->page() != isPage) { 137 | c->res()->redirect(c->uriFor(actionFor(QStringLiteral("index")))); 138 | return; 139 | } 140 | 141 | QString path = page->path(); 142 | QString title = page->title(); 143 | QString content = page->content(); 144 | 145 | if (c->req()->isPost()) { 146 | const ParamsMultiMap params = c->request()->bodyParams(); 147 | title = params.value(QStringLiteral("title")); 148 | content = params.value(QStringLiteral("edit-content")); 149 | path = CMS::Engine::normalizePath(params.value(QStringLiteral("path"))); 150 | const QString action = params.value(QStringLiteral("submit")); 151 | 152 | page->updateContent(content); 153 | page->setTitle(title); 154 | page->setPage(isPage); 155 | page->setPath(path); 156 | if (action == QLatin1String("unpublish")) { 157 | page->setPublished(false); 158 | } else if (action == QLatin1String("publish")) { 159 | page->setPublished(true); 160 | if (!page->publishedAt().isValid()) { 161 | page->setPublishedAt(QDateTime::currentDateTimeUtc()); 162 | } 163 | } 164 | page->setUpdated(QDateTime::currentDateTimeUtc()); 165 | 166 | Author author = engine->user(Authentication::user(c).id().toInt()); 167 | page->setAuthor(author); 168 | 169 | bool ret = engine->savePage(c, page); 170 | if (!ret) { 171 | c->setStash(QStringLiteral("error_msg"), QStringLiteral("Failed to save page")); 172 | } 173 | } 174 | 175 | c->setStash(QStringLiteral("title"), title); 176 | c->setStash(QStringLiteral("path"), path); 177 | c->setStash(QStringLiteral("edit_content"), content); 178 | c->setStash(QStringLiteral("editting"), true); 179 | c->setStash(QStringLiteral("published"), page->published()); 180 | c->setStash(QStringLiteral("post_type"), postType); 181 | c->setStash(QStringLiteral("template"), QStringLiteral("posts/create.html")); 182 | } 183 | -------------------------------------------------------------------------------- /src/rsswriter.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "rsswriter.h" 21 | 22 | #include 23 | #include 24 | 25 | static QString NAMESPACE_CONTENT QStringLiteral("http://purl.org/rss/1.0/modules/content/"); 26 | static QString NAMESPACE_WFW QStringLiteral("http://wellformedweb.org/CommentAPI/"); 27 | static QString NAMESPACE_DC QStringLiteral("http://purl.org/dc/elements/1.1/"); 28 | static QString NAMESPACE_ATOM QStringLiteral("http://www.w3.org/2005/Atom"); 29 | static QString NAMESPACE_SY QStringLiteral("http://purl.org/rss/1.0/modules/syndication/"); 30 | static QString NAMESPACE_SLASH QStringLiteral("http://purl.org/rss/1.0/modules/slash/"); 31 | static QString NAMESPACE_GEORSS QStringLiteral("http://www.georss.org/georss"); 32 | static QString NAMESPACE_GEO QStringLiteral("http://www.w3.org/2003/01/geo/wgs84_pos#"); 33 | static QString NAMESPACE_MEDIA QStringLiteral("http://search.yahoo.com/mrss/"); 34 | 35 | RSSWriter::RSSWriter(QIODevice *device, QObject *parent) : QObject(parent) 36 | { 37 | m_stream.setDevice(device); 38 | } 39 | 40 | RSSWriter::~RSSWriter() 41 | { 42 | 43 | } 44 | 45 | void RSSWriter::startRSS() 46 | { 47 | // m_stream.setAutoFormatting(true); 48 | m_stream.writeStartDocument(); 49 | m_stream.writeStartElement(QStringLiteral("rss")); 50 | m_stream.writeNamespace(NAMESPACE_CONTENT, 51 | QStringLiteral("content")); 52 | m_stream.writeNamespace(NAMESPACE_WFW, 53 | QStringLiteral("wfw")); 54 | m_stream.writeNamespace(NAMESPACE_DC, 55 | QStringLiteral("dc")); 56 | m_stream.writeNamespace(NAMESPACE_ATOM, 57 | QStringLiteral("atom")); 58 | m_stream.writeNamespace(NAMESPACE_SY, 59 | QStringLiteral("sy")); 60 | m_stream.writeNamespace(NAMESPACE_SLASH, 61 | QStringLiteral("slash")); 62 | m_stream.writeNamespace(NAMESPACE_GEORSS, 63 | QStringLiteral("georss")); 64 | m_stream.writeNamespace(NAMESPACE_GEO, 65 | QStringLiteral("geo")); 66 | m_stream.writeNamespace(NAMESPACE_MEDIA, 67 | QStringLiteral("media")); 68 | } 69 | 70 | void RSSWriter::writeStartChannel() 71 | { 72 | m_stream.writeStartElement(QStringLiteral("channel")); 73 | } 74 | 75 | void RSSWriter::writeChannelTitle(const QString &title) 76 | { 77 | m_stream.writeTextElement(QStringLiteral("title"), title); 78 | } 79 | 80 | void RSSWriter::writeChannelLink(const QString &link) 81 | { 82 | m_stream.writeTextElement(QStringLiteral("link"), link); 83 | } 84 | 85 | void RSSWriter::writeChannelFeedLink(const QString &link, const QString &mimeType, const QString &rel) 86 | { 87 | m_stream.writeStartElement(NAMESPACE_ATOM, QStringLiteral("link")); 88 | 89 | m_stream.writeAttribute(QStringLiteral("href"), link); 90 | 91 | if (rel.isNull()) { 92 | m_stream.writeAttribute(QStringLiteral("rel"), QStringLiteral("self")); 93 | } else { 94 | m_stream.writeAttribute(QStringLiteral("rel"), rel); 95 | } 96 | 97 | if (mimeType.isNull()) { 98 | m_stream.writeAttribute(QStringLiteral("type"), QStringLiteral("application/rss+xml")); 99 | } else { 100 | m_stream.writeAttribute(QStringLiteral("type"), mimeType); 101 | } 102 | 103 | m_stream.writeEndElement(); 104 | } 105 | 106 | void RSSWriter::writeChannelDescription(const QString &description) 107 | { 108 | m_stream.writeTextElement(QStringLiteral("description"), description); 109 | } 110 | 111 | void RSSWriter::writeChannelLastBuildDate(const QDateTime &lastBuildDate) 112 | { 113 | const QString dt = QLocale::c().toString(lastBuildDate.toTimeSpec(Qt::UTC), 114 | QStringLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT")); 115 | m_stream.writeTextElement(QStringLiteral("lastBuildDate"), dt); 116 | } 117 | 118 | void RSSWriter::writeChannelLanguage(const QString &language) 119 | { 120 | m_stream.writeTextElement(QStringLiteral("language"), language); 121 | } 122 | 123 | void RSSWriter::writeStartImage() 124 | { 125 | m_stream.writeStartElement(QStringLiteral("image")); 126 | } 127 | 128 | void RSSWriter::writeImageUrl(const QString &url) 129 | { 130 | m_stream.writeTextElement(QStringLiteral("url"), url); 131 | } 132 | 133 | void RSSWriter::writeImageTitle(const QString &title) 134 | { 135 | m_stream.writeTextElement(QStringLiteral("title"), title); 136 | } 137 | 138 | void RSSWriter::writeImageLink(const QString &link) 139 | { 140 | m_stream.writeTextElement(QStringLiteral("link"), link); 141 | } 142 | 143 | void RSSWriter::writeEndImage() 144 | { 145 | m_stream.writeEndElement(); 146 | } 147 | 148 | void RSSWriter::writeStartItem() 149 | { 150 | m_stream.writeStartElement(QStringLiteral("item")); 151 | } 152 | 153 | void RSSWriter::writeItemTitle(const QString &title) 154 | { 155 | m_stream.writeTextElement(QStringLiteral("title"), title); 156 | } 157 | 158 | void RSSWriter::writeItemLink(const QString &link) 159 | { 160 | m_stream.writeTextElement(QStringLiteral("link"), link); 161 | } 162 | 163 | void RSSWriter::writeItemCommentsLink(const QString &link) 164 | { 165 | m_stream.writeTextElement(QStringLiteral("comments"), link); 166 | } 167 | 168 | void RSSWriter::writeItemNumberOfComments(int number) 169 | { 170 | m_stream.writeTextElement(NAMESPACE_SLASH, QStringLiteral("comments"), QString::number(number)); 171 | } 172 | 173 | void RSSWriter::writeItemCreator(const QString &creator) 174 | { 175 | m_stream.writeTextElement(NAMESPACE_DC, QStringLiteral("creator"), creator); 176 | } 177 | 178 | void RSSWriter::writeItemCategory(const QString &category) 179 | { 180 | m_stream.writeTextElement(QStringLiteral("category"), category); 181 | } 182 | 183 | void RSSWriter::writeItemPubDate(const QDateTime &pubDate) 184 | { 185 | const QString dt = QLocale::c().toString(pubDate.toTimeSpec(Qt::UTC), 186 | QStringLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT")); 187 | m_stream.writeTextElement(QStringLiteral("pubDate"), dt); 188 | } 189 | 190 | void RSSWriter::writeItemDescription(const QString &description) 191 | { 192 | m_stream.writeTextElement(QStringLiteral("description"), description); 193 | } 194 | 195 | void RSSWriter::writeItemContent(const QString &content) 196 | { 197 | m_stream.writeTextElement(NAMESPACE_CONTENT, QStringLiteral("encoded"), content); 198 | } 199 | 200 | void RSSWriter::writeEndItem() 201 | { 202 | m_stream.writeEndElement(); 203 | } 204 | 205 | void RSSWriter::writeEndChannel() 206 | { 207 | m_stream.writeEndElement(); 208 | } 209 | 210 | void RSSWriter::endRSS() 211 | { 212 | m_stream.writeEndElement(); 213 | m_stream.writeEndDocument(); 214 | } 215 | -------------------------------------------------------------------------------- /src/root.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "root.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #include "libCMS/page.h" 37 | #include "libCMS/menu.h" 38 | 39 | #include "rsswriter.h" 40 | 41 | Root::Root(QObject *app) : Controller(app) 42 | { 43 | } 44 | 45 | Root::~Root() 46 | { 47 | } 48 | 49 | void Root::notFound(Context *c) 50 | { 51 | c->stash({ 52 | {QStringLiteral("template"), QStringLiteral("404.html")}, 53 | {QStringLiteral("cms"), QVariant::fromValue(engine)}, 54 | }); 55 | c->res()->setStatus(404); 56 | } 57 | 58 | bool Root::End(Context *c) 59 | { 60 | const QString theme = engine->settingsValue(QStringLiteral("theme"), QStringLiteral("default")); 61 | 62 | const QString staticTheme = QLatin1String("/static/themes/") + theme; 63 | c->setStash(QStringLiteral("basetheme"), c->uriFor(staticTheme).toString()); 64 | 65 | return true; 66 | } 67 | 68 | void Root::page(Cutelyst::Context *c) 69 | { 70 | Response *res = c->res(); 71 | Request *req = c->req(); 72 | 73 | // Get the desired page (dispatcher already found it) 74 | auto page = c->stash(QStringLiteral("page")).value(); 75 | // QVariantHash page = c->stash(QStringLiteral("page")).toHash(); 76 | 77 | // See if the page has changed, if the settings have changed 78 | // and have a newer date use that instead 79 | QDateTime currentDateTime = qMax(page->updated(), engine->lastModified()); 80 | // QDateTime currentDateTime = qMax(page.value(QStringLiteral("modified")).toDateTime(), engine->lastModified()); 81 | const QDateTime clientDate = req->headers().ifModifiedSinceDateTime(); 82 | if (clientDate.isValid() && currentDateTime == clientDate) { 83 | res->setStatus(Response::NotModified); 84 | return; 85 | } 86 | res->headers().setLastModified(currentDateTime); 87 | 88 | QString cmsPagePath = QLatin1Char('/') + c->req()->path(); 89 | engine->setProperty("pagePath", cmsPagePath); 90 | 91 | auto settings = engine->settings(); 92 | const QString cms_head = settings.value(QStringLiteral("cms_head")); 93 | if (!cms_head.isEmpty()) { 94 | const Cutelee::SafeString safe(cms_head, true); 95 | c->setStash(QStringLiteral("cms_head"), QVariant::fromValue(safe)); 96 | } 97 | 98 | const QString cms_foot = settings.value(QStringLiteral("cms_foot")); 99 | if (!cms_foot.isEmpty()) { 100 | const Cutelee::SafeString safe(cms_foot, true); 101 | c->setStash(QStringLiteral("cms_foot"), QVariant::fromValue(safe)); 102 | } 103 | 104 | if (page->page()) { 105 | c->setStash(QStringLiteral("template"), QStringLiteral("page.html")); 106 | } else { 107 | c->setStash(QStringLiteral("template"), QStringLiteral("blog.html")); 108 | } 109 | c->setStash(QStringLiteral("meta_title"), page->title()); 110 | c->setStash(QStringLiteral("cms"), QVariant::fromValue(engine)); 111 | } 112 | 113 | void Root::lastPosts(Context *c) 114 | { 115 | Response *res = c->res(); 116 | Request *req = c->req(); 117 | 118 | // See if the page has changed, if the settings have changed 119 | // and have a newer date use that instead 120 | const QDateTime currentDateTime = engine->lastModified(); 121 | const QDateTime clientDate = req->headers().ifModifiedSinceDateTime(); 122 | if (clientDate.isValid()) { 123 | if (currentDateTime == clientDate && currentDateTime.isValid()) { 124 | res->setStatus(Response::NotModified); 125 | return; 126 | } 127 | } 128 | res->headers().setLastModified(currentDateTime); 129 | 130 | const auto settings = engine->settings(); 131 | const int postsPerPage = settings.value(QStringLiteral("posts_per_page"), QStringLiteral("10")).toInt(); 132 | int offset; 133 | 134 | QSqlQuery query = CPreparedSqlQueryThreadForDB( 135 | QStringLiteral("SELECT count(*) FROM posts WHERE page = 0 AND published = 1"), 136 | QStringLiteral("cmlyst")); 137 | if (Q_LIKELY(query.exec() && query.next())) { 138 | int rows = query.value(0).toInt(); 139 | Pagination pagination(rows, 140 | postsPerPage, 141 | c->req()->queryParam(QStringLiteral("page"), QStringLiteral("1")).toInt()); 142 | offset = pagination.offset(); 143 | c->setStash(QStringLiteral("pagination"), pagination); 144 | } else { 145 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("notFound")))); 146 | return; 147 | } 148 | 149 | const QList posts = engine->listPostsPublished(c, offset, postsPerPage); 150 | 151 | QString cmsPagePath = QLatin1Char('/') + c->req()->path(); 152 | engine->setProperty("pagePath", cmsPagePath); 153 | c->stash({ 154 | {QStringLiteral("template"), QStringLiteral("posts.html")}, 155 | {QStringLiteral("meta_title"), settings.value(QStringLiteral("title"))}, 156 | {QStringLiteral("meta_description"), settings.value(QStringLiteral("tagline"))}, 157 | {QStringLiteral("cms"), QVariant::fromValue(engine)}, 158 | {QStringLiteral("posts"), QVariant::fromValue(posts)} 159 | }); 160 | } 161 | 162 | void Root::feed(Context *c) 163 | { 164 | Request *req = c->req(); 165 | Response *res = c->res(); 166 | 167 | // See if the page has changed, if the settings have changed 168 | // and have a newer date use that instead 169 | const QDateTime currentDateTime = engine->lastModified(); 170 | const QDateTime clientDate = req->headers().ifModifiedSinceDateTime(); 171 | if (clientDate.isValid() && currentDateTime.isValid() && currentDateTime == clientDate) { 172 | res->setStatus(Response::NotModified); 173 | return; 174 | } 175 | 176 | Headers &headers = res->headers(); 177 | headers.setLastModified(currentDateTime); 178 | headers.setContentType("text/xml; charset=UTF-8"); 179 | 180 | QSqlQuery query = CPreparedSqlQueryThreadForDB( 181 | QStringLiteral("SELECT p.title, p.path, u.slug, p.published_at, p.content " 182 | "FROM posts p " 183 | "LEFT JOIN users u ON u.id = p.author_id " 184 | "WHERE page = 0 AND published = 1 " 185 | "ORDER BY published_at DESC " 186 | "LIMIT :limit " 187 | ), 188 | QStringLiteral("cmlyst")); 189 | query.bindValue(QStringLiteral(":limit"), 10); 190 | 191 | auto settings = engine->settings(); 192 | 193 | auto buffer = new QBuffer(c); 194 | buffer->open(QIODevice::ReadWrite); 195 | res->setBody(buffer); 196 | 197 | RSSWriter writer(buffer); 198 | 199 | writer.startRSS(); 200 | writer.writeStartChannel(); 201 | writer.writeChannelTitle(settings.value(QStringLiteral("title"))); 202 | writer.writeChannelFeedLink(c->uriFor(c->action()).toString()); 203 | writer.writeChannelLink(req->base()); 204 | writer.writeChannelDescription(settings.value(QStringLiteral("tagline"))); 205 | 206 | if (Q_LIKELY(query.exec())) { 207 | writer.writeChannelLastBuildDate(currentDateTime); 208 | 209 | while (query.next()) { 210 | writer.writeStartItem(); 211 | 212 | writer.writeItemTitle(query.value(0).toString()); 213 | 214 | const QString link = c->uriFor(query.value(1).toString()).toString(); 215 | writer.writeItemLink(link); 216 | writer.writeItemCommentsLink(link + QLatin1String("#comments")); 217 | writer.writeItemCreator(query.value(2).toString()); 218 | 219 | QDateTime published = QDateTime::fromString(query.value(3).toString(), QStringLiteral("yyyy-MM-dd HH:mm:ss")); 220 | published.setTimeSpec(Qt::UTC); 221 | writer.writeItemPubDate(published); 222 | 223 | const QString content = query.value(4).toString(); 224 | writer.writeItemDescription(content.left(300)); 225 | writer.writeItemContent(content); 226 | 227 | writer.writeEndItem(); 228 | } 229 | 230 | writer.writeEndChannel(); 231 | } 232 | writer.endRSS(); 233 | } 234 | 235 | void Root::author(Context *c, const QString &slug) 236 | { 237 | Response *res = c->res(); 238 | Request *req = c->req(); 239 | 240 | QDateTime currentDateTime = engine->lastModified(); 241 | const QDateTime &clientDate = req->headers().ifModifiedSinceDateTime(); 242 | if (clientDate.isValid() && currentDateTime == clientDate) { 243 | res->setStatus(Response::NotModified); 244 | return; 245 | } 246 | res->headers().setLastModified(currentDateTime); 247 | 248 | auto authorData = engine->user(slug); 249 | if (authorData.isEmpty()) { 250 | notFound(c); 251 | return; 252 | } 253 | int authorId = authorData.value(QStringLiteral("id")).toInt(); 254 | 255 | const auto settings = engine->settings(); 256 | const int postsPerPage = settings.value(QStringLiteral("posts_per_page"), QStringLiteral("10")).toInt(); 257 | int offset; 258 | 259 | QSqlQuery query = CPreparedSqlQueryThreadForDB( 260 | QStringLiteral("SELECT count(*) FROM posts WHERE page = 0 AND published = 1 AND author_id = :author_id"), 261 | QStringLiteral("cmlyst")); 262 | query.bindValue(QStringLiteral(":author_id"), authorId); 263 | if (Q_LIKELY(query.exec() && query.next())) { 264 | int rows = query.value(0).toInt(); 265 | Pagination pagination(rows, 266 | postsPerPage, 267 | c->req()->queryParam(QStringLiteral("page"), QStringLiteral("1")).toInt()); 268 | offset = pagination.offset(); 269 | c->setStash(QStringLiteral("pagination"), pagination); 270 | c->setStash(QStringLiteral("posts_count"), rows); 271 | } else { 272 | c->response()->redirect(c->uriFor(CActionFor(QStringLiteral("notFound")))); 273 | return; 274 | } 275 | 276 | QList posts; 277 | posts = engine->listAuthorPostsPublished(c, 278 | authorId, 279 | offset, 280 | postsPerPage); 281 | 282 | const QString cms_head = settings.value(QStringLiteral("cms_head")); 283 | if (!cms_head.isEmpty()) { 284 | const Cutelee::SafeString safe(cms_head, true); 285 | c->setStash(QStringLiteral("cms_head"), QVariant::fromValue(safe)); 286 | } 287 | 288 | const QString cms_foot = settings.value(QStringLiteral("cms_foot")); 289 | if (!cms_foot.isEmpty()) { 290 | const Cutelee::SafeString safe(cms_foot, true); 291 | c->setStash(QStringLiteral("cms_foot"), QVariant::fromValue(safe)); 292 | } 293 | 294 | c->stash({ 295 | {QStringLiteral("template"), QStringLiteral("author.html")}, 296 | {QStringLiteral("meta_title"), settings.value(QStringLiteral("title"))}, 297 | {QStringLiteral("meta_description"), settings.value(QStringLiteral("tagline"))}, 298 | {QStringLiteral("cms"), QVariant::fromValue(engine)}, 299 | {QStringLiteral("author"), QVariant::fromValue(authorData)}, 300 | {QStringLiteral("posts"), QVariant::fromValue(posts)} 301 | }); 302 | } 303 | -------------------------------------------------------------------------------- /src/libCMS/fileengine.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2014-2015 Daniel Nicoletti * 3 | * * 4 | * This program is free software; you can redistribute it and/or modify * 5 | * it under the terms of the GNU General Public License as published by * 6 | * the Free Software Foundation; either version 2 of the License, or * 7 | * (at your option) any later version. * 8 | * * 9 | * This program is distributed in the hope that it will be useful, * 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 12 | * GNU General Public License for more details. * 13 | * * 14 | * You should have received a copy of the GNU General Public License * 15 | * along with this program; see the file COPYING. If not, write to * 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 17 | * Boston, MA 02110-1301, USA. * 18 | ***************************************************************************/ 19 | 20 | #include "fileengine_p.h" 21 | 22 | #include "page.h" 23 | #include "menu.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | #include 38 | #include 39 | 40 | Q_LOGGING_CATEGORY(CMS_FILEENGINE, "cms.fileengine") 41 | 42 | using namespace CMS; 43 | 44 | FileEngine::FileEngine(QObject *parent) : 45 | Engine(parent), 46 | d_ptr(new FileEnginePrivate) 47 | { 48 | } 49 | 50 | FileEngine::~FileEngine() 51 | { 52 | delete d_ptr; 53 | } 54 | 55 | bool FileEngine::init(const QHash &settings) 56 | { 57 | Q_D(FileEngine); 58 | 59 | QString root = settings.value(QStringLiteral("root")); 60 | if (root.isEmpty()) { 61 | root = QDir::currentPath(); 62 | } 63 | d->rootPath = root; 64 | d->pagesPath = root + QLatin1String("/pages"); 65 | d->settingsInfo = d->rootPath.absoluteFilePath(QStringLiteral("site.conf")); 66 | d->settings = new QSettings(d->settingsInfo.absoluteFilePath(), QSettings::IniFormat); 67 | 68 | if (!d->pagesPath.exists() && 69 | !d->pagesPath.mkpath(d->pagesPath.absolutePath())) { 70 | qCWarning(CMS_FILEENGINE) << "Failed to create pages path" << d->pagesPath.absolutePath(); 71 | return false; 72 | } 73 | 74 | QFileSystemWatcher *watch = new QFileSystemWatcher(this); 75 | watch->addPath(d->pagesPath.absolutePath()); 76 | connect(watch, &QFileSystemWatcher::directoryChanged, 77 | this, &FileEngine::loadPages); 78 | loadPages(); 79 | 80 | // When the settings file changes the watcher stops 81 | // working, so it's better to watch for the settings directory 82 | QFileSystemWatcher *settingsWatch = new QFileSystemWatcher(this); 83 | settingsWatch->addPath(d->settingsInfo.absoluteFilePath()); 84 | connect(settingsWatch, &QFileSystemWatcher::fileChanged, 85 | this, &FileEngine::loadSettings); 86 | loadSettings(); 87 | 88 | if (!d->settings->isWritable()) { 89 | qCWarning(CMS_FILEENGINE) << "Settings file is not writable!" << d->settingsInfo.absoluteFilePath(); 90 | return false; 91 | } 92 | 93 | return true; 94 | } 95 | 96 | Page *FileEngine::getPage(const QString &path, QObject *parent) 97 | { 98 | Q_D(FileEngine); 99 | Q_UNUSED(parent) 100 | 101 | QHash::ConstIterator it = d->pathPages.constFind(path); 102 | if (it != d->pathPages.constEnd()) { 103 | return it.value(); 104 | } 105 | return 0; 106 | } 107 | 108 | Page *FileEngine::loadPage(const QString &filename) 109 | { 110 | Q_D(FileEngine); 111 | 112 | // skip temporary files 113 | if (filename.endsWith(QLatin1Char('~'))) { 114 | return 0; 115 | } 116 | 117 | const QString &relPathPercent = d->pagesPath.relativeFilePath(filename); 118 | QString relPath = QUrl::fromPercentEncoding(relPathPercent.toLatin1()); 119 | 120 | // Paths like http://foo.com or http://foo.com/bar/ 121 | // have files that ends with index 122 | if (relPath.endsWith(QLatin1String("index"))) { 123 | relPath.remove(relPath.size() - 5, 5); 124 | } 125 | 126 | Page *page = 0; 127 | QFileInfo fileInfo(filename); 128 | QHash::ConstIterator it = d->pathPages.constFind(relPath); 129 | if (it != d->pathPages.constEnd()) { 130 | page = it.value(); 131 | 132 | if (page->modified() != fileInfo.lastModified()) { 133 | d->pathPages.remove(relPath); 134 | d->posts.removeOne(page); 135 | d->pages.removeOne(page); 136 | delete page; 137 | page = 0; 138 | } 139 | } 140 | 141 | if (!page) { 142 | QSettings data(filename, QSettings::IniFormat); 143 | page = new Page(this); 144 | 145 | page->setPath(relPath); 146 | page->setName(data.value(QStringLiteral("Name")).toString()); 147 | 148 | QString author = data.value(QStringLiteral("Author")).toString(); 149 | if (author.isEmpty()) { 150 | author = fileInfo.owner(); 151 | } 152 | page->setAuthor(author); 153 | 154 | QDateTime modified = QDateTime::fromString(data.value(QStringLiteral("Modified")).toString(), Qt::ISODate); 155 | if (modified.isValid()) { 156 | } else { 157 | modified = fileInfo.lastModified().toUTC(); 158 | } 159 | page->setModified(modified); 160 | 161 | QDateTime created = QDateTime::fromString(data.value(QStringLiteral("Created")).toString(), Qt::ISODate); 162 | if (created.isValid()) { 163 | } else { 164 | created = fileInfo.created().toUTC(); 165 | } 166 | page->setCreated(created); 167 | 168 | page->setNavigationLabel(data.value(QStringLiteral("NavigationLabel")).toString()); 169 | page->setTags(data.value(QStringLiteral("Tags")).toStringList()); 170 | page->setBlog(data.value(QStringLiteral("Blog")).toBool()); 171 | page->setAllowComments(data.value(QStringLiteral("AllowComments")).toBool()); 172 | 173 | data.beginGroup(QStringLiteral("Body")); 174 | page->setContent(data.value(QStringLiteral("Content")).toString()); 175 | data.endGroup(); 176 | 177 | d->pathPages.insert(relPath, page); 178 | if (page->blog()) { 179 | d->posts.append(page); 180 | } else { 181 | d->pages.append(page); 182 | } 183 | } 184 | 185 | return page; 186 | } 187 | 188 | bool FileEngine::savePageBackend(Page *page) 189 | { 190 | Q_D(FileEngine); 191 | 192 | QString path = page->path(); 193 | path.remove(QRegularExpression(QStringLiteral("^/"))); 194 | if (path.isEmpty()) { 195 | path = QStringLiteral("index"); 196 | } 197 | 198 | const QString file = d->pagesPath.absoluteFilePath(QString::fromLatin1(path.toLatin1().toPercentEncoding())); 199 | // qDebug() << "save Page" << page->path() << path; 200 | QSettings data(file, QSettings::IniFormat); 201 | data.setValue(QStringLiteral("Name"), page->name()); 202 | data.setValue(QStringLiteral("Modified"), QDateTime::currentDateTimeUtc().toString(Qt::ISODate)); 203 | data.setValue(QStringLiteral("Created"), page->created().toUTC().toString(Qt::ISODate)); 204 | data.setValue(QStringLiteral("Author"), page->author()); 205 | data.setValue(QStringLiteral("NavigationLabel"), page->navigationLabel()); 206 | data.setValue(QStringLiteral("Tags"), page->tags()); 207 | data.setValue(QStringLiteral("Blog"), page->blog()); 208 | data.setValue(QStringLiteral("AllowComments"), page->allowComments()); 209 | 210 | data.beginGroup(QStringLiteral("Body")); 211 | data.setValue(QStringLiteral("Content"), page->content()); 212 | data.endGroup(); 213 | data.sync(); 214 | 215 | #if (QT_VERSION <= QT_VERSION_CHECK(5, 4, 0)) 216 | // Force a change to notify cache that something changed 217 | utime(file.toLatin1().data(), NULL); 218 | #endif 219 | 220 | // qDebug() << "save page" << file; 221 | // if it's not writable we can't save the page 222 | return data.isWritable(); 223 | } 224 | 225 | bool dateLessThan(Page *page1, Page *page2) 226 | { 227 | return page1->created() < page2->created(); 228 | } 229 | 230 | bool dateGreaterThan(Page *page1, Page *page2) 231 | { 232 | return page1->created() > page2->created(); 233 | } 234 | 235 | bool nameLessThan(Page *page1, Page *page2) 236 | { 237 | return page1->name() < page2->name(); 238 | } 239 | 240 | bool nameGreaterThan(Page *page1, Page *page2) 241 | { 242 | return page1->name() > page2->name(); 243 | } 244 | 245 | bool dateNameLessThan(Page *page1, Page *page2) 246 | { 247 | const QDateTime &dt1 = page1->created(); 248 | const QDateTime &dt2 = page2->created(); 249 | if (dt1 == dt2) { 250 | return page1->name() < page2->name(); 251 | } else { 252 | return dt1 < dt2; 253 | } 254 | } 255 | 256 | bool dateNameGreaterThan(Page *page1, Page *page2) 257 | { 258 | const QDateTime &dt1 = page1->created(); 259 | const QDateTime &dt2 = page2->created(); 260 | if (dt1 == dt2) { 261 | return page1->name() > page2->name(); 262 | } else { 263 | return dt1 > dt2; 264 | } 265 | } 266 | 267 | QList FileEngine::listPages(QObject *parent, Engine::Filters filters, Engine::SortFlags sort, int depth, int limit) 268 | { 269 | Q_D(const FileEngine); 270 | Q_UNUSED(parent) 271 | 272 | QList ret; 273 | QList pages; 274 | 275 | if (filters != NoFilter) { 276 | if (filters & Posts) { 277 | pages.append(d->posts); 278 | } 279 | 280 | if (filters & Pages) { 281 | pages.append(d->pages); 282 | } 283 | } else { 284 | pages = d->pages + d->posts; 285 | } 286 | 287 | 288 | if (depth == -1) { 289 | ret = pages; 290 | } else { 291 | Q_FOREACH (Page *page, pages) { 292 | if (depth != -1 && page->path().count(QLatin1Char('/')) > depth) { 293 | continue; 294 | } 295 | 296 | ret.append(page); 297 | } 298 | } 299 | 300 | // apply the sorting 301 | if (sort & Date && sort & Name) { 302 | qSort(ret.begin(), ret.end(), sort & Reversed ? dateNameGreaterThan : dateNameLessThan); 303 | } else if (sort & Date) { 304 | qSort(ret.begin(), ret.end(), sort & Reversed ? dateGreaterThan : dateLessThan); 305 | } else if (sort & Name) { 306 | qSort(ret.begin(), ret.end(), sort & Reversed ? nameGreaterThan : nameLessThan); 307 | } 308 | 309 | // Limit the list 310 | if (limit == -1) { 311 | return ret; 312 | } else { 313 | return ret.mid(0, limit); 314 | } 315 | } 316 | 317 | QList FileEngine::menus() 318 | { 319 | Q_D(FileEngine); 320 | return d->menus; 321 | } 322 | 323 | QHash FileEngine::menuLocations() 324 | { 325 | Q_D(FileEngine); 326 | return d->menuLocations; 327 | } 328 | 329 | bool FileEngine::saveMenu(Menu *menu, bool replace) 330 | { 331 | Q_D(const FileEngine); 332 | 333 | bool ret = false; 334 | d->settings->beginGroup(QStringLiteral("Menus")); 335 | 336 | if (replace || !d->settings->childGroups().contains(menu->id())) { 337 | d->settings->beginGroup(menu->id()); 338 | 339 | d->settings->setValue(QStringLiteral("Name"), menu->name()); 340 | d->settings->setValue(QStringLiteral("AutoAddPages"), menu->autoAddPages()); 341 | d->settings->setValue(QStringLiteral("Locations"), menu->locations()); 342 | 343 | QList urls = menu->entries(); 344 | d->settings->beginWriteArray(QStringLiteral("urls"), urls.size()); 345 | for (int i = 0; i < urls.size(); ++i) { 346 | d->settings->setArrayIndex(i); 347 | // TODO save all values 348 | d->settings->setValue(QStringLiteral("text"), urls.at(i).value(QStringLiteral("text"))); 349 | d->settings->setValue(QStringLiteral("url"), urls.at(i).value(QStringLiteral("url"))); 350 | } 351 | d->settings->endArray(); 352 | 353 | d->settings->endGroup(); 354 | ret = true; 355 | } 356 | 357 | d->settings->endGroup(); // Menus 358 | 359 | // Force reloading of settings 360 | loadSettings(); 361 | 362 | return ret && d->settings->isWritable(); 363 | } 364 | 365 | bool FileEngine::removeMenu(const QString &name) 366 | { 367 | Q_D(FileEngine); 368 | 369 | qCDebug(CMS_FILEENGINE) << "Removing menu" << name; 370 | 371 | d->settings->beginGroup(QStringLiteral("Menus")); 372 | if (d->settings->childGroups().contains(name)) { 373 | d->settings->remove(name); 374 | } 375 | d->settings->endGroup(); 376 | 377 | // Force reloading of settings 378 | loadSettings(); 379 | 380 | return d->settings->isWritable(); 381 | } 382 | 383 | QDateTime FileEngine::lastModified() 384 | { 385 | Q_D(FileEngine); 386 | return d->mainSettingsDT; 387 | } 388 | 389 | bool FileEngine::settingsIsWritable() const 390 | { 391 | Q_D(const FileEngine); 392 | return d->settings->isWritable(); 393 | } 394 | 395 | QHash FileEngine::settings() 396 | { 397 | Q_D(FileEngine); 398 | return d->mainSettings; 399 | } 400 | 401 | QString FileEngine::settingsValue(Cutelyst::Context *c, const QString &key, const QString &defaultValue) const 402 | { 403 | Q_D(const FileEngine); 404 | 405 | QHash::ConstIterator it = d->mainSettings.constFind(key); 406 | if (it != d->mainSettings.constEnd()) { 407 | return it.value(); 408 | } 409 | return defaultValue; 410 | } 411 | 412 | bool FileEngine::setSettingsValue(Cutelyst::Context *c, const QString &key, const QString &value) 413 | { 414 | Q_D(FileEngine); 415 | d->settings->beginGroup(QStringLiteral("Main")); 416 | d->settings->setValue(key, value); 417 | d->settings->endGroup(); 418 | 419 | #if (QT_VERSION <= QT_VERSION_CHECK(5, 4, 0)) 420 | // Force a change to notify cache that something changed 421 | utime(d->settingsInfo.absoluteFilePath().toLatin1().data(), NULL); 422 | #endif 423 | 424 | // this is needed due to a new 425 | // call to value() does not return old values 426 | d->mainSettings.insert(key, value); 427 | 428 | return d->settings->isWritable(); 429 | } 430 | 431 | void FileEngine::loadPages() 432 | { 433 | Q_D(FileEngine); 434 | 435 | qCDebug(CMS_FILEENGINE) << "Loading pages..." << QCoreApplication::applicationPid(); 436 | 437 | QDirIterator it(d->pagesPath.path(), 438 | QDir::Files | QDir::NoDotAndDotDot, 439 | QDirIterator::Subdirectories); 440 | while (it.hasNext()) { 441 | loadPage(it.next()); 442 | } 443 | } 444 | 445 | void CMS::FileEngine::settingsDirChanged(const QString &path) 446 | { 447 | qCDebug(CMS_FILEENGINE) << "Changed :" << path << QCoreApplication::applicationPid(); 448 | } 449 | 450 | void FileEngine::loadSettings() 451 | { 452 | Q_D(FileEngine); 453 | 454 | qCDebug(CMS_FILEENGINE) << "Loading settings" << QCoreApplication::applicationPid(); 455 | 456 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 1)) 457 | QFileSystemWatcher *settingsWatch = qobject_cast(sender()); 458 | if (settingsWatch) { 459 | // On 5.4.1 the watch stops working once the file get's replaced 460 | // so make sure we watch it again 461 | settingsWatch->addPath(d->settingsInfo.absoluteFilePath()); 462 | } 463 | #endif 464 | 465 | // Make sure we read new things 466 | d->settings->sync(); 467 | 468 | // Load main settings 469 | QHash settings; 470 | d->settings->beginGroup(QStringLiteral("Main")); 471 | Q_FOREACH (const QString &key, d->settings->allKeys()) { 472 | settings.insert(key, d->settings->value(key).toString()); 473 | } 474 | d->settings->endGroup(); 475 | d->mainSettings = settings; 476 | 477 | // Load menus and menu locations 478 | d->settings->beginGroup(QStringLiteral("Menus")); 479 | QList menus; 480 | QHash menuLocations; 481 | Q_FOREACH (const QString &menu, d->settings->childGroups()) { 482 | Menu *obj = d->createMenu(menu, this); 483 | 484 | bool added = false; 485 | Q_FOREACH (const QString &location, obj->locations()) { 486 | if (!menuLocations.contains(location)) { 487 | menuLocations.insert(location, obj); 488 | added = true; 489 | } 490 | } 491 | 492 | menus.append(obj); 493 | } 494 | d->settings->endGroup(); 495 | d->menus = menus; 496 | d->menuLocations = menuLocations; 497 | 498 | // Store the last modified date 499 | d->mainSettingsDT = d->settingsInfo.lastModified(); 500 | } 501 | 502 | Menu *FileEnginePrivate::createMenu(const QString &id, QObject *parent) 503 | { 504 | settings->beginGroup(id); 505 | 506 | Menu *menu = new Menu(id); 507 | 508 | menu->setName(settings->value(QStringLiteral("Name")).toString()); 509 | menu->setLocations(settings->value(QStringLiteral("Locations")).toStringList()); 510 | menu->setAutoAddPages(settings->value(QStringLiteral("AutoAddPages")).toBool()); 511 | 512 | QList urls; 513 | 514 | int size = settings->beginReadArray(QStringLiteral("urls")); 515 | for (int i = 0; i < size; ++i) { 516 | settings->setArrayIndex(i); 517 | QVariantHash data; 518 | // TODO read all data 519 | data.insert(QStringLiteral("id"), i); 520 | data.insert(QStringLiteral("text"), settings->value(QStringLiteral("text"))); 521 | data.insert(QStringLiteral("url"), settings->value(QStringLiteral("url"))); 522 | data.insert(QStringLiteral("attr"), settings->value(QStringLiteral("attr"))); 523 | urls.append(data); 524 | } 525 | settings->endArray(); 526 | 527 | menu->setEntries(urls); 528 | 529 | settings->endGroup(); // menu name 530 | 531 | return menu; 532 | } 533 | --------------------------------------------------------------------------------