Compare commits

...

163 commits
b2.2.0 ... beta

Author SHA1 Message Date
Ro
62e2dea287
Beta 2.6.1 update 2023-05-14 18:08:31 -07:00
Ro
f824b53f2a
Update versioning to 2.6.1
All the issues in milestone 2.6.1 have been completed, so now it's time for an update
2023-05-14 18:00:53 -07:00
Ro
8461d88dd6
Settings CSS Cleanup
The Settings UI needed some responsive polish, so that's been given a
bit more love.
2023-05-14 17:37:39 -07:00
Ro
a9c88f1430
Edits for #86 and #92
Fixed the issue where the text edit controller would scroll right off
the screen. Now it stick when it's the bottom of the header.

Also changed the background color of page links on the Start and Book
pages to indicate there is no image. It's just cleaner
2023-04-28 16:17:15 -07:00
Ro
fa4b252d9c
Fix for #94
Header images were missing from the archive and tags pages, so this is a
patch to make sure those are working again.
2023-04-28 14:08:49 -07:00
Ro
181225329a
CSS and icon fixes
Fixes for issues #93, #88, #87
2023-04-13 14:31:43 -07:00
Ro
4876c1336e
Updated Composer packages; empty field fix
Composer dependencies were pretty old, so they needed to be upgraded to
the latest.

There was also a minor bug that was triggered when a new page was saved
with empty tags and no images or documents, so that's been patched as
well.
2023-04-09 17:16:24 -07:00
Ro
8ce253418d
Adding site creation hot fix
Adding the fixes for site install process errors to the branch.
2023-04-06 18:03:53 -07:00
Ro
f1850ce7f7
Site Creation Hot Fix
There was an error in the request to set up a fresh site on the front
end and handling the respective object on the backend that was causing
the process to error out.

Also added the source map for the dash script because it's eventually
going to be fully transparent anyway.
2023-04-06 17:59:31 -07:00
Ro
8622ba5941
Minor js hotfixes
Updating the script and the html to call the new file name, which is
called 'dash.js'
2023-04-05 17:19:39 -07:00
Ro
e7cd52bd12
Fixed script after removing dependency
Removed 'carot-ps' from dependencies so it had to be removed from
scripts calling that package.

Also renamed dash script to 'dash.js' just so it's clear it is for the
dashboard
2023-04-05 17:14:21 -07:00
Ro
f9190c2a41
removed carot-pos from dependecies 2023-04-05 17:05:38 -07:00
Ro
302362a478
Merge 'develop' 2.6.0 upadte into beta
Massive update covering issues #71, #80, #81, #83 as well as updated
responsive styles and an overhauled File Manager
2023-04-05 16:00:18 -07:00
Ro
8885ae4c63
updated version nunmber 2023-04-05 15:48:36 -07:00
Ro
405be1a6ed
Emplty Layout Hotfix
When creating new pages, there is no layout, so the system was pushing a
null error when trying to use string_contains and a null string, so
cleaned that up so it defaults to 'page' when that string is empty
2023-04-05 15:16:30 -07:00
Ro
3f9506ac6b
Responsive P 4 - Dash, Index, Editor, Nav, Menu
Cleaned up resonsive for the rest of the remaining pages: the dash
index, page index, page editor, navigation editor and plugged in a
mobile nav that activates when the viewport gets skinny.

Whew.
2023-04-05 14:40:14 -07:00
Ro
e7fd91c152
Responsive Pt 3 - Pass Reset, Site Create/Restore
Rebuilt forms for resetting the password, creating a fresh site and
restoring a site from a backup, as well as adjusting the accompanying
scripts that handle those processes.
2023-03-31 16:20:11 -07:00
Ro
5adf196783
Responsive Part 2 - Login
Updated Login CSS and cleaned up login notifications to alert when
something is amiss.
2023-03-30 14:27:56 -07:00
Ro
2ce86fad2e
Settings CSS Remix
Wasn't feeling the previous CSS responsive structure, so edited it to be
a bit more streamline.

Also fixed small issue with the backup API request.

Made a small change to notifications so the alert stays live while the
system is processing a request rather than going back to an unalert
state.
2023-03-30 13:40:59 -07:00
Ro
bfb0873f5f
Reponsive: Part 1 - Settings
Started cleaning up responsive styles for the site starting with the
Settings section. Still needs some tweaking but the structure for that
section is in so it's just a matter of police.

Some changes need to be made to the main nav as reduced viewport throws
off the alignment.
2023-03-28 18:19:40 -07:00
Ro
97278e3a90
Notifications Rework #81
Integrated the Notifications UI component into the header to streamline
user alerts into the overall experience.

Also added titles to use the space created by moving the notifications
compoenent to it's own space.
2023-03-26 19:47:42 -07:00
Ro
78bfe4596b
Styled Nav Editor
Created new CSS styles for dash nav editor and updated the appropriate
controller scripts.

Also updated the icons for the main nav.
2023-03-25 20:28:38 -07:00
Ro
1b89d1d072
Restyled Settings UI
Rebuilt the css for the Settings UI, which also led to some changed in
the FilesAPI so it knows what to do with specific targets. There's still
some additional styling needed to polish it, but the core structure is
in place so now it can just be tweaked. The controller for this page was
adjusted as needed.

Also moved the settings sub nav convtrols to the header menu since it's
sticky now.
2023-03-25 16:44:35 -07:00
Ro
fcca7357bc
Fixes for removing media items and page deletion
The upload process changed, so some tweaks needed to me made to the page
deletion process, which just marks the page as deleted but keeps the
file. Also updated the file manager to properly delete items from the
display list.

The css for page listings also had to updated [forgot to put that on the
list] so the styles for that were updated and the template pages
adjusted accordingnly.

Also forgot to mention changes to the notification display in the last
commit. It's basic as of right now but it will be enhanced as needed.
2023-03-24 14:57:01 -07:00
Ro
07b422a9c3
CSS Overhaul Part 1
This one is a doozy, so let's breakt it down into what areas where
touched.

-   updated package json to remove unneeded dependencies.
-   rebuilt file uploading to simply a very convuluted process
-   began proces to replace icons with https://tabler-icons.io
-   began process of removing the need for css preprocessor and using
    pure css
        - login completed
        - dashboard index completed
        - page edit ui completed
- page edit ui text editor tweaked so syntax highlighting is cleaner and
  more accurate

The settings and navigation UIs still remain and then polishing the
responsive for the new css structure
2023-03-23 13:55:34 -07:00
Ro
ec1dc49ba1
Login Hotfix
The script that handles logggin in and the form for getting that
information were both posting the info which would result in an
intemittent uncaught error.

An attribute was added to the form so it does not submit at the same
time the JS sends a request.

A minor bug but it was annoying.
2022-11-04 12:52:19 -07:00
Ro
61ae73a9e5
Issue #83 Round 1
First pass for CSS refactor for the dashboard, including the login and
index templates. Still rough but the basic structure is in place for
both as well as the re-worked css that will be added to the repo later
once all the pages have been updated.

Lots to do still but a good start.
2022-11-03 13:46:36 -07:00
Ro
859b75e9f3
Removed links to old repo from ReadMe
I'll probably miss some as the migration completes but let's start with
scrubbing the old repo from the new one.
2022-10-10 13:07:12 -07:00
Ro
a14d4a0a08
Move Repo test
Just a quick update to see if I got all the setting right as I'm
migrating the show to a fresh repo
2022-10-10 12:54:35 -07:00
Ro
7890715ea6
PHP Linting Tweaks
PHP syntax checking was being weird so I spent some time to make some
corrections. The problem was I was just using the wrong protocal, PEAR
when I've been coding to the PSR12 standard. Easy fix.
2022-10-10 11:49:04 -07:00
Ro
77eb8dd1a8
HTTP Method notes for RouteControl
Currently only two http methods are being utilized for route traffic so
classes are getting jumped trying to stuff every action in on or the
other. More methods need to be implemented to better organize route
pathing and subsequent requests
2022-09-19 21:43:57 -07:00
Ro
e431f1afa4
Sign commit test
Testing out commit signing for a bit of extra security.
2022-09-19 21:12:16 -07:00
Ro
254a7f1c38 Scrubbed Moment from codebase
Moment was still being used in some classes so found and replaced all
those instances with Carbon and uninstalled the packaged from composer.
2022-09-19 20:39:15 -07:00
Ro
c2b3b234fa Merging autoload changes 2022-09-19 16:55:38 -07:00
Ro
b092645733 Removed Fipamo classes from composer autoloading
I didn't like the extra step that had to be taken to register new
classes from the command line using composer's auto dump, so a quick
script was implemented to handle Fipamo loading classes seperately so
composer can manage itself, removing the need for updating it whenever I
add a new classs to the codebase
2022-09-19 16:52:29 -07:00
Ro
fce378d437 Merging dependency updates and Moment removal
Bringing over changes from develop into the beta branch, including
updating composer package dependencies and getting rid of Moment in
favor of Carbon for date formatting
2022-09-18 13:58:04 -07:00
Ro
73e4243231 Removed TODO note for Carbon
Left a stray TODO in there that needed to be removed since Moment has
been replaced with Carbon.
2022-09-18 13:22:02 -07:00
Ro
3260e3b76b Updated dependencies, replaced Moment
Composer package dependencies hadn't been updated in awhile, so a part
of the clean up for the php 8.1 install, that has been handled

Moment was being used to handle date formatting but it hasn't been
updated in awhile either, so I switched to Carbon which is still in
active development.
2022-09-18 13:07:52 -07:00
Are0h
69fc689d38 Finally merging 2.5.1 updates into beta
2.5.1 is ready to go, so it's time to merge into the beta branch and
  test it to make sure everything is ship shape
2022-06-16 13:59:57 -07:00
Are0h
a1c0d86580 update versioning, clean up npm
I updated the versioning to match in both composer.json and pacakge.json
config files. I need to start cleaning up the dependencies in the front
end scripting as well, so I started my updating them to see much work
needs to get done. Not too bad, but it's going to be a pretty
significant effort.
2022-06-16 13:43:52 -07:00
Are0h
197fb005de Added Markdown table conversion and version update
Added markdown table conversion to the html process so we get sexy
tables. Also updated the version number to prep for the latest beta
realease. Ha, took a minute but we're back on track
2022-06-14 17:08:31 -07:00
Are0h
00d41a3664 Update composer packages
Needed to update some packages rely on because it's been a minute since
they've been touched. I need to get better with doing that on a regular
basis.
2022-05-19 14:07:09 -07:00
Are0h
3fa3a9e0e6 removed stray console command for #78 updated
just had a stray trace in there that was removed and correct the issue
referene from the last update. oops.
2022-05-18 16:49:16 -07:00
Are0h
8734baf85e Fix for #76, editing upload que
Whew it took a bit but now the upload que on the page edit page is
editable. You can add and remove files as needed without having to add
all your files at once, which us how it previously worked. Still needs
to be tested a bit but the plumbing is up and running.

Also removed a stray php format config as it is no longer needed.
2022-05-18 16:41:25 -07:00
Are0h
a31dff94cb Updated PHP lint to @PSR12 b/c @PSR2 is deprecated
So, @PSR12 is the recommended coding standard, so I updated the config
and reformatted the appropriate files.

Again. Whew.
2022-05-16 19:14:38 -07:00
Are0h
63eaba08e2 Added config for PHP formatting (PSR2)
I needed some consistent php formatting, so I plugged in a php fixer
config and then reformatted all PHP files so it's all consistent.

Fixed an ID issue with the page-edit template that was causing page
editing to fail.
2022-05-16 17:41:15 -07:00
Are0h
d9c9f7744e Fixed white space issue, page edit style tweaks
There was a white space issue that made text displayed title and tags
text appear missaligned. It turned out to be a small layout problem that
was cleaned up by getting rid of white space in the textarea element
itself.

Also got in there and fixed some lingering css issues that was causing
the svg icon colors to be off in some of the butttons.
2022-05-11 17:41:56 -07:00
Are0h
b2c7dae322 Page Editor style fixes
The page editor UI was looking very shaky, so I went in there and
cleaned it up. The nesting is a bit intense, so I'm gonna think about
better ways of laying that out that aren't so convoluted.
2022-05-10 18:48:34 -07:00
Are0h
2501a19685 UI design tweaks
There were some inconsistencies in the UI due to all the changes that
were made to the styleshets, so I fixed the obvious ones that were show
stoppers.

There's more in there to be smoothed out, so this is just the start.
2022-05-10 18:05:41 -07:00
Are0h
b8b763637f finished scss stylint refactor for remaining pages
finished up refacting all of the style sheets to be brought in line with
the stylint scss standard. the standard will probably change as i tweak
the rules but the foundation is solid and it's rendering so it's a
great place to start

i'll go through the ui to make sure everything is in tact and i'll make
the necessary adjustments

i should probably refactor some structures as well as they nesting is a
bit overly complicated
2022-05-10 17:19:46 -07:00
Are0h
61b9acb280 I swapped out sass for scss file for styling because scss is a bit more
accessible because of its similarity with base css.

I also plugged in stylint to normalize a css standard throughout the
project to work with prettier, which handles formatting

The structure scss file has been brought in line with the new standard
but the remaining need to be adjusted as well. Those will be added with
an additional commit.
2022-05-10 14:13:31 -07:00
Are0h
c546aa7b63 hotfix for editor img uploads, formatting tweaks 2022-04-23 11:31:20 -07:00
Are0h
aa3301fb66 hotfix for images in editor uploads 2022-04-23 11:28:20 -07:00
Are0h
9baaed6d50 formatting changes 2022-04-16 13:21:57 -07:00
Are0h
6f2a8cfb4b hot fix for open graph image rendering 2022-04-14 14:47:28 -07:00
Are0h
2e30d6eb26 fix for open graph image render, minor tweaks to class importing 2022-04-14 14:46:17 -07:00
Are0h
7393e4572c readme tweak 2022-03-25 15:01:07 -07:00
are0h
614c9859b1 fix for weird css dahsh deletion bug 2022-03-20 15:28:02 -07:00
are0h
0ee4083949 fix for dash css 2022-03-20 15:23:04 -07:00
are0h
d92944b2ec fixes for dash templates default theme 2022-03-20 15:14:57 -07:00
are0h
a841063ddb template update and clean up 2022-03-20 15:14:23 -07:00
are0h
c004452c55 just fixing a script error 2022-03-19 20:05:36 -07:00
are0h
692926c816 script check 2022-03-19 20:05:15 -07:00
are0h
7cefc12692 2.5.0 commit part 1, whew 2022-03-19 19:41:02 -07:00
are0h
b230f3f15d fixes for nav editing 2022-03-19 19:36:15 -07:00
are0h
d7c5fb7a70 dev env tweaks 2022-03-19 18:56:58 -07:00
are0h
d98bccdd1f missed a class reference. oops 2022-03-19 16:24:19 -07:00
are0h
382c314af0 fixes for restore from backup file 2022-03-19 16:15:56 -07:00
Ro
6279ad4730 fixes for site init 2022-03-19 14:55:56 -07:00
Ro
2b7db3cc88 removed unecessary mixins 2022-03-19 14:17:24 -07:00
Ro
a70c98afa0 fixes for site back up and mailer, turned on md list rendering 2022-03-18 16:58:11 -07:00
Ro
8ad3fa38c5 respeced class imports to psr-4 standard, fixed asset moving, upgraded theme rendering 2022-03-18 16:00:51 -07:00
Ro
1351b98ee4 cleaned up create page process, update page edit template to handle empty files 2022-03-15 16:56:37 -07:00
Ro
2210e39aee added remaining upload types, updated templates for new file types 2022-03-13 17:46:42 -07:00
Ro
523b611ac5 fixed weird form post bug, updated page listing display for new assets 2022-03-13 14:22:22 -07:00
Ro
fe74fd6e07 fixed css issue with text edit area 2022-02-23 16:42:25 -08:00
Ro
2ed2cd3803 fix for page-edit template, oops 2022-02-23 16:16:12 -08:00
Ro
f24a6b5099 implemented video uploading, updated styles and templates 2022-02-23 15:48:11 -08:00
Ro
3c52bca8ba started implementation of multiple file uploads and sorting 2022-02-12 18:35:09 +00:00
Ro
2d5de69f1c format bar float fix, added upload progess bar 2022-01-30 13:13:38 -08:00
Ro
8f9021bb7d added progress bar to entry image uploads 2022-01-26 16:25:05 -08:00
Ro
53864becc1 implemented progress upload indicator in notifications 2022-01-26 15:57:27 -08:00
Ro
601fd6b1ab fix for weird css file deletion thing 2022-01-26 13:26:41 -08:00
Ro
b69559541a edit format bar always stays on screen 2022-01-25 15:17:41 -08:00
Ro
f6aac33ae4 Had a stray var dump in there. Oops 2022-01-17 16:31:51 -08:00
Ro
8684c8b1ac removed stray var dump... oops 2022-01-17 16:31:05 -08:00
Ro
c98a75f931 updated production assets 2022-01-16 13:53:06 -08:00
Ro
c15b6cdc5b updated production assets 2022-01-16 13:48:36 -08:00
Ro
257d2a0623 text editor adjustments, fix for page delete, ignore update 2022-01-16 13:42:46 -08:00
Ro
59e0f37b3e restored dash styles 2022-01-16 13:37:57 -08:00
Ro
7cabb1d1f0 more text editor touch ups, fix for page delete option 2022-01-16 13:33:04 -08:00
Ro
f1a8ef67bc another patch for textarea misalignment bug 2022-01-13 15:43:38 -08:00
Ro
10081c4323 fix for text editor alignment issue. finally 2022-01-12 14:56:24 -08:00
Ro
32a4f32202 fix for src being removed from img with relative urls, added iframe to
allowed html
2022-01-07 15:46:23 -08:00
Ro
b70308d990 fixed src removal from image tags with relative urls, add iframe tag to allowed list 2022-01-07 15:45:35 -08:00
Ro
0e52528de0 accidentlly removed feature img from recent and featured lists. oops 2022-01-04 17:06:15 -08:00
Ro
fdf2319783 re-inserted feature img into feature and recent lists. oops 2022-01-04 17:05:57 -08:00
Ro
6c053868a2 hot fix for page rendering defaulting to page every time 2022-01-04 16:57:27 -08:00
Ro
71dc41e950 fix for page rendering always defaulting to page 2022-01-04 16:56:58 -08:00
Ro
dc08f60098 hot fix for template and empty layout on new page creation 2022-01-04 14:20:19 -08:00
Ro
db385d938c not fix for template and empty layout for new page creation 2022-01-04 14:19:30 -08:00
Ro
8502c4f0e0 fixed start twig template 2022-01-04 13:55:31 -08:00
Ro
2fdd7f40f0 cleared conflict with git ignore 2022-01-04 13:47:55 -08:00
Ro
de2aec58c9 Added new dash script, duh
# Conflicts:
#	.gitignore
#	src/com/Base.js
2022-01-04 13:44:47 -08:00
Ro
d2f02eea50 edited ignore file to include new script. duh. 2022-01-04 13:39:28 -08:00
Ro
55b16a0acd update ignore file 2022-01-04 12:53:57 -08:00
Ro
39c6ff7f11 Update for #76 - Beta 2.5.0
# Conflicts:
#	src/com/actions/PageActions.js
#	src/com/controllers/PageEditor.js
#	src/com/controllers/SettingsIndex.js
#	src/com/ui/TextEditor.js
#	src/styles/dash.sass
#	src/styles/main/_colors.sass
#	src/styles/main/_editor-highlight.sass
#	src/styles/main/_normalize.sass
#	src/styles/main/_posts.sass
2022-01-04 12:53:29 -08:00
Ro
dad43f4a19 pages marked as menu items render as non-blog pages 2022-01-03 16:52:55 -08:00
Ro
7775c1d409 expanded and streamlined markdown rendering 2021-12-29 17:10:01 -08:00
Ro
0742e06c45 cleand up text editor layout, tweaked editor colors, re-activated editor options 2021-12-28 14:48:54 -08:00
Ro
682406515d added assets compiling scripts, added syntax highlighting for text editor, fix for #73 2021-12-27 16:42:10 -08:00
Ro
1b66f5daf9 Beta 2.4.1 - fixes for #68, #69, #70, #72, #75 2021-12-19 14:15:02 -08:00
Ro
c0c3b60fd5 added feature image to tags to display page 2021-12-19 13:53:47 -08:00
Ro
4f4fee807c fix for #69, updated settings ui 2021-12-18 16:09:26 -08:00
Ro
1c2ba579df fix for #68, updated page edit ui 2021-12-18 15:42:16 -08:00
Ro
03c629462b fix for #75, fix for adding image to entry text 2021-12-18 12:05:13 -08:00
Ro
f119bdc773 fix for #72, index page not using page themes 2021-12-18 11:30:41 -08:00
Ro
e5873b92cf Hot fix to add images to recent and featured links on the index 2021-12-17 14:40:41 -08:00
Ro
39775e624d hot fix for adding images two recent and featured links 2021-12-17 14:40:09 -08:00
Ro
4151891129 Release 2.4.0 - #61 #62, fixed #63, #64, #65, #66
# Conflicts:
#	src/com/actions/PageActions.js
#	src/com/controllers/SettingsIndex.js
#	src/styles/main/_posts.sass
2021-11-23 15:34:24 -08:00
Ro
7e38b4edb8 quick patch for #66 2021-11-22 16:52:26 -08:00
Ro
3994e97829 moved layout selector in page edit ui 2021-11-22 14:29:50 -08:00
Ro
c867b6c508 adde page preview feature, updated page edit ui with preview option button 2021-11-22 14:24:26 -08:00
Ro
49e53a9638 added styled page layout selector, page renders selected layout 2021-11-18 14:59:59 -08:00
Ro
a8355b2da4 created new Theme data class for theme stuff, added custom page view, added view select for page edit screen 2021-11-17 16:59:53 -08:00
Ro
bbfe37597a fixes for #63, #64, #65, updated composer dependencies 2021-11-14 13:54:36 -08:00
Ro
4f4ee5dfc7 updated gitignore 2021-11-08 13:59:49 -08:00
Ro
cecead05a1 Fixed #58, #59, #60 2021-11-08 13:56:42 -08:00
Ro
e6cda301cf #59 - added toggle to render pages when page is edited 2021-11-08 13:52:33 -08:00
Ro
4796431076 completes #58 default theme links adapt to dynamic rendering, no page rendering if dynamic active 2021-11-03 15:19:52 -07:00
Ro
35c780bba6 added dynamic page rendering for index, archives, tages and blog pages 2021-11-02 16:19:03 -07:00
Ro
e5cffd39d7 fix for #60 - render flag false fix 2021-10-27 14:37:42 -07:00
Ro
815ebf58f7 Hot fix for page creation (token not being share in create mode) 2021-10-21 13:28:04 -07:00
Ro
d6f6f771ac hot fix for page creation (token not being shared on create mode) 2021-10-21 13:27:48 -07:00
Ro
a475b64aca Fixes for issues #56, #56 and renamed base logo 2021-10-06 13:21:28 -07:00
Ro
9fe8ce1e4c #56 moves global assets from theme if not present in public dir 2021-10-06 13:17:42 -07:00
Ro
1bb13ce771 #57 notifications ui position tweak 2021-09-23 13:15:03 -07:00
Ro
ccf65e1899 Merge branch 'develop' into beta
Fixed #51, #52, #53, #55
2021-09-14 12:54:06 -07:00
Ro
934d29f4cf added field check for page edits to make sure unnecessary fields are not being added 2021-09-14 12:47:57 -07:00
Ro
ccbf55bb54 added form token auth to page editing, updated API 2021-09-13 15:43:54 -07:00
Ro
fdc6cb2cf2 added form token to session to validate form submission from frontend 2021-09-12 14:40:27 -07:00
Ro
f2450b2be5 fixed incorrect input type (#52), cleaned up site restore process (#53) 2021-08-25 16:12:35 -07:00
Ro
6e9368c2aa updated composer dependenciesa 2021-08-10 11:47:48 -07:00
Ro
d7c2827115 updated composer dependencies 2021-08-10 11:47:23 -07:00
Ro
854cf12067 re-activated site backups for now 2021-08-09 14:07:31 -07:00
Ro
8df5e68024 re-activated site back up... for now... 2021-08-09 14:04:49 -07:00
Ro
85a7eab8be Minor Admin API tweaks, added theme render flag check for one pagers 2021-08-08 15:09:48 -07:00
Ro
734c6d36f1 Admin API tweaks, added check for theme render flag for one pagers 2021-08-08 15:09:13 -07:00
Ro
bf383785a0 changed auth process to just ask server if session is active 2021-08-03 14:32:29 -07:00
Ro
183a620b1e changed auth process to just ask server if session is active 2021-08-03 14:31:13 -07:00
Ro
b1c884689e Relaxed image upload auth.
# Conflicts:
#	src/com/controllers/MaintenanceManager.js
#	src/com/controllers/SettingsIndex.js
2021-08-02 15:35:57 -07:00
Ro
b3c47d6721 overdid it with the image upload auth, making it a regular upload 2021-08-02 15:34:21 -07:00
Ro
ce30f3efb7 Fixed image upload that needed authorization 2021-08-02 15:15:30 -07:00
Ro
cad32ace74 fixed image upload request that required token authorization 2021-08-02 15:13:46 -07:00
Ro
fe8a5e4e96 Adds key to original settings file on site restore if not present 2021-08-02 13:52:50 -07:00
Ro
7d3f2d13a4 adds externalAPI key in settings if not present in original settings file when restoring 2021-08-02 13:52:12 -07:00
Ro
da49388caa Added missing key to settings init template 2021-08-02 13:07:46 -07:00
Ro
a1932e1a24 added missing eternal API toggle to settings template 2021-08-02 13:06:59 -07:00
Ro
3c3f2a0881 Quick patch for CORS check while in init state 2021-08-02 12:53:36 -07:00
Ro
b24f0a6adb quick patch for CORS check while in site init state 2021-08-02 12:52:19 -07:00
Ro
3968e32e78 Merge branch 'develop' into beta
added admin method for getting site and member data
2021-07-22 13:08:01 -07:00
Ro
0ec49de8ab admin api commenting tweak 2021-07-22 13:06:52 -07:00
Ro
83b4aff716 added admin API method for getting site and member info 2021-07-21 12:46:14 -07:00
142 changed files with 37690 additions and 7975 deletions

View file

@ -1 +1,13 @@
{ "presets": ["env"] }
{
"presets": [],
"plugins": [
[
"prismjs",
{
"languages": ["html", "markdown", "markup"],
"theme": "okaidia",
"css": false
}
]
]
}

View file

@ -49,7 +49,7 @@
"no-unsafe-finally": 2,
"no-unsafe-negation": 2,
"no-unused-labels": 2,
"no-unused-vars": 2,
"no-unused-vars": 1,
"no-useless-escape": 1,
"require-yield": 2,
"use-isnan": 2,

29
.gitignore vendored
View file

@ -1,5 +1,6 @@
node_modules/
src/node_modules/
.parcel-cache/
.sass-cache/
.cache/
.nova/
@ -11,11 +12,21 @@ public/*
public/assets/*
!public/assets/css
public/assets/css/*
!public/assets/css/dash.css
!public/assets/css/dash
!public/assets/scripts
public/assets/scripts/*
!public/assets/scripts/dash.min.js
<<<<<<< HEAD
!public/assets/scripts/Start.js
/public/assets/images/global/rikc-logo.svg
=======
!public/assets/scripts/dash.js
<<<<<<< HEAD
>>>>>>> develop
=======
!public/assets/scripts/dash.js.map
>>>>>>> develop
!public/assets/images
public/assets/images/*
!public/assets/images/global/
@ -41,13 +52,7 @@ config/tags.json
config.codekit3
/config/backups
/src/com
/src/style
/src/node_modules
/src/com/Base.js
/src/com/controllers/NavIndex.js
/src/com/controllers/PageEditor.js
/src/com/controllers/SettingsIndex.js
/src/libraries/FipamoAPI.js
/src/package-lock.json
/src/package.json
src/com/*
src/styles/*
src/com/ui/TextEditor.js

74
.php-cs-fixer.php Normal file
View file

@ -0,0 +1,74 @@
<?php
$config = new PhpCsFixer\Config();
return $config
->setRiskyAllowed(true)
->setRules([
'@PSR12' => true,
'array_indentation' => true,
'array_syntax' => [
'syntax' => 'short',
],
'combine_consecutive_unsets' => true,
'method_chaining_indentation' => true,
'class_attributes_separation' => [
'elements' => [
'const' => 'none',
'method' => 'one',
'property' => 'none',
'trait_import' => 'none',
],
],
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'single_quote' => false,
'binary_operator_spaces' => [
'default' => 'single_space',
'operators' => [
'=' => 'align_single_space_minimal',
'=>' => 'align_single_space_minimal',
],
],
'braces' => [
'allow_single_line_closure' => true,
],
'concat_space' => [
'spacing' => 'one',
],
'declare_equal_normalize' => true,
'function_typehint_space' => true,
'single_line_comment_style' => [
'comment_types' => [
'hash',
],
],
'include' => true,
'lowercase_cast' => true,
'no_extra_blank_lines' => [
'tokens' => [
'curly_brace_block',
'extra',
'parenthesis_brace_block',
'throw',
]
],
'no_multiline_whitespace_around_double_arrow' => true,
'no_spaces_around_offset' => true,
'no_unused_imports' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
'object_operator_without_whitespace' => true,
'single_blank_line_before_namespace' => true,
'ternary_operator_spaces' => true,
'trim_array_spaces' => true,
'unary_operator_spaces' => true,
'whitespace_after_comma_in_array' => true,
'single_line_after_imports' => true,
'ordered_imports' => [
'sort_algorithm' => 'none',
],
//Other rules here...
])
->setLineEnding("\n");

7
.prettierignore Normal file
View file

@ -0,0 +1,7 @@
.babelrc
README.md
*.twig
*.sass
*.json
*.php
*.md

36
.prettierrc Normal file
View file

@ -0,0 +1,36 @@
{
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
},
{
"files": "*.scss",
"options": {
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"printWidth": 90
}
},
{
"files": "*.js",
"options": {
"arrowParens": "avoid",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"bracketSameLine": false,
"jsxSingleQuote": true,
"proseWrap": "preserve",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"useTabs": true,
"tabWidth": 4,
"printWidth": 90
}
}
]
}

3
.stylelintrc Normal file
View file

@ -0,0 +1,3 @@
{
"extends": ["stylelint-config-standard"]
}

View file

@ -1,13 +1,9 @@
![This is Fipamo](https://playvicio.us/base-assets/images/fipamo-brand.png)
# Fipamo means to save
The Fipamo project was born from a need for a simple, easy to use no data blog platform that doesn't require much effort to set up and maintain. Fipamo uses Markdown to handle posts and renders them to flat html so you can serve them from anywhere. No complicated set ups. No long list of dependencies. Just write and publish.
The Fipamo project was born from a need for a simple, easy to use no data blog platform that doesn't require much effort to set up and maintain. Fipamo uses Markdown to handle posts and renders them to flat html so you can serve them from anywhere. No complicated set ups. No long list of dependencies. Just set up and go.
Because nobody has time for all that.
## Check the (WIP) Docs to get you started. <br>
[Getting Started](https://code.playvicio.us/Are0h/Fipamo/wiki/00---Start) <br>
[Install](https://code.playvicio.us/Are0h/Fipamo/wiki/01---Install) <br>
[Using Fipamo](https://code.playvicio.us/Are0h/Fipamo/wiki/02-Usage) <br>
[Getting Started](https://koodu.ubiqueros.com/are0h/Fipamo/wiki/00---Start) <br>

View file

@ -1,46 +0,0 @@
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
include "../brain/controller/RouteControl.inc.php";
include "../brain/data/Settings.inc.php";
include "../brain/data/Session.inc.php";
include "../brain/data/Member.inc.php";
include "../brain/data/Auth.inc.php";
include "../brain/data/Render.inc.php";
include "../brain/utility/StringTools.inc.php";
include "../brain/utility/FileUploader.inc.php";
include "../brain/utility/DocTools.inc.php";
include "../brain/utility/Sorting.inc.php";
include "../brain/utility/Setup.inc.php";
include "../brain/utility/Maintenance.inc.php";
include "../brain/utility/Mailer.inc.php";
include "../brain/utility/HandleCors.inc.php";
class App
{
public function __construct()
{
// set up cors
new HandleCors();
$app = AppFactory::create();
$twig = Twig::create("../brain/views/");
$app->add(TwigMiddleware::create($app, $twig));
//set up routing
$app->get(
"/[{first}[/{second}[/{third}[/{fourth}[/{fifth}]]]]]",
"\RouteControl:get"
);
$app->post(
"/[{first}[/{second}[/{third}[/{fourth}]]]]",
"\RouteControl:post"
);
//start the app
$app->run();
}
}

10
brain/_loader.php Normal file
View file

@ -0,0 +1,10 @@
<?php
spl_autoload_register(function ($className) {
$file = dirname(__DIR__) . '\\' . $className . '.php';
$file = str_replace('\\', DIRECTORY_SEPARATOR, $file);
//echo $file;
if (file_exists($file)) {
include $file;
}
});

View file

@ -1,73 +0,0 @@
<?php
class AuthAPI
{
public function __construct()
{
}
public static function status()
{
$result = [];
if (Auth::status()) {
$result = [
"message" => "Authorized",
"type" => "apiUseAuthorized",
"token" => Session::get("token"),
];
} else {
$result = [
"message" => "Not Authorized",
"type" => "apiUseNotAuthorized",
];
}
return $result;
}
public static function login($body)
{
$result = [];
switch (Auth::login($body)) {
case "no_name":
$result = [
"message" => "Need to see some id, champ",
"type" => "requestLame",
];
break;
case "bad_pass":
$result = [
"message" => "Check your password, sport",
"type" => "requestLame",
];
break;
default:
$result = [
"message" => "Welcome back",
"type" => "requestGood",
];
break;
}
return $result;
}
public static function logout($body)
{
Auth::logout($body);
$result = [
"message" => "Till next time, g.",
"type" => "TASK_LOGOUT",
];
return $result;
}
public static function requestSecret($body)
{
$result = Auth::findSecret($body);
return $result;
}
public static function resetPassword($body)
{
$result = Auth::makeNewPassword($body);
return $result;
}
}

81
brain/api/v1/AuthAPI.php Normal file
View file

@ -0,0 +1,81 @@
<?php
namespace brain\api\v1;
use brain\data\Auth;
use brain\data\Session;
class AuthAPI
{
public function __construct()
{
}
public static function status()
{
$result = [];
//internal check for admin action
if (Auth::status()) {
$result = [
'message' => 'Authorized',
'type' => 'apiUseAuthorized',
'token' => Session::get('token'),
];
} else {
$result = [
'message' => 'Not Authorized',
'type' => 'apiUseNotAuthorized',
];
}
return $result;
}
public static function login($body)
{
$result = [];
switch (Auth::login($body)) {
case 'no_name':
$result = [
'message' => 'Need to see some id, champ',
'type' => 'requestLame',
];
break;
case 'bad_pass':
$result = [
'message' => 'Check your password, sport',
'type' => 'requestLame',
];
break;
default:
$result = [
'message' => 'Welcome back',
'type' => 'requestGood',
];
break;
}
return $result;
}
public static function logout($body)
{
Auth::logout($body);
$result = [
'message' => 'Till next time, g.',
'type' => 'TASK_LOGOUT',
];
return $result;
}
public static function requestSecret($body)
{
$result = Auth::findSecret($body);
return $result;
}
public static function resetPassword($body)
{
$result = Auth::makeNewPassword($body);
return $result;
}
}

73
brain/api/v1/FilesAPI.php Normal file
View file

@ -0,0 +1,73 @@
<?php
namespace brain\api\v1;
use brain\utility\FileUploader;
use brain\data\Settings;
use brain\data\Member;
class FilesAPI
{
public function __construct()
{
}
public static function uploadFiles($request, $type = null)
{
$upload = $request->getUploadedFiles(); //grab uploaded files
$options = $request->getParsedBody();
$file = $upload['upload_files'][0]; //front end sends one by one for progress tracking, so grab first
$type = $file->getClientMediaType();
$filesPath = '';
$path = date('Y') . '/' . date('m');
$response = [];
switch ($type) {
case 'image/jpeg':
case 'image/png':
case 'image/gif':
case 'image/svg':
if (isset($options["source"])) {
if ($options["source"] == "avatar-upload") {
$filesPath = '/assets/images/user/' . $path . '/';
Member::updateData(
'avi',
$filesPath . $file->getClientFileName()
);
} else {
$filesPath = '/assets/images/user/' . $path . '/';
Settings::updateGlobalData(
'background',
$filesPath . '/' . $file->getClientFileName()
);
}
} else {
$filesPath = '/assets/images/blog/' . $path . '/';
}
break;
case 'video/mp4':
$filesPath = '/assets/video/blog/' . $path . '/';
break;
case 'audio/mpeg':
$filesPath = '/assets/sound/blog/' . $path . '/';
break;
case 'application/pdf':
case 'text/plain':
case 'text/rtf':
$filesPath = '/assets/docs/blog/' . $path . '/';
break;
}
FileUploader::uploadFile('../public' . $filesPath, $file);
$response = [
'message' => "File Uploaded. Great!",
"filePath" => $filesPath . urlencode($file->getClientFileName()),
"fileName" => urlencode($file->getClientFileName()),
'type' => $type,
];
return $response;
}
}

View file

@ -1,76 +0,0 @@
<?php
class ImagesAPI
{
public function __construct()
{
}
public static function uploadImage($request, $type = null)
{
$file = $request->getUploadedFiles();
$uploadPath = "";
$path = date("Y") . "/" . date("m");
$response = [];
switch ($type) {
case "avatar":
$image = $file["avatar_upload"];
$uploadPath = "../public/assets/images/user/" . $path;
break;
case "background":
$image = $file["background_upload"];
$uploadPath = "../public/assets/images/user/" . $path;
break;
default:
$image = $file["post_image"];
$path = date("Y") . "/" . date("m");
$uploadPath = "../public/assets/images/blog/" . $path;
break;
}
$result = FileUploader::uploadFile($uploadPath, $image);
switch ($type) {
case "avatar":
$response = [
"message" => "Avatar Added. You look great!",
"type" => "avatarUploaded",
"url" =>
"/assets/images/user/" . $path . "/" . $image->getClientFileName(),
];
//update member data
Member::updateData(
"avi",
"/assets/images/user/" . $path . "/" . $image->getClientFileName()
);
break;
case "background":
$response = [
"message" => "Background plugged in. That's nice!",
"type" => "siteBackgroundUploaded",
"url" =>
"/assets/images/user/" . $path . "/" . $image->getClientFileName(),
];
//update settings file
Settings::updateGlobalData(
"background",
"/assets/images/user/" . $path . "/" . $image->getClientFileName()
);
break;
default:
$response = [
"message" => "Image Added. Very slick",
"type" => "postImageAdded",
"url" =>
"/assets/images/blog/" . $path . "/" . $image->getClientFileName(),
];
break;
}
return $response;
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace brain\api\v1;
use brain\data\Member;
use brain\data\Settings;
use brain\utility\FileUploader;
class ImagesAPI
{
public function __construct()
{
}
public static function uploadImage($request, $type = null)
{
$file = $request->getUploadedFiles();
$uploadPath = '';
$path = date('Y') . '/' . date('m');
$response = [];
switch ($type) {
case 'avatar':
$image = $file['avatar_upload'];
$uploadPath = '../public/assets/images/user/' . $path;
break;
case 'background':
$image = $file['background_upload'];
$uploadPath = '../public/assets/images/user/' . $path;
break;
default:
$image = $file['post_image'];
$path = date('Y') . '/' . date('m');
$uploadPath = '../public/assets/images/blog/' . $path;
break;
}
$result = FileUploader::uploadFile($uploadPath, $image);
switch ($type) {
case 'avatar':
$response = [
'message' => 'Avatar Added. You look great!',
'type' => 'avatarUploaded',
'url' => '/assets/images/user/' . $path . '/' . $image->getClientFileName(),
];
//update member data
Member::updateData(
'avi',
'/assets/images/user/' . $path . '/' . $image->getClientFileName()
);
break;
case 'background':
$response = [
'message' => "Background plugged in. That's nice!",
'type' => 'siteBackgroundUploaded',
'url' => '/assets/images/user/' . $path . '/' . $image->getClientFileName(),
];
//update settings file
Settings::updateGlobalData(
'background',
'/assets/images/user/' . $path . '/' . $image->getClientFileName()
);
break;
default:
$response = [
'message' => 'Image Added. Very slick',
'type' => 'postImageAdded',
'url' => '/assets/images/blog/' . $path . '/' . $image->getClientFileName(),
];
break;
}
return $response;
}
}

View file

@ -1,30 +0,0 @@
<?php
class InitAPI
{
public function __construct()
{
}
public static function handleInitTasks($task, $request)
{
//check if a site config already exists. if it does, deny set up request
//restore to previous version of site while a config exists is only accessible
//through settings.
if (Setup::status()) {
$result = ["type" => "blogInitFail", "message" => "Site already set up"];
} else {
switch ($task) {
case "init":
$result = Setup::init($request);
break;
case "restore":
$result = Setup::restore($request);
break;
}
}
return $result;
}
}

34
brain/api/v1/InitAPI.php Normal file
View file

@ -0,0 +1,34 @@
<?php
namespace brain\api\v1;
use brain\utility\Setup;
class InitAPI
{
public function __construct()
{
}
public static function handleInitTasks($task, $request)
{
//check if a site config already exists. if it does, deny set up request
//restore to previous version of site while a config exists is only accessible
//through settings.
if (Setup::status()) {
$result = ['type' => 'blogInitFail', 'message' => 'Site already set up'];
} else {
switch ($task) {
case 'init':
$result = Setup::init($request);
break;
case 'restore':
$result = Setup::restore($request);
break;
}
}
return $result;
}
}

View file

@ -1,26 +0,0 @@
<?php
class MailerAPI
{
public function __construct()
{
}
public static function handleMail($request, $body, $response)
{
//if testing, verify session is active
if ($body["mail_task"] == "TESTING") {
if (Session::active()) {
$result = Mailer::sendmail($body);
} else {
$result = [
"message" => "You need to be logged in for this, champ.",
"type" => "MAILER_ERROR",
];
}
} else {
}
return $result;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace brain\api\v1;
use brain\data\Session;
use brain\utility\Mailer;
class MailerAPI
{
public function __construct()
{
}
public static function handleMail($request, $body, $response)
{
// if testing, verify session is active
// add clean method for sending programmtic emails
if ($body['mail_task'] == 'TESTING') {
if (Session::active()) {
$result = Mailer::sendmail($body);
} else {
$result = [
'message' => 'You need to be logged in for this, champ.',
'type' => 'MAILER_ERROR',
];
}
} else {
}
return $result;
}
}

View file

@ -1,122 +0,0 @@
<?php
use function _\filter;
use Mni\FrontYAML\Parser;
class PagesAPI
{
public function __construct()
{
}
public static function getPageContent($request, $args)
{
$task = $args["fourth"];
$pages = (new Book("../content/pages"))->getContents();
$content = [];
foreach ($pages as $page) {
$entry = [
"id" => $page["id"],
"uuid" => $page["uuid"],
"title" => $page["title"],
"feature" => $page["feature"],
"path" => $page["path"],
"layout" => $page["layout"],
"tags" => $page["tags"],
"author" => $page["author"],
"created" => $page["created"],
"updated" => $page["updated"],
"deleted" => $page["deleted"],
"menu" => $page["menu"],
"featured" => $page["featured"],
"published" => $page["published"],
"slug" => $page["slug"],
"content" => StringTools::sanitizeContent($page["content"]),
];
array_push($content, $entry);
}
switch ($task) {
case "published":
$published = filter($content, function ($item) {
return $item["published"] == true && $item["deleted"] == false;
});
$result = ["pages" => $published, "totalItems" => count($published)];
break;
case "featured":
$featured = filter($content, function ($item) {
return $item["featured"] == true && $item["deleted"] == false;
});
$result = [
"pages" => $featured,
"totalItems" => count($featured),
];
break;
case "menu":
$menu = filter($content, function ($item) {
return $item["menu"] == true && $item["deleted"] == false;
});
$result = ["pages" => $menu, "totalItems" => count($menu)];
break;
case "single":
$uuid = $args["fifth"];
$page = (new Book("../content/pages"))->findPageById($uuid);
$entry = [
"id" => $page["id"],
"uuid" => $page["uuid"],
"title" => $page["title"],
"feature" => $page["feature"],
"path" => $page["path"],
"layout" => $page["layout"],
"tags" => $page["tags"],
"author" => $page["author"],
"created" => $page["created"],
"updated" => $page["updated"],
"deleted" => $page["deleted"],
"menu" => $page["menu"],
"featured" => $page["featured"],
"published" => $page["published"],
"slug" => $page["slug"],
"content" => StringTools::sanitizeContent($page["content"]),
];
$result = $entry;
break;
case "tags":
$result = Settings::getTags();
break;
default:
$result = [
"message" => "Hm, no task. That's unfortunate",
"type" => "TASK_NONE",
];
break;
}
return $result;
}
public static function handlePageTask($request, $args)
{
$task = $args["fourth"];
switch ($task) {
case "delete":
case "create":
case "write":
$result = (new Book("../content/pages"))->editPage($task, $request);
break;
case "add-entry-image":
$result = ImagesAPI::uploadImage($request);
break;
default:
$result = [
"message" => "Hm, no task. That's unfortunate",
"type" => "TASK_NONE",
];
break;
}
return $result;
}
}

179
brain/api/v1/PagesAPI.php Normal file
View file

@ -0,0 +1,179 @@
<?php
namespace brain\api\v1;
use brain\data\Book;
use brain\data\Settings;
use brain\data\Session;
use brain\utility\StringTools;
use function _\filter;
class PagesAPI
{
public function __construct()
{
}
public static function getPageContent($request, $args)
{
$task = $args['fourth'];
$pages = (new Book('../content/pages'))->getContents();
$content = [];
foreach ($pages as $page) {
$entry = [
'id' => $page['id'],
'uuid' => $page['uuid'],
'title' => $page['title'],
'feature' => $page['feature'],
'path' => $page['path'],
'layout' => $page['layout'],
'tags' => $page['tags'],
'author' => $page['author'],
'created' => $page['created'],
'updated' => $page['updated'],
'deleted' => $page['deleted'],
'menu' => $page['menu'],
'featured' => $page['featured'],
'published' => $page['published'],
'slug' => $page['slug'],
'content' => StringTools::sanitizeContent($page['content']),
];
array_push($content, $entry);
}
switch ($task) {
case 'published':
$published = filter($content, function ($item) {
return $item['published'] == true && $item['deleted'] == false;
});
$result = ['pages' => $published, 'totalItems' => count($published)];
break;
case 'featured':
$featured = filter($content, function ($item) {
return $item['featured'] == true && $item['deleted'] == false;
});
$result = [
'pages' => $featured,
'totalItems' => count($featured),
];
break;
case 'menu':
$menu = filter($content, function ($item) {
return $item['menu'] == true && $item['deleted'] == false;
});
$result = ['pages' => $menu, 'totalItems' => count($menu)];
break;
case 'single':
$uuid = $args['fifth'];
$page = (new Book('../content/pages'))->findPageById($uuid);
$entry = [
'id' => $page['id'],
'uuid' => $page['uuid'],
'title' => $page['title'],
'feature' => $page['feature'],
'path' => $page['path'],
'layout' => $page['layout'],
'tags' => $page['tags'],
'author' => $page['author'],
'created' => $page['created'],
'updated' => $page['updated'],
'deleted' => $page['deleted'],
'menu' => $page['menu'],
'featured' => $page['featured'],
'published' => $page['published'],
'slug' => $page['slug'],
'content' => StringTools::sanitizeContent($page['content']),
];
$result = $entry;
break;
case 'tags':
$result = Settings::getTags();
break;
default:
$result = [
'message' => "Hm, no task. That's unfortunate",
'type' => 'TASK_NONE',
];
break;
}
return $result;
}
public static function handlePageTask($request, $args)
{
$task = $args['fourth'];
switch ($task) {
case 'delete':
case 'create':
case 'write':
$body = json_decode(file_get_contents("php://input"), true);
$passed = true;
if (!isset($body['form_token'])) {
$result = [
'message' => 'No form token. Not good, sport.',
'type' => 'TASK_FORM_AUTH',
];
} else {
if ($body['form_token'] == Session::get('form_token')) {
$keys = [
'id',
'uuid',
'layout',
'current_title',
'content',
'title',
'created',
'slug',
'tags',
'menu',
'featured',
'published',
'form_token',
'imageList',
"fileList",
"remote"
];
foreach ($body as $key => $item) {
if (!in_array($key, $keys)) {
//found unnecessary key, so reject submission
var_dump($key);
$passed = false;
}
}
if ($passed) {
$result = (new Book())->editPage($task, $request);
} else {
$result = [
'message' => 'Unneccessary key found. Post not authorized, slick.',
'type' => 'TASK_FORM_AUTH',
];
}
} else {
$result = [
'message' => 'Form token, auth failed. Uh oh.',
'type' => 'TASK_FORM_AUTH',
];
}
}
break;
case 'add-entry-image':
$result = ImagesAPI::uploadImage($request);
break;
default:
$result = [
'message' => "Hm, no task. That's unfortunate",
'type' => 'TASK_NONE',
];
break;
}
return $result;
}
}

View file

@ -1,63 +0,0 @@
<?php
use Slim\Views\Twig;
class SettingsAPI
{
public function __construct()
{
}
public static function handleSettingsTask($request, $args, $body = null)
{
$task = $args["fourth"];
switch ($task) {
case "publish":
$render = new Render();
$render->renderTags();
$render->renderArchive();
$render->renderPages();
$result = [
"message" => "Site Rendered. GOOD EFFORT",
"type" => "TASK_NONE",
];
break;
case "add-avatar":
$result = ImagesAPI::uploadImage($request, "avatar");
break;
case "add-feature-background":
$result = ImagesAPI::uploadImage($request, "background");
break;
case "sync":
Settings::sync($body);
$result = [
"message" => "Settings Synced. You're doing great!",
"type" => "settingsUpdated",
];
break;
case "nav-sync":
Settings::navSync($body);
$result = [
"message" => "Navigation updated. Very slick!",
"type" => "menuUpdated",
];
break;
default:
$result = [
"message" => "Hm, no task. That's unfortunate",
"type" => "TASK_NONE",
];
break;
}
return $result;
}
public static function createBackup()
{
$result = Maintenance::makeBackup();
return $result;
}
}

View file

@ -0,0 +1,153 @@
<?php
namespace brain\api\v1;
use brain\data\Render;
use brain\data\Settings;
use brain\data\Session;
use brain\utility\Maintenance;
class SettingsAPI
{
public function __construct()
{
}
public static function handleSettingsTask($request, $args, $body = null)
{
$task = $args['fourth'];
switch ($task) {
case 'publish':
//check settings to see if site is a one pager
$config = new Settings();
$settings = $config->getSettings();
$theme = $settings['global']['theme'];
$themeConfig = json_decode(
file_get_contents('../content/themes/' . $theme . '/theme.json'),
true
);
//check to see if dynamic rendering is active
if (
isset($settings['global']['dynamicRender']) &&
$settings['global']['dynamicRender'] === 'true'
) {
$result = [
'message' => "Dynamic Render Active! You're good!",
'type' => 'RENDER_SUCCESS',
];
} else {
$render = new Render();
if (isset($themeConfig['render'])) {
//rendering for one page sites
if (!$themeConfig['render'] || $themeConfig['render'] === 'false') {
$render->renderIndex();
$result = [
'message' => 'Index Rendered. HAND CLAPS',
'type' => 'RENDER_SUCCESS',
];
} else {
$render->renderTags();
$render->renderArchive();
$render->renderPages();
$result = [
'message' => 'Site Rendered. GOOD EFFORT',
'type' => 'RENDER_SUCCESS',
];
}
} else {
// just incase the render flag is missing
$render->renderTags();
$render->renderArchive();
$render->renderPages();
$result = [
'message' => 'Site Rendered. GOOD EFFORT',
'type' => 'RENDER_SUCCESS',
];
}
}
//if render flag is set and false, just render index page for one page sites
//otherwise, render all pages according to theme template files
break;
case 'add-avatar':
$result = ImagesAPI::uploadImage($request, 'avatar');
break;
case 'add-feature-background':
$result = ImagesAPI::uploadImage($request, 'background');
break;
case 'sync':
Settings::sync($body);
$result = [
'message' => "Settings Synced. You're doing great!",
'type' => 'settingsUpdated',
];
break;
case 'nav-sync':
Settings::navSync($body);
$result = [
'message' => 'Navigation updated. Very slick!',
'type' => 'menuUpdated',
];
break;
default:
$result = [
'message' => "Hm, no task. That's unfortunate",
'type' => 'TASK_NONE',
];
break;
}
return $result;
}
public static function getInfo($request, $args)
{
$task = $args['fourth'];
switch ($task) {
case 'site':
$config = new Settings();
$settings = $config->getSettings();
$data = [
'title' => $settings['global']['title'],
'base_url' => $settings['global']['base_url'],
'description' => $settings['global']['descriptions'],
];
$result = [
'message' => 'Settings Found',
'type' => 'GET_SETTINGS',
'data' => $data,
];
break;
case 'member':
if (Session::active()) {
$member = $member = Session::get('member');
$data = ['handle' => $member['handle'], 'email' => $member['email']];
$result = [
'message' => 'Member Info Found',
'type' => 'GET_MEMBER_INFO',
'data' => $data,
];
} else {
$result = [
'message' => "Not logged in. C'mon, bruh",
'type' => 'TASK_NONE',
];
}
break;
default:
$result = [
'message' => 'No Settings found. Frowny Face',
'type' => 'TASK_NONE',
];
break;
}
return $result;
}
public static function createBackup()
{
$result = Maintenance::makeBackup();
return $result;
}
}

View file

@ -1,204 +0,0 @@
<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
include "../brain/api/v1/AuthAPI.inc.php";
include "../brain/api/v1/ImagesAPI.inc.php";
include "../brain/api/v1/PagesAPI.inc.php";
include "../brain/api/v1/SettingsAPI.inc.php";
include "../brain/api/v1/InitAPI.inc.php";
include "../brain/api/v1/MailerAPI.inc.php";
class APIControl
{
public static function get(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
$filename = "";
switch (isset($args["third"]) ? $args["third"] : "none") {
case "status":
if (Member::verifyKey($_GET["key"])) {
$result = AuthAPI::status();
} else {
$result = [
"message" => "Valid key required. API access denied, homie",
"type" => "API_ERROR",
];
}
break;
case "page":
//echo
if (Member::verifyKey($_GET["key"])) {
$result = PagesAPI::getPageContent($request, $args);
} else {
$result = [
"message" => "API access denied, homie",
"type" => "API_ERROR",
];
}
break;
case "files":
if (Session::active()) {
if ($args["third"] == "backup") {
$filename = "../config/backups/latest_backup.zip";
if (file_exists($filename)) {
header("Content-Type: application/zip");
header(
'Content-Disposition: attachment; filename="' .
basename($filename) .
'"'
);
header("Content-Length: " . filesize($filename));
flush();
// return readfile($filename);
//readfile($filename);
// delete file
//unlink($filename);
}
}
} else {
$result = [
"message" => "API access denied, homie",
"type" => "API_ERROR",
];
}
default:
break;
}
$freshResponse = $response;
if ($args["third"] == "files") {
$freshResponse
->getBody()
->write(file_get_contents("../config/backups/latest_back.zip"));
$freshResponse->withHeader("Content-Type", "application/zip");
return $freshResponse->withAddedHeader(
"Content-Disposition",
"attachment; filename=latest_backup.zip"
);
} else {
$response->getBody()->write(json_encode($result));
return $response->withHeader("Content-Type", "application/json");
}
}
public static function post(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
$contentType = $request->getHeader("Content-Type");
switch ($contentType[0]) {
case "application/json":
$body = json_decode(file_get_contents("php://input"), true);
break;
default:
break;
}
switch (isset($args["third"]) ? $args["third"] : "none") {
case "restore": //move to 'api/auth'
case "init": //move to 'api/auth'
$task = $args["third"];
$result = InitApi::handleInitTasks(
$task,
$task == "init" ? $body : $request
);
break;
case "backup": //move to 'api/auth'
$token = $request->getHeader("fipamo-access-token");
//Verify token for admin tasks
if (Session::verifyToken($token[0])) {
$result = SettingsAPI::createBackup();
} else {
$result = [
"message" => "API access denied, homie",
"type" => "API_ERROR",
];
}
break;
case "login": //move to 'api/auth'
//check if request is remote and if so, verify token
if ($body["remote"] || $body["remote"] == "true") {
if (Member::verifyKey($body["key"])) {
$result = AuthAPI::login($body);
} else {
$result = [
"message" => "API access denied, homie",
"type" => "API_ERROR",
];
}
} else {
//request is local, so it's cool
$result = AuthAPI::login($body);
}
break;
case "logout": //move to 'api/auth'
$result = AuthAPI::logout($body);
break;
case "get-secret": //move to 'api/auth'
$result = AuthAPI::requestSecret($body);
break;
case "reset-password": //move to 'api/auth'
$result = AuthAPI::resetPassword($body);
break;
case "page":
$token = $request->getHeader("fipamo-access-token");
//Verify token for admin tasks
if (isset($token[0])) {
if (Session::verifyToken($token[0])) {
$result = PagesAPI::handlePageTask($request, $args);
} else {
$result = [
"message" => "Invalid token, API access denied, homie",
"type" => "API_ERROR",
];
}
} else {
$result = [
"message" => "No token, API access denied, homie",
"type" => "API_ERROR",
];
}
break;
case "settings":
$token = $request->getHeader("fipamo-access-token");
if (Session::verifyToken($token[0])) {
if (isset($body)) {
$postBody = $body;
} else {
$postBody = null;
}
$result = SettingsAPI::handleSettingsTask($request, $args, $postBody);
} else {
$result = [
"message" => "API access denied, homie",
"type" => "API_ERROR",
];
}
break;
case "mailer":
$result = MailerAPI::handleMail($request, $body, $response);
break;
default:
$result = [
"message" => "Oh, nothing to do. That's unfortunate",
"type" => "TASK_NONE",
];
break;
}
$response->getBody()->write(json_encode($result));
return $response->withHeader("Content-Type", "application/json");
}
}

View file

@ -0,0 +1,250 @@
<?php
namespace brain\controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use brain\api\v1\AuthAPI;
use brain\api\v1\PagesAPI;
use brain\api\v1\FilesAPI;
use brain\api\v1\SettingsAPI;
use brain\api\v1\InitAPI;
use brain\api\v1\MailerAPI;
use brain\data\Member;
use brain\data\Session;
class APIControl
{
public static function get(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
$filename = '';
switch (isset($args['third']) ? $args['third'] : 'none') {
case 'status':
$result = AuthAPI::status();
break;
case 'page':
//echo
if (Member::verifyKey($_GET['key'])) {
$result = PagesAPI::getPageContent($request, $args);
} else {
$result = [
'message' => 'API access denied, homie',
'type' => 'API_ERROR',
];
}
break;
case 'settings':
$token = $request->getHeader('fipamo-access-token');
//Verify token to get site info
if (isset($token[0])) {
if (Session::verifyToken($token[0])) {
$result = SettingsAPI::getInfo($request, $args);
} else {
$result = [
'message' => 'Invalid token, API access denied, homie',
'type' => 'API_ERROR',
];
}
} else {
$result = [
'message' => 'No token, API access denied, homie',
'type' => 'API_ERROR',
];
}
break;
case 'files':
if (Session::active()) {
if ($args['third'] == 'backup') {
$filename = '../config/backups/latest_backup.zip';
if (file_exists($filename)) {
header('Content-Type: application/zip');
header(
'Content-Disposition: attachment; filename="' .
basename($filename) .
'"'
);
header('Content-Length: ' . filesize($filename));
flush();
// return readfile($filename);
//readfile($filename);
// delete file
//unlink($filename);
}
}
} else {
$result = [
'message' => 'API access denied, homie',
'type' => 'API_ERROR',
];
}
// no break
default:
break;
}
$freshResponse = $response;
if ($args['third'] == 'files') {
$freshResponse
->getBody()
->write(file_get_contents('../config/backups/latest_back.zip'));
$freshResponse->withHeader('Content-Type', 'application/zip');
return $freshResponse->withAddedHeader(
'Content-Disposition',
'attachment; filename=latest_backup.zip'
);
} else {
$response->getBody()->write(json_encode($result));
return $response->withHeader('Content-Type', 'application/json');
}
}
public static function post(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
$contentType = $request->getHeader('Content-Type');
switch ($contentType[0]) {
case 'application/json':
$body = json_decode(file_get_contents('php://input'), true);
break;
default:
break;
}
switch (isset($args['third']) ? $args['third'] : 'none') {
case 'restore': //move to 'api/auth'
case 'init': //move to 'api/auth'
$task = $args['third'];
$result = InitApi::handleInitTasks(
$task,
$task == 'init' ? $body : $request
);
break;
case 'backup': //move to 'api/auth'
$token = $request->getHeader('fipamo-access-token');
//Verify token for admin tasks
$result = SettingsAPI::createBackup();
/*
if (Session::verifyToken($token[0])) {
$result = SettingsAPI::createBackup();
} else {
$result = [
"message" => "API access denied, homie",
"type" => "API_ERROR",
];
}
*/
break;
case 'login': //move to 'api/auth'
//check if request is remote and if so, verify token
if ($body['remote'] || $body['remote'] == 'true') {
if (Member::verifyKey($body['key'])) {
$result = AuthAPI::login($body);
} else {
$result = [
'message' => 'API access denied, homie',
'type' => 'API_ERROR',
];
}
} else {
//request is local, so it's cool
$result = AuthAPI::login($body);
}
break;
case 'logout': //move to 'api/auth'
$result = AuthAPI::logout($body);
break;
case 'get-secret': //move to 'api/auth'
$result = AuthAPI::requestSecret($body);
break;
case 'reset-password': //move to 'api/auth'
$result = AuthAPI::resetPassword($body);
break;
case 'page':
$token = $request->getHeader('fipamo-access-token');
//Verify token for admin tasks
if (isset($token[0])) {
if (Session::verifyToken($token[0])) {
$result = PagesAPI::handlePageTask($request, $args);
} else {
$result = [
'message' => 'Invalid token, API access denied, homie',
'type' => 'API_ERROR',
];
}
} else {
$result = [
'message' => 'No token, API access denied, homie',
'type' => 'API_ERROR',
];
}
break;
case "files":
$token = $request->getHeader('fipamo-access-token');
if (isset($token[0])) {
if (Session::verifyToken($token[0])) {
$result = FilesAPI::uploadFiles($request, $args);
} else {
$result = [
'message' => 'Invalid token, API access denied, homie',
'type' => 'API_ERROR',
];
}
} else {
$result = [
'message' => 'No token, API access denied, homie',
'type' => 'API_ERROR',
];
}
break;
case 'settings':
if (isset($body)) {
$postBody = $body;
} else {
$postBody = null;
}
$task = $args['fourth'];
if ($task == 'add-feature-background' || $task == 'add-avatar') {
$result = SettingsAPI::handleSettingsTask($request, $args, $postBody);
} else {
$token = $request->getHeader('fipamo-access-token');
if (Session::verifyToken($token[0])) {
$result = SettingsAPI::handleSettingsTask(
$request,
$args,
$postBody
);
} else {
$result = [
'message' => 'API access denied, homie',
'type' => 'API_ERROR',
];
}
}
break;
case 'mailer':
$result = MailerAPI::handleMail($request, $body, $response);
break;
default:
$result = [
'message' => "Oh, nothing to do. That's unfortunate",
'type' => 'TASK_NONE',
];
break;
}
$response->getBody()->write(json_encode($result));
return $response->withHeader('Content-Type', 'application/json');
}
}

View file

@ -1,153 +0,0 @@
<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;
include "../brain/data/Book.inc.php";
class DashControl
{
public static function start(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
$view = Twig::fromRequest($request);
$pageOptions = [];
$template = "";
if (Setup::status()) {
switch (isset($args["second"]) ? $args["second"] : "index") {
case "settings":
if (Session::active()) {
$config = new Settings();
$settings = $config->getSettings();
$themes = $config->getThemes();
$template = "dash/settings.twig";
$member = Session::get("member");
$updated = new \Moment\Moment($settings["global"]["last_backup"]);
$pageOptions = [
"title" => "Dash Settings",
"private" => $settings["global"]["private"],
"render" => $settings["global"]["renderOnSave"],
"background" => $settings["global"]["background"],
"member" => $member,
"siteTitle" => $settings["global"]["title"],
"baseUrl" => $settings["global"]["base_url"],
"desc" => $settings["global"]["descriptions"],
"lastBackup" => $updated->format("Y M D d"),
"currentTheme" => $settings["global"]["theme"],
"themes" => $themes,
"apiStatus" => isset($settings["global"]["externalAPI"])
? $settings["global"]["externalAPI"]
: "false",
"mailOption" => $settings["email"]["active"],
"mailConfig" => $settings["email"],
"status" => Session::active(),
];
} else {
header("Location: /dashboard");
die();
}
break;
case "navigation":
if (Session::active()) {
$config = new Settings();
$settings = $config->getSettings();
$template = "dash/navigation.twig";
$pageOptions = [
"title" => "Edit Dash Navigation",
"status" => Session::active(),
"menu" => $settings["menu"],
];
} else {
header("Location: /dashboard");
die();
}
break;
case "pages":
if (Session::active()) {
$currentPage = isset($args["fourth"]) ? $args["fourth"] : 1;
$filter = isset($args["third"]) ? $args["third"] : "all";
$data = (new Book("../content/pages"))->getPages(
$currentPage,
4,
$filter
);
$template = "dash/book.twig";
$pageOptions = [
"entryCount" => $data["entryCount"],
"numOfPages" => $data["numOfPages"],
"currentPage" => $currentPage,
"filter" => $data["paginate"]["sort"],
"stats" => $data["stats"],
"pages" => $data["pages"],
"paginate" => $data["paginate"],
"status" => Session::active(),
];
} else {
header("Location: /dashboard");
die();
}
break;
case "page":
if (Session::active()) {
$template = "dash/page-edit.twig";
$mode = $args["third"];
if ($mode == "edit") {
$uuid = $args["fourth"];
$pageOptions = [
"title" => "Fipamo | Edit Page",
"page" => (new Book("../content/pages"))->findPageById($uuid),
"mode" => $mode,
"status" => Session::active(),
];
} else {
$pageOptions = [
"title" => "Fipamo | Create Page",
"mode" => $mode,
"status" => Session::active(),
];
}
} else {
header("Location: /dashboard");
die();
}
break;
case "logout":
Session::kill();
header("Location: /dashboard");
die();
break;
case "reset-password":
$template = "dash/reset-password.twig";
$pageOptions = [
"title" => "Reset Password",
];
break;
default:
$template = "dash/start.twig";
if (Session::active()) {
$pageOptions = [
"title" => "Welcome Back",
"status" => Session::active(),
"data" => (new Book("../content/pages"))->getPages(1, 4),
];
} else {
$pageOptions = [
"title" => "Welcome to Fipamo",
"status" => Session::active(),
];
}
break;
}
} else {
$template = "dash/init.twig";
$pageOptions = ["title" => "Fipamo Setup"];
}
return $view->render($response, $template, $pageOptions);
}
}

View file

@ -0,0 +1,226 @@
<?php
namespace brain\controller;
use brain\data\Book;
use brain\data\Session;
use brain\data\Settings;
use brain\data\Themes;
use brain\utility\Setup;
use brain\utility\Sorting;
use Carbon\Carbon;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;
class DashControl
{
public static function start(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
$view = Twig::fromRequest($request);
$pageOptions = [];
$template = '';
if (Setup::status()) {
switch (isset($args['second']) ? $args['second'] : 'index') {
case 'settings':
if (Session::active()) {
$config = new Settings();
$settings = $config->getSettings();
$themes = (new Themes())->getThemes(); // $config->getThemes();
$template = 'dash/settings.twig';
$member = Session::get('member');
$form_token = Session::get('form_token');
$updated = new Carbon($settings['global']['last_backup']);
$pageOptions = [
'title' => 'Settings',
'private' => $settings['global']['private'],
'renderOnSave' => $settings['global']['renderOnSave'],
'background' => $settings['global']['background'],
'member' => $member,
'ftoken' => $form_token,
'siteTitle' => $settings['global']['title'],
'baseUrl' => $settings['global']['base_url'],
'desc' => $settings['global']['descriptions'],
'lastBackup' => $updated->format('Y M D d'),
'currentTheme' => $settings['global']['theme'],
'themes' => $themes,
'apiStatus' => isset($settings['global']['externalAPI'])
? $settings['global']['externalAPI']
: 'false',
'dynamicRenderStatus' => isset(
$settings['global']['dynamicRender']
)
? $settings['global']['dynamicRender']
: 'false',
'mailOption' => $settings['email']['active'],
'mailConfig' => $settings['email'],
'status' => Session::active(),
];
} else {
header('Location: /dashboard');
exit();
}
break;
case 'navigation':
if (Session::active()) {
$config = new Settings();
$settings = $config->getSettings();
$template = 'dash/navigation.twig';
$pageOptions = [
'title' => 'Edit Menu',
'status' => Session::active(),
'menu' => $settings['menu'],
];
} else {
header('Location: /dashboard');
exit();
}
break;
case 'pages':
if (Session::active()) {
$currentPage = isset($args['fourth']) ? $args['fourth'] : 1;
$filter = isset($args['third']) ? $args['third'] : 'all';
$data = (new Book())->getPages($currentPage, 4, $filter);
$template = 'dash/book.twig';
$pageOptions = [
'title' => 'Contents',
'entryCount' => $data['entryCount'],
'numOfPages' => $data['numOfPages'],
'currentPage' => $currentPage,
'filter' => $data['paginate']['sort'],
'stats' => $data['stats'],
'pages' => $data['pages'],
'paginate' => $data['paginate'],
'status' => Session::active(),
];
} else {
header('Location: /dashboard');
exit();
}
break;
case 'page':
if (Session::active()) {
$template = 'dash/page-edit.twig';
$mode = $args['third'];
$uuid = $args['fourth'];
switch ($mode) {
case 'edit':
$page = (new Book())->findPageById($uuid);
$views = [];
if (!isset($page['layout'])) {
$page['layout'] = "page";
}
if (str_contains($page['layout'], 'index')) {
$views = (new Themes())->getCustomIndex();
} else {
$views = (new Themes())->getCustomViews();
}
$imageList = [];
$fileList = [];
if (isset($page['feature'])) {
$imageList = explode(',', $page['feature']);
}
if (isset($page['files'])) {
$fileList = explode(',', $page['files']);
}
$images = [];
$files = [];
foreach ($imageList as $item) {
$image = trim($item);
if (!empty($image)) {
array_push($images, $image);
}
}
foreach ($fileList as $item) {
$file = trim($item);
if (!empty($file)) {
array_push($files, $file);
}
}
$pageOptions = [
'title' => $page['title'],
'page' => $page,
'mode' => $mode,
'token' => Session::get('form_token'),
'status' => Session::active(),
'images' => $images,
'files' => $files,
'views' => $views,
];
break;
case 'preview':
$config = new Settings();
$settings = $config->getSettings();
$loader = new \Twig\Loader\FilesystemLoader(
'../content/themes/' . $settings['global']['theme'] .
'/'
);
$display = new \Twig\Environment($loader, []);
$book = new Book();
$page = $book->findPageById($uuid);
$pageOptions = Sorting::page($page);
$preview = $page['layout'] .
'.twig';
$html = $display->render($preview, $pageOptions);
$response->getBody()->write($html);
return $response;
break;
default:
$pageOptions = [
'title' => 'Fipamo | Create Page',
'token' => Session::get('form_token'),
'mode' => $mode,
'status' => Session::active(),
];
break;
}
} else {
header('Location: /dashboard');
exit();
}
break;
case 'logout':
Session::kill();
header('Location: /dashboard');
exit();
break;
case 'reset-password':
$template = 'dash/reset-password.twig';
$pageOptions = [
'title' => 'Reset Password',
];
break;
default:
$template = 'dash/start.twig';
if (Session::active()) {
$pageOptions = [
'title' => 'Start',
'status' => Session::active(),
'data' => (new Book())->getPages(1, 4),
];
} else {
$pageOptions = [
'title' => 'Welcome to Fipamo',
'status' => Session::active(),
];
}
break;
}
} else {
$template = 'dash/init.twig';
$pageOptions = ['title' => 'Fipamo Setup'];
}
return $view->render($response, $template, $pageOptions);
}
}

View file

@ -1,19 +0,0 @@
<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;
class IndexControl
{
public static function start(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
//unset($_SESSION);
$view = Twig::fromRequest($request);
$html = file_get_contents("../public/index.html");
$response->getBody()->write($html);
return $response;
}
}

View file

@ -0,0 +1,110 @@
<?php
namespace brain\controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;
use brain\data\Settings;
use brain\utility\Sorting;
use function _\find;
class IndexControl
{
public static function start(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
//unset($_SESSION);
$config = new Settings();
$settings = $config->getSettings();
$view = Twig::fromRequest($request);
//checks dynamic render flag for site render status
if ($settings['global']['dynamicRender']) {
if ($settings['global']['dynamicRender'] == 'true') {
$loader = new \Twig\Loader\FilesystemLoader('../content/themes');
$display = new \Twig\Environment($loader, []);
$template = '';
$pageOptions = [];
$pageInfo = [
'keywords' => isset($settings['global']['keywords'])
? $settings['global']['keywords']
: 'fipamo, blog, jamstack, php, markdown, js',
'description' => $settings['global']['descriptions'],
'image' => $settings['global']['base_url'] . $settings['global']['background'],
'baseURL' => $settings['global']['base_url'],
];
if (isset($args['first'])) {
switch ($args['first']) {
case 'tags':
$template = $settings['global']['theme'] . '/tags.twig';
$tag = trim($args['second']);
$taglist = Sorting::tags();
$item = find($taglist, ['tag_name' => $tag]);
$pageOptions = [
'title' => 'Pages Tagged as ' . $item['tag_name'],
'background' => $pageInfo['image'],
'tag_list' => $item['pages'],
'info' => $pageInfo,
'menu' => $settings['menu'],
'dynamicRender' => $settings['global']['dynamicRender'],
];
break;
case 'archives':
$archive = Sorting::archive();
$template = $settings['global']['theme'] . '/archive.twig';
$pageOptions = [
'title' => 'Archive',
'background' => $pageInfo['image'],
'archives' => $archive,
'info' => $pageInfo,
'menu' => $settings['menu'],
'dynamicRender' => $settings['global']['dynamicRender'],
];
break;
default:
//check if page is a menu item, if not render along path as usual
$page = [];
$book = new Book();
if (is_numeric($args['first'])) {
$page = $book->findPageBySlug($args['third']);
} else {
$page = $book->findPageBySlug($args['first']);
}
$template = $settings['global']['theme'] . '/' . $page['layout'] . '.twig';
$pageOptions = Sorting::page($page);
break;
}
} else {
//index
$template = $settings['global']['theme'] . '/' . $page['layout'] . '.twig';
$book = new Book('');
$page = $book->findPageBySlug();
$pageOptions = Sorting::page($page);
}
$html = $display->render($template, $pageOptions);
$response->getBody()->write($html);
return $response;
} else {
//if dynamic flag is false, load up html
$view = Twig::fromRequest($request);
$html = file_get_contents('../public/index.html');
$response->getBody()->write($html);
return $response;
}
} else {
//if flag is not present, default to static html
$view = Twig::fromRequest($request);
$html = file_get_contents('../public/index.html');
$response->getBody()->write($html);
return $response;
}
}
}

View file

@ -1,44 +0,0 @@
<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
include "../brain/controller/IndexControl.inc.php";
include "../brain/controller/DashControl.inc.php";
include "../brain/controller/APIControl.inc.php";
class RouteControl
{
public function get(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
switch (isset($args["first"]) ? $args["first"] : "index") {
case "dashboard":
return DashControl::start($request, $response, $args);
break;
case "api":
return APIControl::get($request, $response, $args);
break;
default:
return IndexControl::start($request, $response, $args);
break;
}
}
public function post(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
switch (isset($args["first"]) ? $args["first"] : "index") {
case "api":
return APIControl::post($request, $response, $args);
break;
default:
//echo "YES";
//return IndexControl::start($request, $response, $args);
break;
}
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace brain\controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class RouteControl
{
//TODO: Add additional HTTP Methods to better organize API control paths
public function get(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
switch (isset($args['first']) ? $args['first'] : 'index') {
case 'dashboard':
return DashControl::start($request, $response, $args);
break;
case 'api':
return APIControl::get($request, $response, $args);
break;
default:
return IndexControl::start($request, $response, $args);
break;
}
}
public function post(
ServerRequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
switch (isset($args['first']) ? $args['first'] : 'index') {
case 'api':
return APIControl::post($request, $response, $args);
break;
default:
$result = [
'message' => "Nothing matches this route. That's unfortunate",
'type' => 'TASK_NONE',
];
$response->getBody()->write(json_encode($result));
return $response->withHeader('Content-Type', 'application/json');
}
}
}

View file

@ -1,151 +0,0 @@
<?php
use function _\find;
use ReallySimpleJWT\Token;
class Auth
{
public function __construct()
{
}
public static function sessionStatus()
{
if (isset($_SESSION["member"])) {
return true;
} else {
return false;
}
//return $this->secret;
}
public static function status()
{
$result = "";
if (Session::active()) {
$result = true;
} else {
$result = false;
}
return $result;
}
public static function login($who)
{
//grab member list
$folks = (new Settings())->getFolks();
$found = find($folks, ["handle" => $who["handle"]]);
if ($found) {
//name is found, verify password
if (password_verify($who["password"], $found["password"])) {
$member = [
"handle" => $found["handle"],
"email" => $found["email"],
"role" => $found["role"],
"avatar" => $found["avi"],
"key" => $found["key"],
];
$token = Token::create(
$found["key"],
$found["secret"],
time() + 3600,
"localhost"
); //expires in an hour
Session::start();
Session::set("member", $member);
Session::set("token", $token);
$result = "good_login";
} else {
$result = "bad_pass";
}
} else {
//if name is not found
$result = "no_name";
}
return $result;
}
public static function findSecret($data)
{
$result = [];
$folks = (new Settings())->getFolks();
if (
!empty($data["email"]) &&
filter_var($data["email"], FILTER_VALIDATE_EMAIL)
) {
$found = find($folks, ["email" => $data["email"]]);
if ($found) {
//if email is cool, check mail relay status
//if set up, send secret there, if not just return it
$config = new Settings();
$settings = $config->getSettings();
$email = $settings["email"]["active"];
if ($email != "option-none") {
$data["mail_task"] = "SEND_SECRET";
$data["secret"] = $found["secret"];
$result = Mailer::sendmail($data);
} else {
$result = [
"message" => "Valid email, but no email set up!",
"type" => "secretFound",
"secret" => $found["secret"],
];
}
} else {
$result = [
"message" => "No valid email, no goodies, pleighboi",
"type" => "secretNotFound",
];
}
} else {
$result = [
"message" => "Aye, this address is not right, slick.",
"type" => "secretNotFound",
];
}
return $result;
}
public static function makeNewPassword($data)
{
//check if passwordsmatch
if ($data["newPass"] == $data["newPassConfirm"]) {
//verify secret
$folks = (new Settings())->getFolks();
$found = find($folks, ["secret" => $data["secret"]]);
if ($found) {
//create new pass and secret key, then update file
$hash = password_hash($data["newPass"], PASSWORD_DEFAULT);
$freshSecret = StringTools::randomString(12);
Member::updateData("password", $hash, $data["secret"]);
Member::updateData("secret", $freshSecret, $data["secret"]);
$result = [
"message" => "Password Updated. Very nice!",
"type" => "passCreated",
];
} else {
$result = [
"message" => "Secret key is invalid. Try to retrieve it again",
"type" => "passNotCreated",
];
}
} else {
$result = [
"message" => "Passwords don't match. Try it again.",
"type" => "passNotCreated",
];
}
return $result;
}
public static function logout()
{
Session::kill();
}
}

154
brain/data/Auth.php Normal file
View file

@ -0,0 +1,154 @@
<?php
namespace brain\data;
use ReallySimpleJWT\Token;
use function _\find;
class Auth
{
public function __construct()
{
}
public static function sessionStatus()
{
if (isset($_SESSION['member'])) {
return true;
} else {
return false;
}
//return $this->secret;
}
public static function status()
{
$result = '';
if (Session::active()) {
$result = true;
} else {
$result = false;
}
return $result;
}
public static function login($who)
{
//grab member list
$folks = (new Settings())->getFolks();
$found = find($folks, ['handle' => $who['handle']]);
if ($found) {
//name is found, verify password
if (password_verify($who['password'], $found['password'])) {
$member = [
'handle' => $found['handle'],
'email' => $found['email'],
'role' => $found['role'],
'avatar' => $found['avi'],
'key' => $found['key'],
];
$token = Token::create(
$found['key'],
$found['secret'],
time() + 3600,
'localhost'
); //expires in an hour
$form_token = md5(uniqid(microtime(), true));
Session::start();
Session::set('member', $member);
Session::set('token', $token);
Session::set('form_token', $form_token);
$result = 'good_login';
} else {
$result = 'bad_pass';
}
} else {
//if name is not found
$result = 'no_name';
}
return $result;
}
public static function findSecret($data)
{
$result = [];
$folks = (new Settings())->getFolks();
if (!empty($data['email']) && filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$found = find($folks, ['email' => $data['email']]);
if ($found) {
//if email is cool, check mail relay status
//if set up, send secret there, if not just return it
$config = new Settings();
$settings = $config->getSettings();
$email = $settings['email']['active'];
if ($email != 'option-none') {
$data['mail_task'] = 'SEND_SECRET';
$data['secret'] = $found['secret'];
$result = Mailer::sendmail($data);
} else {
$result = [
'message' => 'Valid email, but no email set up!',
'type' => 'secretFound',
'secret' => $found['secret'],
];
}
} else {
$result = [
'message' => 'No valid email, no goodies, pleighboi',
'type' => 'secretNotFound',
];
}
} else {
$result = [
'message' => 'Aye, this address is not right, slick.',
'type' => 'secretNotFound',
];
}
return $result;
}
public static function makeNewPassword($data)
{
//check if passwordsmatch
if ($data['newPass'] == $data['newPassConfirm']) {
//verify secret
$folks = (new Settings())->getFolks();
$found = find($folks, ['secret' => $data['secret']]);
if ($found) {
//create new pass and secret key, then update file
$hash = password_hash($data['newPass'], PASSWORD_DEFAULT);
$freshSecret = StringTools::randomString(12);
Member::updateData('password', $hash, $data['secret']);
Member::updateData('secret', $freshSecret, $data['secret']);
$result = [
'message' => 'Password Updated. Very nice!',
'type' => 'passCreated',
];
} else {
$result = [
'message' => 'Secret key is invalid. Try to retrieve it again',
'type' => 'passNotCreated',
];
}
} else {
$result = [
'message' => "Passwords don't match. Try it again.",
'type' => 'passNotCreated',
];
}
return $result;
}
public static function logout()
{
Session::kill();
}
}

View file

@ -1,290 +0,0 @@
<?php
use Mni\FrontYAML\Parser;
use function _\orderBy;
use function _\filter;
use function _\find;
class Book
{
public $files = [];
public function __construct($folder)
{
$this->read($folder);
}
public function read($folder)
{
$folders = glob("$folder/*", GLOB_ONLYDIR);
foreach ($folders as $folder) {
//$this->files[] = $folder . "/";
$this->read($folder);
}
$files = array_filter(glob("$folder/*md"), "is_file");
foreach ($files as $file) {
$this->files[] = $file;
}
}
public function findPageById(string $uuid)
{
$content = $this->getContents();
$page = find($content, ["uuid" => $uuid]);
return $page;
}
public function editPage($task, $request)
{
$content = $this->getContents();
if ($task == "delete") {
$parsed = json_decode(file_get_contents("php://input"), true);
$body = find($content, ["uuid" => $parsed["id"]]);
} else {
$body = $request->getParsedBody();
}
$page = find($content, ["uuid" => $body["uuid"]]);
$image = $request->getUploadedFiles();
$member = Session::get("member");
if ($task != "create") {
$path =
date("Y", date($page["rawCreated"])) .
"/" .
date("m", date($page["rawCreated"]));
} else {
$path = date("Y") . "/" . date("m");
}
if (isset($image["feature_image"])) {
if ($task != "create") {
$feature =
"/assets/images/blog/" .
$path .
"/" .
$image["feature_image"]->getClientFileName();
} else {
$feature =
"/assets/images/blog/" .
$path .
"/" .
$image["feature_image"]->getClientFileName();
}
FileUploader::uploadFile(
"../public/assets/images/blog/" . $path . "/",
$image["feature_image"]
);
} else {
if (isset($body["feature_image"])) {
$url = explode("/", $body["feature_image"]);
$feature =
"/" .
$url[3] .
"/" .
$url[4] .
"/" .
$url[5] .
"/" .
$url[6] .
"/" .
$url[7] .
"/" .
$url[8];
} else {
$task == "create" ? ($feature = "") : ($feature = $body["feature"]);
}
}
if ($task == "delete") {
$deleted = "true";
$body["menu"] ? ($body["menu"] = "true") : ($body["menu"] = "false");
$body["published"]
? ($body["published"] = "true")
: ($body["published"] = "false");
$body["featured"]
? ($body["featured"] = "true")
: ($body["featured"] = "false");
} else {
$deleted = isset($page["deleted"]) ? $page["deleted"] : "false";
}
$created =
$task != "create"
? new \Moment\Moment($page["rawCreated"])
: new \Moment\Moment();
$updated = new \Moment\Moment();
//grab current index from settings and update
$id = $task != "create" ? $body["id"] : Settings::getCurrentIndex();
$uuid = $task != "create" ? $body["uuid"] : StringTools::createUUID();
// now that variables are done, set to body object and then convert to markdown to save
$body["id"] = $id;
$body["uuid"] = $uuid;
$body["feature"] = $feature;
$body["path"] = $path;
$body["author"] = $member["handle"];
$body["created"] = $created->format("Y-m-d\TH:i:sP");
$body["updated"] = $updated->format("Y-m-d\TH:i:sP");
$body["deleted"] = $deleted;
$write = DocTools::objectToMD($body);
// if layout is index, change path to file
if ($body["layout"] == "index") {
$writePath = "../content/pages/start/index.md";
} else {
$writePath = "../content/pages/" . $path . "/" . $body["slug"] . ".md";
}
$status = DocTools::writePages($task, $path, $writePath, $write);
if ($status) {
$response = [
"message" => "File edited. Nice work",
"type" => $task == "write" ? "postUpdated" : "postAdded",
"id" => $uuid,
];
//**just testing to see why indexing isn't working **
//once saved, update menu
$body["path"] = $path;
Settings::updateMenu($body);
Settings::updateTags();
//if new page added, update current index in Settings file
if ($task == "create") {
Settings::updateIndex();
}
} else {
$response = [
"message" => "Uh oh. File save problem. Don't panic",
"type" => "postError",
"id" => $uuid,
];
}
return $response;
}
public function getPages(int $page, int $limit, string $sort = null)
{
$content = $this->getContents();
$published = filter($content, function ($item) {
return $item["published"] == true && $item["deleted"] == false;
});
$deleted = filter($content, function ($item) {
return $item["deleted"] == true;
});
$all = $content;
$filter = isset($sort) ? $sort : "all";
switch ($filter) {
case "published":
$filtered = $published;
break;
case "deleted":
$filtered = $deleted;
break;
default:
$filtered = $content;
break;
}
$numOfPages = ceil(count($filtered) / ($limit + 1));
$folder = [];
if (count($filtered) != 0) {
if (count($filtered) < $limit) {
$limit = count($filtered) - 1;
}
$range = $page * $limit - $limit;
if ($range != 0) {
$range = $range + 1;
}
for ($i = 0; $i <= $limit; $i++) {
if (isset($filtered[$i + $range])) {
array_push($folder, $filtered[$i + $range]);
} else {
//chill out
}
}
}
$prev = $page - 1;
if ($prev <= 0) {
$prev = $numOfPages;
}
$next = $page + 1;
if ($next > $numOfPages) {
$next = 1;
}
return [
"pages" => $folder,
"numOfPages" => $numOfPages,
"entryCount" => count($filtered),
"paginate" => [
"sort" => $sort,
"nextPage" => $next,
"prevPage" => $prev,
],
"stats" => [
"all" => count($all),
"published" => count($published),
"deleted" => count($deleted),
],
];
}
public function getContents()
{
//move page collection to utiltiy class
$parser = new Parser();
$contents = [];
foreach ($this->files as $file) {
$doc = $parser->parse(file_get_contents($file), false);
$meta = $doc->getYAML();
$page = [
"id" => $meta["id"],
"uuid" => $meta["uuid"],
"title" => $meta["title"],
"feature" => $meta["feature"],
"path" => $meta["path"],
"layout" => $meta["layout"],
"tags" => $meta["tags"],
"author" => $meta["author"],
"created" => date("Y M D d", $meta["created"]),
"updated" => date("Y M D d", $meta["updated"]),
"rawCreated" => $meta["created"],
"rawUpdated" => $meta["updated"],
"createdYear" => date("Y", $meta["created"]),
"createdMonth" => date("m", $meta["created"]),
"deleted" => $meta["deleted"],
"menu" => $meta["menu"],
"featured" => $meta["featured"],
"published" => $meta["published"],
"slug" => $meta["slug"],
"filePath" => $file,
"content" => $doc->getContent(),
];
//checks for duplicates
$uuid = $meta["uuid"];
$found = current(
array_filter($contents, function ($item) use ($uuid) {
return isset($item["uuid"]) && $uuid == $item["uuid"];
})
);
// if uuid is not present, add it
if (!$found) {
array_push($contents, $page);
}
}
$contents = orderBy($contents, ["id"], ["desc"]);
return $contents;
}
}

230
brain/data/Book.php Normal file
View file

@ -0,0 +1,230 @@
<?php
namespace brain\data;
use Carbon\Carbon;
use brain\utility\DocTools;
use brain\utility\StringTools;
use function _\find;
use function _\filter;
class Book
{
public function __construct()
{
}
public function findPageById(string $uuid)
{
$content = $this->getContents();
$page = find($content, ['uuid' => $uuid]);
return $page;
}
public function findPageBySlug(string $slug = null)
{
$content = $this->getContents();
if (isset($slug)) {
$page = find($content, ['slug' => $slug]);
} else {
$page = find($content, ['layout' => 'index']);
}
return $page;
}
public function editPage($task, $request)
{
$content = $this->getContents();
$body = json_decode(file_get_contents("php://input"), true);
//$body = find($content, ["uuid" => $parsed["id"]]);
$page = find($content, ['uuid' => $body['uuid']]);
$files = $request->getUploadedFiles();
$member = Session::get('member');
if ($task != 'create') {
$path = date('Y', date($page['rawCreated'])) .
'/' .
date('m', date($page['rawCreated']));
} else {
$path = date('Y') . '/' . date('m');
}
$page_feature = '';
$page_files = '';
if ($task == 'delete') {
$deleted = 'true';
$body['menu'] = 'false';
$body['published'] = 'false';
$body['featured'] = 'false';
} else {
$deleted = isset($page['deleted']) ? $page['deleted'] : 'false';
}
$created = $task != 'create' ? new Carbon($page['rawCreated']) : Carbon::now();
$updated = Carbon::now();
// grab current index from settings and update
$id = $task != 'create' ? $body['id'] : Settings::getCurrentIndex();
$uuid = $task != 'create' ? $body['uuid'] : StringTools::createUUID();
// now that variables are done, set to body object and then convert to markdown to save
$body['id'] = $id;
$body['uuid'] = $uuid;
//$body['feature'] = $page_feature;
//$body['files'] = $page_files;
$body['path'] = $path;
$body['author'] = $member['handle'];
$body['created'] = $created->format("Y-m-d\TH:i:sP");
$body['updated'] = $updated->format("Y-m-d\TH:i:sP");
$body['deleted'] = $deleted;
$write = DocTools::objectToMD($body);
// if layout is index, change path to file
if ($body['layout'] == 'index') {
$writePath = '../content/pages/start/index.md';
} else {
$writePath = '../content/pages/' . $path . '/' . $body['slug'] . '.md';
}
$status = DocTools::writePages($task, $path, $writePath, $write);
if ($status) {
$config = new Settings();
$settings = $config->getSettings();
$message = '';
if (
$settings['global']['renderOnSave'] == 'true' &&
$settings['global']['dynamicRender'] == 'false'
) {
$render = new Render();
$render->renderTags();
$render->renderArchive();
$render->renderPages();
$message = 'Filed edited and rendered. NOICE.';
} else {
$message = 'File edited. Nice work';
}
$response = [
'message' => $message,
'type' => $task == 'write' ? 'postUpdated' : 'postAdded',
'id' => $uuid,
];
// TODO: When form submission is successful, make new form token
// Session token doesn't reset on the front end, so turning this off for now
// $form_token = md5(uniqid(microtime(), true));
// Session::set("form_token", $form_token);
// once saved, update menu
$body['path'] = $path;
Settings::updateMenu($body);
Settings::updateTags();
// if new page added, update current index in Settings file
if ($task == 'create') {
Settings::updateIndex();
}
} else {
$response = [
'message' => "Uh oh. File save problem. Don't panic",
'type' => 'postError',
'id' => $uuid,
];
}
return $response;
}
public function getPages(int $page, int $limit, string $sort = null)
{
$content = $this->getContents();
$published = filter($content, function ($item) {
return $item['published'] == true && $item['deleted'] == false;
});
$deleted = filter($content, function ($item) {
return $item['deleted'] == true;
});
// $all = $content;
$all = filter($content, function ($item) {
return $item['deleted'] == false;
});
$filter = isset($sort) ? $sort : 'all';
switch ($filter) {
case 'published':
$filtered = $published;
break;
case 'deleted':
$filtered = $deleted;
break;
default:
$filtered = $all;
break;
}
$numOfPages = ceil(count($filtered) / ($limit + 1));
$folder = [];
if (count($filtered) != 0) {
if (count($filtered) < $limit) {
$limit = count($filtered) - 1;
}
$range = $page * $limit - $limit;
if ($range != 0) {
$range = $range + 1;
}
for ($i = 0; $i <= $limit; ++$i) {
if (isset($filtered[$i + $range])) {
array_push($folder, $filtered[$i + $range]);
} else {
// chill out
}
}
}
$prev = $page - 1;
if ($prev <= 0) {
$prev = $numOfPages;
}
$next = $page + 1;
if ($next > $numOfPages) {
$next = 1;
}
return [
'pages' => $folder,
'numOfPages' => $numOfPages,
'entryCount' => count($filtered),
'paginate' => [
'sort' => $sort,
'nextPage' => $next,
'prevPage' => $prev,
],
'stats' => [
'all' => count($all),
'published' => count($published),
'deleted' => count($deleted),
],
];
}
public function getContents()
{
// test new contents data class
// $new = (new Contents("../content/pages"))->getAll();
$contents = (new Contents('../content/pages'))->getAll();
return $contents;
}
}

193
brain/data/Contents.php Normal file
View file

@ -0,0 +1,193 @@
<?php
namespace brain\data;
use HtmlSanitizer\SanitizerBuilder;
use League\CommonMark\MarkdownConverter;
use League\CommonMark\Environment\Environment;
use HtmlSanitizer\Extension\Basic\BasicExtension;
use HtmlSanitizer\Extension\Listing\ListExtension;
use HtmlSanitizer\Extension\Iframe\IframeExtension;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\Extension\Attributes\AttributesExtension;
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
use function _\orderBy;
class Contents
{
public $files = [];
public $config = [];
public function __construct($folder)
{
$this->read($folder);
}
public function read($folder)
{
$folders = glob("$folder/*", GLOB_ONLYDIR);
foreach ($folders as $folder) {
//$this->files[] = $folder . "/";
$this->read($folder);
}
$files = array_filter(glob("$folder/*md"), 'is_file');
foreach ($files as $file) {
$this->files[] = $file;
}
}
public function getAll()
{
$environment = new Environment($this->config);
$environment->addExtension(new CommonMarkCoreExtension());
// Add the extension
$environment->addExtension(new FrontMatterExtension());
//Add Strikethrough rendering
$environment->addExtension(new StrikethroughExtension());
//add attributes to elements in markdown
$environment->addExtension(new AttributesExtension());
//add table rendering
$environment->addExtension(new TableExtension());
// Instantiate the converter engine and start converting some Markdown!
$converter = new MarkdownConverter($environment);
$contents = [];
foreach ($this->files as $file) {
//get meta and html from file
$result = $converter->convertToHtml(file_get_contents($file));
$meta = [];
if ($result instanceof RenderedContentWithFrontMatter) {
$meta = $result->getFrontMatter();
}
//get raw markdown from file
$frontMatterExtension = new FrontMatterExtension();
$parsed = $frontMatterExtension
->getFrontMatterParser()
->parse(file_get_contents($file));
//never trust the front end. clean it up
//add what sanitizer extensions we need manually
$builder = new SanitizerBuilder();
$builder->registerExtension(new BasicExtension());
$builder->registerExtension(new IframeExtension());
$builder->registerExtension(new ListExtension());
//just add it straight because classname is already in use
$builder->registerExtension(new \HtmlSanitizer\Extension\Table\TableExtension());
//relative-a and relative-image
$builder->registerExtension(
new \HtmlSanitizer\Extension\Relative\A\AExtension()
);
$builder->registerExtension(
new \HtmlSanitizer\Extension\Relative\Image\ImageExtension()
);
$detergent = [
'extensions' => ['basic', 'list', 'relative-a', 'relative-image', 'iframe', 'table'],
'tags' => [
'div' => [
'allowed_attributes' => ['class', 'title', 'id', 'style'],
],
'img' => [
'allowed_attributes' => ['src', 'alt', 'title', 'class'],
],
'iframe' => [
'allowed_attributes' => ['height', 'width', 'title', 'src'],
],
],
];
$sanitizer = $builder->build($detergent);
$scrubbed = $sanitizer->sanitize($result->getContent());
if (isset($meta['feature'])) {
$featureList = explode(',', $meta['feature']);
} else {
$featureList = "";
}
$docs = '';
if (isset($meta['files'])) {
$fileList = explode(',', $meta['files']);
$docs = $meta['files'];
} else {
$fileList = [];
$docs = '';
}
$media = [];
$files = [];
if ($featureList != '') {
foreach ($featureList as $file) {
$item = trim($file);
$ext = pathinfo($item, PATHINFO_EXTENSION);
if ($item != null || $item != '') {
array_push($media, ['file' => $item, 'type' => trim($ext)]);
}
}
}
if ($fileList != "") {
foreach ($fileList as $file) {
$item = trim($file);
$ext = pathinfo($item, PATHINFO_EXTENSION);
if ($item != null || $item != '') {
array_push($files, ['file' => $item, 'type' => trim($ext)]);
}
}
}
//sort attributes into page object
$page = [
'id' => $meta['id'],
'uuid' => $meta['uuid'],
'title' => $meta['title'],
'feature' => $meta['feature'],
'files' => $docs,
'path' => $meta['path'],
'layout' => $meta['layout'],
'tags' => $meta['tags'],
'author' => $meta['author'],
'created' => date('Y M D d', $meta['created']),
'updated' => date('Y M D d', $meta['updated']),
'rawCreated' => $meta['created'],
'rawUpdated' => $meta['updated'],
'createdYear' => date('Y', $meta['created']),
'createdMonth' => date('m', $meta['created']),
'deleted' => $meta['deleted'],
'menu' => $meta['menu'],
'featured' => $meta['featured'],
'published' => $meta['published'],
'slug' => $meta['slug'],
'filePath' => $file,
'content' => $parsed->getContent(),
'html' => $scrubbed,
'media' => $media,
'docs' => $files
];
//checks for duplicates
$uuid = $meta['uuid'];
$found = current(
array_filter($contents, function ($item) use ($uuid) {
return isset($item['uuid']) && $uuid == $item['uuid'];
})
);
// if uuid is not present, add it
if (!$found) {
array_push($contents, $page);
}
}
$contents = orderBy($contents, ['id'], ['desc']);
return $contents;
}
}

View file

@ -1,54 +0,0 @@
<?php
use function _\find;
class Member
{
public function __construct()
{
}
public static function verifyKey(string $key)
{
if (isset($key)) {
$folks = (new Settings())->getFolks();
$found = find($folks, ["key" => $key]);
if ($found) {
return true;
} else {
return false;
}
} else {
return false;
}
}
public static function updateData(string $key, string $data, $secret = null)
{
$folks = (new Settings())->getFolks();
if (isset($secret)) {
$found = find($folks, ["secret" => $secret]);
} else {
$member = Session::get("member");
$found = find($folks, ["handle" => $member["handle"]]);
}
$found[$key] = $data;
//record time updated
$updated = new \Moment\Moment();
$found["updated"] = $updated->format("Y-m-d\TH:i:sP");
$newFolks = [];
array_push($newFolks, $found);
//save updated file
DocTools::writeSettings("../config/folks.json", $newFolks);
//update member data in session
if (!isset($secret)) {
$member = [
"handle" => $found["handle"],
"email" => $found["email"],
"role" => $found["role"],
"avatar" => $found["avi"],
];
Session::set("member", $member);
}
}
}

61
brain/data/Member.php Normal file
View file

@ -0,0 +1,61 @@
<?php
namespace brain\data;
use Carbon\Carbon;
use brain\utility\DocTools;
use function _\find;
class Member
{
public function __construct()
{
}
public static function verifyKey(string $key)
{
if (isset($key)) {
$folks = (new Settings())->getFolks();
$found = find($folks, ['key' => $key]);
if ($found) {
return true;
} else {
return false;
}
} else {
return false;
}
}
public static function updateData(string $key, string $data, $secret = null)
{
$folks = (new Settings())->getFolks();
if (isset($secret)) {
$found = find($folks, ['secret' => $secret]);
} else {
$member = Session::get('member');
$found = find($folks, ['handle' => $member['handle']]);
}
$found[$key] = $data;
//record time updated
$updated = Carbon::now();
$found['updated'] = $updated->format("Y-m-d\TH:i:sP");
$newFolks = [];
array_push($newFolks, $found);
//save updated file
DocTools::writeSettings('../config/folks.json', $newFolks);
//update member data in session
if (!isset($secret)) {
$member = [
'handle' => $found['handle'],
'email' => $found['email'],
'role' => $found['role'],
'avatar' => $found['avi'],
'key' => $found['key'],
];
Session::set('member', $member);
}
}
}

View file

@ -1,215 +0,0 @@
<?php
use Mni\FrontYAML\Parser;
class Render
{
public $loader;
public $twig;
public $pageInfo;
public $menu;
public $background;
public function __construct()
{
$config = new Settings();
$this->loader = new \Twig\Loader\FilesystemLoader("../content/themes");
$this->twig = new \Twig\Environment($this->loader, []);
$settings = $config->getSettings();
$this->menu = $settings["menu"];
$this->theme = $settings["global"]["theme"];
$this->pageInfo = [
"keywords" => isset($settings["global"]["keywords"])
? $settings["global"]["keywords"]
: "fipamo, blog, jamstack, php, markdown, js",
"description" => $settings["global"]["descriptions"],
"image" =>
$settings["global"]["base_url"] . $settings["global"]["background"],
"baseURL" => $settings["global"]["base_url"],
];
//copy current theme assets to public
if (is_file("../public/assets/css/base.css")) {
unlink("../public/assets/css/base.css");
}
copy(
"../content/themes/" . $this->theme . "/assets/css/base.css",
"../public/assets/css/base.css"
);
if (is_file("../public/assets/scripts/start.min.js")) {
unlink("../public/assets/scripts/start.min.js");
}
copy(
"../content/themes/" . $this->theme . "/assets/scripts/start.min.js",
"../public/assets/scripts/start.min.js"
);
}
public function renderPages()
{
$pages = (new Book("../content/pages"))->getContents();
$recent = [];
$featured = [];
$limit = 4;
foreach ($pages as $page) {
if (!$page["deleted"] && $page["published"]) {
if (count($recent) < $limit) {
array_push($recent, [
"path" => $page["path"],
"slug" => $page["slug"],
"title" => $page["title"],
]);
}
if ($page["featured"] == true) {
if (count($featured) < $limit) {
array_push($featured, [
"path" => $page["path"],
"slug" => $page["slug"],
"title" => $page["title"],
]);
}
}
$taglist = explode(",", $page["tags"]);
$tags = [];
foreach ($taglist as $tag) {
$label = trim($tag);
array_push($tags, [
"label" => $label . " ",
"slug" => StringTools::safeString($label),
]);
}
$meta = [
"who" => $page["author"],
"when" => $page["created"],
"tags" => $tags,
];
//render markdown content and clean it
$parser = new Parser();
$rendered = $parser->parse($page["content"]);
$sanitizer = HtmlSanitizer\Sanitizer::create([
"extensions" => ["basic", "image", "list", "code"],
"tags" => [
"img" => [
"allowed_attributes" => ["src", "alt", "title", "class"],
"allowed_hosts" => null,
],
],
]);
$preclean = $sanitizer->sanitize($rendered->getContent());
//just clean renderd string for now, Sanitize doesn't like relative img urls
//so another option is needed
$cleaned = strip_tags($rendered->getContent(), [
"a",
"br",
"p",
"strong",
"br",
"img",
"iframe",
"ul",
"li",
"i",
"h1",
"h2",
"h3",
"pre",
"code",
]);
//$cleaned = preg_replace('/(?:\r\n|[\r\n]){2,}/', "\n\n", $cleaned);
//$cleaned = html_entity_decode($cleaned, ENT_QUOTES, "UTF-8");
//if page feature isn't empty, replace page info meta image
if ($page["feature"] != "" || $page["feature"] != null) {
$this->pageInfo["image"] =
$this->pageInfo["baseURL"] . $page["feature"];
}
if ($page["layout"] == "index") {
$template = $this->theme . "/index.twig";
$location = "../public/index.html";
$dir = null;
$pageOptions = [
"title" => $page["title"],
"background" => $page["feature"],
"content" => $cleaned,
"meta" => $meta,
"recent" => $recent,
"featured" => $featured,
"info" => $this->pageInfo,
"menu" => $this->menu,
];
} else {
$template = $this->theme . "/page.twig";
$location =
"../public/" . $page["path"] . "/" . $page["slug"] . ".html";
$dir = "../public/" . $page["path"];
$pageOptions = [
"title" => $page["title"],
"background" => $page["feature"],
"content" => $cleaned,
"meta" => $meta,
"info" => $this->pageInfo,
"menu" => $this->menu,
];
}
$html = $this->twig->render($template, $pageOptions);
DocTools::writeHTML($location, $html, $dir);
}
}
}
public function renderArchive()
{
$archive = Sorting::archive();
$template = $this->theme . "/archive.twig";
$pageOptions = [
"title" => "Archive",
"background" => $this->pageInfo["image"],
"archives" => $archive,
"info" => $this->pageInfo,
"menu" => $this->menu,
];
$html = $this->twig->render($template, $pageOptions);
$location = "../public/archives.html";
DocTools::writeHTML($location, $html);
}
public function renderTags()
{
$list = Sorting::tags();
foreach ($list as $item) {
$template = $this->theme . "/tags.twig";
$pageOptions = [
"title" => "Pages Tagged as " . $item["tag_name"],
"background" => $this->pageInfo["image"],
"tag_list" => $item["pages"],
"info" => $this->pageInfo,
"menu" => $this->menu,
];
$html = $this->twig->render($template, $pageOptions);
$location = "../public/tags/" . $item["slug"] . ".html";
//if tags folder doesn't exist, make it
if (!is_dir("../public/tags")) {
mkdir("../public/tags", 0755, true);
} else {
}
if (!is_file($location)) {
file_put_contents($location, $html);
} else {
($new = fopen($location, "w")) or die("Unable to open file!");
fwrite($new, $html);
fclose($new);
}
}
}
}

204
brain/data/Render.php Normal file
View file

@ -0,0 +1,204 @@
<?php
namespace brain\data;
use brain\utility\Sorting;
use brain\utility\DocTools;
use function _\find;
class Render
{
public $loader;
public $twig;
public $pageInfo;
public $menu;
public $background;
public function __construct()
{
$config = new Settings();
//TODO: Add theme folder to loader
$settings = $config->getSettings();
$this->menu = $settings['menu'];
$this->theme = $settings['global']['theme'];
$this->loader = new \Twig\Loader\FilesystemLoader('../content/themes/' . $this->theme);
$this->twig = new \Twig\Environment($this->loader, []);
$this->pageInfo = [
'keywords' => isset($settings['global']['keywords'])
? $settings['global']['keywords']
: 'fipamo, blog, jamstack, php, markdown, js',
'description' => $settings['global']['descriptions'],
'image' => $settings['global']['base_url'] . $settings['global']['background'],
'baseURL' => $settings['global']['base_url'],
];
//move global theme image assets to public folder
foreach (
new \DirectoryIterator('../content/themes/' . $this->theme . '/assets/images/global/') as $file
) {
if ($file->isDot()) {
continue;
}
if (!is_file('../public/assets/images/global/' . $file->getFileName())) {
copy(
'../content/themes/' .
$this->theme .
'/assets/images/global/' .
$file->getFileName(),
'../public/assets/images/global/' . $file->getFileName()
);
} else {
//image is already there, so chill
}
//print $file->getFilename() . "\n";
}
//copy current theme assets to public
//clear files in css and scripts folder
$styles = glob('../public/assets/css/*'); // get all file names
foreach ($styles as $file) { // iterate files
if (is_file($file)) {
//don't erase dashboard css
if (!$file == '../public/assets/css/dash.css') {
unlink($file); // delete file
}
}
}
$scripts = glob('../public/assets/scripts/*'); // get all file names
foreach ($scripts as $file) { // iterate files
if (is_file($file)) {
if (!$file == '../public/assets/scripts/Start.js') {
unlink($file); // delete file
}
}
}
//copy theme assets to public
$newcss = glob('../content/themes/' . $this->theme . '/assets/css/*');
foreach ($newcss as $file) { // iterate files
if (is_file($file)) {
$path = explode('/', $file);
copy($file, '../public/assets/css/' . $path[6]);
}
}
$newjs = glob('../content/themes/' . $this->theme . '/assets/scripts/*');
foreach ($newjs as $file) { // iterate files
if (is_file($file)) {
$path = explode('/', $file);
copy($file, '../public/assets/scripts/' . $path[6]);
}
}
}
public function renderPages()
{
$pages = (new Book())->getContents();
$recent = [];
$featured = [];
$limit = 4;
foreach ($pages as $page) {
$pageOptions = Sorting::page($page);
$layout = $page['layout'];
//new pages have no layout, so defautl for now
if ($layout == '' || $layout == null) {
$layout = 'page';
}
$template = $layout . '.twig';
if (str_contains($layout, 'index')) {
$location = '../public/index.html';
$dir = null;
} else {
// if page is a menu item, render the page on public root
if ($page['menu'] == 'true') {
$location = '../public/' . $page['slug'] . '.html';
$dir = '../public/';
} else {
$location = '../public/' . $page['path'] . '/' . $page['slug'] . '.html';
$dir = '../public/' . $page['path'];
}
}
$html = $this->twig->render($template, $pageOptions);
DocTools::writeHTML($location, $html, $dir);
}
}
public function renderArchive()
{
$archive = Sorting::archive();
$template = 'archive.twig';
$pageOptions = [
'title' => 'Archive',
'background' => $this->pageInfo['image'],
'archives' => $archive,
'info' => $this->pageInfo,
'menu' => $this->menu,
'media' => [['file' => $this->pageInfo['image'], 'type' => trim(pathinfo($this->pageInfo['image'], PATHINFO_EXTENSION))]],
];
$html = $this->twig->render($template, $pageOptions);
$location = '../public/archives.html';
DocTools::writeHTML($location, $html);
}
public function renderTags()
{
$list = Sorting::tags();
foreach ($list as $item) {
$template = 'tags.twig';
$pageOptions = [
'title' => 'Pages Tagged as ' . $item['tag_name'],
'background' => $this->pageInfo['image'],
'tag_list' => $item['pages'],
'info' => $this->pageInfo,
'menu' => $this->menu,
'media' => [['file' => $this->pageInfo['image'], 'type' => trim(pathinfo($this->pageInfo['image'], PATHINFO_EXTENSION))]],
];
$html = $this->twig->render($template, $pageOptions);
$location = '../public/tags/' . $item['slug'] . '.html';
//if tags folder doesn't exist, make it
if (!is_dir('../public/tags')) {
mkdir('../public/tags', 0755, true);
} else {
}
if (!is_file($location)) {
file_put_contents($location, $html);
} else {
($new = fopen($location, 'w')) or die('Unable to open file!');
fwrite($new, $html);
fclose($new);
}
}
}
public function renderIndex()
{
//TODO: Need to fix this to account for new index templating system
$pages = (new Book())->getContents();
$index = find($pages, ['layout' => 'index']);
$template = 'index.twig';
$location = '../public/index.html';
$dir = null;
$meta = [
'who' => $index['author'],
'when' => $index['created'],
];
$pageOptions = [
'title' => $index['title'],
'background' => $index['feature'],
'meta' => $meta,
];
$html = $this->twig->render($template, $pageOptions);
DocTools::writeHTML($location, $html, $dir);
}
}

View file

@ -1,89 +0,0 @@
<?php
use function _\find;
use ReallySimpleJWT\Token;
class Session
{
private static $file = "../content/.session";
private static $data = [
"member" => "",
"token" => "",
];
public static function start()
{
if (!is_file(self::$file)) {
file_put_contents(self::$file, json_encode(self::$data));
} else {
($new = fopen(self::$file, "w")) or die("Unable to open file!");
fwrite($new, json_encode(self::$data));
fclose($new);
}
}
public static function active()
{
if (!is_file(self::$file)) {
return false;
} else {
$data = json_decode(file_get_contents(self::$file), true);
if ($data["member"] != null) {
$secret = (new Settings())->getFolks("secret");
if ($secret == null) {
return false;
} else {
if (
Token::validate($data["token"], $secret) &&
Token::validateExpiration($data["token"], $secret)
) {
return true;
} else {
return false;
}
}
} else {
return false;
}
}
}
public static function verifyToken($token)
{
$data = json_decode(file_get_contents(self::$file), true);
if ($data["member"] != null) {
$secret = (new Settings())->getFolks("secret");
if (
Token::validate($token, $secret) &&
Token::validateExpiration($token, $secret)
) {
return true;
} else {
return false;
}
} else {
return false;
}
}
public static function set($key, $value)
{
$data = json_decode(file_get_contents(self::$file), true);
$data[$key] = $value;
($fresh = fopen(self::$file, "w")) or die("Unable to open file!");
fwrite($fresh, json_encode($data));
fclose($fresh);
}
public static function get($key)
{
$data = json_decode(file_get_contents(self::$file), true);
return $data[$key];
}
public static function kill()
{
($fresh = fopen(self::$file, "w")) or die("Unable to open file!");
fwrite($fresh, json_encode(self::$data));
fclose($fresh);
}
}

93
brain/data/Session.php Normal file
View file

@ -0,0 +1,93 @@
<?php
namespace brain\data;
use ReallySimpleJWT\Token;
class Session
{
private static $file = '../content/.session';
private static $data = [
'member' => '',
'token' => '',
'form_token' => '',
];
public static function start()
{
if (!is_file(self::$file)) {
file_put_contents(self::$file, json_encode(self::$data));
} else {
($new = fopen(self::$file, 'w')) or die('Unable to open file!');
fwrite($new, json_encode(self::$data));
fclose($new);
}
}
public static function active()
{
if (!is_file(self::$file)) {
return false;
} else {
$data = json_decode(file_get_contents(self::$file), true);
if ($data['member'] != null) {
$secret = (new Settings())->getFolks('secret');
if ($secret == null) {
return false;
} else {
if (
Token::validate($data['token'], $secret) &&
Token::validateExpiration($data['token'], $secret)
) {
return true;
} else {
return false;
}
}
} else {
return false;
}
}
}
public static function verifyToken($token)
{
$data = json_decode(file_get_contents(self::$file), true);
if ($data['member'] != null) {
$secret = (new Settings())->getFolks('secret');
if (
Token::validate($token, $secret) &&
Token::validateExpiration($token, $secret)
) {
return true;
} else {
return false;
}
} else {
return false;
}
}
public static function set($key, $value)
{
$data = json_decode(file_get_contents(self::$file), true);
$data[$key] = $value;
($fresh = fopen(self::$file, 'w')) or die('Unable to open file!');
fwrite($fresh, json_encode($data));
fclose($fresh);
}
public static function get($key)
{
$data = json_decode(file_get_contents(self::$file), true);
return $data[$key];
}
public static function kill()
{
($fresh = fopen(self::$file, 'w')) or die('Unable to open file!');
fwrite($fresh, json_encode(self::$data));
fclose($fresh);
}
}

View file

@ -1,182 +0,0 @@
<?php
use function _\find;
use function _\pull;
use function _\remove;
class Settings
{
private $folks;
private static $tags;
private $themes = [];
private static $settings;
public function __construct()
{
//gets all settings files and converts to php objects
$this->folks = json_decode(file_get_contents("../config/folks.json"), true);
self::$tags = json_decode(file_get_contents("../config/tags.json"), true);
self::$settings = json_decode(
file_get_contents("../config/settings.json"),
true
);
$_themes = glob("../content/themes/*", GLOB_ONLYDIR);
foreach ($_themes as $theme) {
array_push(
$this->themes,
json_decode(file_get_contents($theme . "/theme.json"), true)
);
}
}
public static function sync($data)
{
$settings = self::$settings;
$settings["global"]["base_url"] = $data["global"]["base_url"];
$settings["global"]["title"] = $data["global"]["title"];
$settings["global"]["descriptions"] = $data["global"]["descriptions"];
$settings["global"]["base_url"] = $data["global"]["base_url"];
$settings["global"]["private"] = $data["global"]["private"];
$settings["global"]["renderOnSave"] = $data["global"]["renderOnSave"];
$settings["global"]["theme"] = $data["global"]["theme"];
$settings["global"]["externalAPI"] = $data["global"]["externalAPI"];
Member::updateData("handle", $data["member"]["handle"]);
Member::updateData("email", $data["member"]["email"]);
$settings["email"]["active"] = $data["email"]["active"];
$settings["email"]["smtp"] = $data["email"]["smtp"];
$settings["email"]["mailgun"] = $data["email"]["mailgun"];
DocTools::writeSettings("../config/settings.json", $settings);
}
public static function navSync($data)
{
$settings = self::$settings;
$remove = $data["remove"];
//if remove contains id, find nav item page and set menu to false
if ($remove != null || $remove != "") {
$page = (new Book("../content/pages"))->findPageById($remove);
$page["menu"] = "false";
$page["published"]
? ($page["published"] = "true")
: ($page["published"] = "false");
$page["featured"]
? ($page["featured"] = "true")
: ($page["featured"] = "false");
$page["deleted"]
? ($page["deleted"] = "true")
: ($page["deleted"] = "false");
$updated = new \Moment\Moment();
$created = new \Moment\Moment($page["rawCreated"]);
$page["created"] = $created->format("Y-m-d\TH:i:sP");
$page["updated"] = $updated->format("Y-m-d\TH:i:sP");
$md = DocTools::objectToMD($page);
if ($page["layout"] == "index") {
$writePath = "../content/pages/start/index.md";
} else {
$writePath =
"../content/pages/" . $page["path"] . "/" . $page["slug"] . ".md";
}
DocTools::writePages("write", $page["path"], $writePath, $md);
}
$settings["menu"] = [];
$items = $data["menu"];
foreach ($items as $item) {
array_push($settings["menu"], [
"title" => $item["title"],
"id" => $item["id"],
"uuid" => $item["uuid"],
"slug" => $item["slug"],
"path" => $item["path"],
]);
}
DocTools::writeSettings("../config/settings.json", $settings);
}
public function getThemes()
{
return $this->themes;
}
public function getFolks($key = null)
{
if (isset($key)) {
$member = Session::get("member");
$found = find($this->folks, ["handle" => $member["handle"]]);
if ($found) {
return $found[$key];
}
} else {
return $this->folks;
}
}
public function getSettings($key = null)
{
return self::$settings;
}
public static function getTags()
{
return self::$tags;
}
public static function updateGlobalData($key, $data)
{
$settings = self::$settings;
$settings["global"][$key] = $data;
DocTools::writeSettings("../config/settings.json", $settings);
}
public static function getCurrentIndex()
{
$settings = self::$settings;
return $settings["library_stats"]["current_index"];
}
public static function updateIndex()
{
$settings = self::$settings;
$settings["library_stats"]["current_index"] =
$settings["library_stats"]["current_index"] + 1;
DocTools::writeSettings("../config/settings.json", $settings);
}
public static function updateMenu($body)
{
$settings = self::$settings;
//$menu = $settings["menu"];
$item = [
"title" => $body["title"],
"id" => $body["id"],
"uuid" => $body["uuid"],
"slug" => $body["slug"],
"path" => $body["path"],
];
if ($body["menu"] == "true") {
if (!find($settings["menu"], ["uuid" => $item["uuid"]])) {
array_push($settings["menu"], $item);
}
} else {
if (find($settings["menu"], ["uuid" => $item["uuid"]])) {
pull($settings["menu"], $item);
}
}
DocTools::writeSettings("../config/settings.json", $settings);
}
public static function updateTags()
{
$tags = Sorting::tags();
DocTools::writeSettings("../config/tags.json", $tags);
}
}

175
brain/data/Settings.php Normal file
View file

@ -0,0 +1,175 @@
<?php
namespace brain\data;
use brain\utility\DocTools;
use brain\utility\Sorting;
use Carbon\Carbon;
use function _\find;
use function _\pull;
use function _\remove;
class Settings
{
private $folks;
private static $tags;
private static $settings;
public function __construct()
{
//gets all settings files and converts to php objects
$this->folks = json_decode(file_get_contents('../config/folks.json'), true);
self::$tags = json_decode(file_get_contents('../config/tags.json'), true);
self::$settings = json_decode(
file_get_contents('../config/settings.json'),
true
);
}
public static function sync($data)
{
$settings = self::$settings;
$settings['global']['base_url'] = $data['global']['base_url'];
$settings['global']['title'] = $data['global']['title'];
$settings['global']['descriptions'] = $data['global']['descriptions'];
$settings['global']['base_url'] = $data['global']['base_url'];
$settings['global']['private'] = $data['global']['private'];
$settings['global']['renderOnSave'] = $data['global']['renderOnSave'];
$settings['global']['theme'] = $data['global']['theme'];
$settings['global']['externalAPI'] = $data['global']['externalAPI'];
$settings['global']['dynamicRender'] = $data['global']['dynamicRender'];
Member::updateData('handle', $data['member']['handle']);
Member::updateData('email', $data['member']['email']);
$settings['email']['active'] = $data['email']['active'];
$settings['email']['smtp'] = $data['email']['smtp'];
$settings['email']['mailgun'] = $data['email']['mailgun'];
DocTools::writeSettings('../config/settings.json', $settings);
}
public static function navSync($data)
{
$settings = self::$settings;
$remove = $data['remove'];
//if remove contains id, find nav item page and set menu to false
if ($remove != null || $remove != '') {
$page = (new Book('../content/pages'))->findPageById($remove);
$page['menu'] = 'false';
$page['published']
? ($page['published'] = 'true')
: ($page['published'] = 'false');
$page['featured']
? ($page['featured'] = 'true')
: ($page['featured'] = 'false');
$page['deleted']
? ($page['deleted'] = 'true')
: ($page['deleted'] = 'false');
$updated = Carbon::now();
$created = new Carbon($page['rawCreated']);
$page['created'] = $created->format("Y-m-d\TH:i:sP");
$page['updated'] = $updated->format("Y-m-d\TH:i:sP");
$md = DocTools::objectToMD($page);
if ($page['layout'] == 'index') {
$writePath = '../content/pages/start/index.md';
} else {
$writePath = '../content/pages/' . $page['path'] . '/' . $page['slug'] . '.md';
}
DocTools::writePages('write', $page['path'], $writePath, $md);
}
$settings['menu'] = [];
$items = $data['menu'];
foreach ($items as $item) {
array_push($settings['menu'], [
'title' => $item['title'],
'id' => $item['id'],
'uuid' => $item['uuid'],
'slug' => $item['slug'],
'path' => $item['path'],
]);
}
DocTools::writeSettings('../config/settings.json', $settings);
}
public function getFolks($key = null)
{
if (isset($key)) {
$member = Session::get('member');
$found = find($this->folks, ['handle' => $member['handle']]);
if ($found) {
return $found[$key];
}
} else {
return $this->folks;
}
}
public function getSettings($key = null)
{
return self::$settings;
}
public static function getTags()
{
return self::$tags;
}
public static function updateGlobalData($key, $data)
{
$settings = self::$settings;
$settings['global'][$key] = $data;
DocTools::writeSettings('../config/settings.json', $settings);
}
public static function getCurrentIndex()
{
$settings = self::$settings;
return $settings['library_stats']['current_index'];
}
public static function updateIndex()
{
$settings = self::$settings;
$settings['library_stats']['current_index'] = $settings['library_stats']['current_index'] + 1;
DocTools::writeSettings('../config/settings.json', $settings);
}
public static function updateMenu($body)
{
$settings = self::$settings;
//$menu = $settings["menu"];
$item = [
'title' => $body['title'],
'id' => $body['id'],
'uuid' => $body['uuid'],
'slug' => $body['slug'],
'path' => $body['path'],
];
if ($body['menu'] == 'true') {
if (!find($settings['menu'], ['uuid' => $item['uuid']])) {
array_push($settings['menu'], $item);
}
} else {
if (find($settings['menu'], ['uuid' => $item['uuid']])) {
pull($settings['menu'], $item);
}
}
DocTools::writeSettings('../config/settings.json', $settings);
}
public static function updateTags()
{
$tags = Sorting::tags();
DocTools::writeSettings('../config/tags.json', $tags);
}
}

62
brain/data/Themes.php Normal file
View file

@ -0,0 +1,62 @@
<?php
namespace brain\data;
class Themes
{
private $themes = [];
public function __construct()
{
$_themes = glob('../content/themes/*', GLOB_ONLYDIR);
foreach ($_themes as $theme) {
array_push(
$this->themes,
json_decode(file_get_contents($theme . '/theme.json'), true)
);
}
}
public function getThemes()
{
return $this->themes;
}
public function getCustomIndex()
{
$settings = (new Settings())->getSettings();
$currentTheme = $settings['global']['theme'];
$folder = '../content/themes/' . $currentTheme;
$files = array_filter(glob("$folder/*twig"), 'is_file');
$views = [];
foreach ($files as $file) {
$path = explode('/', $file);
$fileName = $path[4];
if (str_contains($fileName, 'index')) {
$page = explode('.', $fileName);
$views[] = $page[0];
}
}
return $views;
}
public function getCustomViews()
{
$settings = (new Settings())->getSettings();
$currentTheme = $settings['global']['theme'];
$folder = '../content/themes/' . $currentTheme;
$files = array_filter(glob("$folder/*twig"), 'is_file');
$views = [];
foreach ($files as $file) {
$path = explode('/', $file);
$fileName = $path[4];
if (str_contains($fileName, 'page')) {
$page = explode('.', $fileName);
$views[] = $page[0];
}
}
return $views;
}
}

33
brain/init/App.php Normal file
View file

@ -0,0 +1,33 @@
<?php
namespace brain\init;
use brain\utility\HandleCors;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
class App
{
public function __construct()
{
// when a new class is made, run composer dump-autoload
// set up cors
new HandleCors();
$app = AppFactory::create();
$twig = Twig::create('../brain/views/');
$app->add(TwigMiddleware::create($app, $twig));
// set up routing
$app->get(
'/[{first}[/{second}[/{third}[/{fourth}[/{fifth}]]]]]',
"brain\controller\RouteControl:get"
);
$app->post(
'/[{first}[/{second}[/{third}[/{fourth}]]]]',
"brain\controller\RouteControl:post"
);
// start the app
$app->run();
}
}

View file

@ -1,138 +0,0 @@
<?php
class DocTools
{
public function __construct()
{
}
public static function writePages($task, $path, $fileLocation, $fileContents)
{
try {
if ($task == "create") {
if (!is_dir("../content/pages/" . $path)) {
//Directory does not exist, so lets create it.
mkdir("../content/pages/" . $path, 0755, true);
}
file_put_contents($fileLocation, $fileContents);
} else {
($new = fopen($fileLocation, "w")) or die("Unable to open file!");
fwrite($new, $fileContents);
fclose($new);
}
return true;
} catch (Error $error) {
return false;
}
}
public static function writeSettings($fileLocation, $fileContents)
{
if (!is_file($fileLocation)) {
file_put_contents($fileLocation, json_encode($fileContents));
} else {
($new = fopen($fileLocation, "w")) or die("Unable to open file!");
fwrite($new, json_encode($fileContents));
fclose($new);
}
}
public static function writeHTML($location, $html, $path = null)
{
if ($path != null) {
if (!is_dir($path)) {
//Directory does not exist, so lets create it.
mkdir($path, 0755, true);
}
}
if (!is_file($location)) {
file_put_contents($location, $html);
} else {
($new = fopen($location, "w")) or die("Unable to open file!");
fwrite($new, $html);
fclose($new);
}
}
public static function deleteFolder($path)
{
if (!empty($path) && is_dir($path)) {
$dir = new RecursiveDirectoryIterator(
$path,
RecursiveDirectoryIterator::SKIP_DOTS
); //upper dirs are not included,otherwise DISASTER HAPPENS :)
$files = new RecursiveIteratorIterator(
$dir,
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $f) {
if (is_file($f)) {
unlink($f);
} else {
$empty_dirs[] = $f;
}
}
if (!empty($empty_dirs)) {
foreach ($empty_dirs as $eachDir) {
rmdir($eachDir);
}
}
rmdir($path);
}
}
public static function objectToMD($object)
{
$markdown =
"---\n" .
"id: " .
$object["id"] .
"\n" .
"uuid: " .
$object["uuid"] .
"\n" .
"title: " .
$object["title"] .
"\n" .
"feature: " .
$object["feature"] .
"\n" .
"path: " .
$object["path"] .
"\n" .
"layout: " .
$object["layout"] .
"\n" .
"tags: " .
$object["tags"] .
"\n" .
"author: " .
$object["author"] .
"\n" .
"created: " .
$object["created"] .
"\n" .
"updated: " .
$object["updated"] .
"\n" .
"deleted: " .
$object["deleted"] .
"\n" .
"slug: " .
$object["slug"] .
"\n" .
"menu: " .
$object["menu"] .
"\n" .
"published: " .
$object["published"] .
"\n" .
"featured: " .
$object["featured"] .
"\n---\n" .
$object["content"];
return $markdown;
}
}

144
brain/utility/DocTools.php Normal file
View file

@ -0,0 +1,144 @@
<?php
namespace brain\utility;
class DocTools
{
public function __construct()
{
}
public static function writePages($task, $path, $fileLocation, $fileContents)
{
try {
if ($task == 'create') {
if (!is_dir('../content/pages/' . $path)) {
//Directory does not exist, so lets create it.
mkdir('../content/pages/' . $path, 0755, true);
}
file_put_contents($fileLocation, $fileContents);
} else {
($new = fopen($fileLocation, 'w')) or die('Unable to open file!');
fwrite($new, $fileContents);
fclose($new);
}
return true;
} catch (Error $error) {
return false;
}
}
public static function writeSettings($fileLocation, $fileContents)
{
if (!is_file($fileLocation)) {
file_put_contents($fileLocation, json_encode($fileContents));
} else {
($new = fopen($fileLocation, 'w')) or die('Unable to open file!');
fwrite($new, json_encode($fileContents));
fclose($new);
}
}
public static function writeHTML($location, $html, $path = null)
{
if ($path != null) {
if (!is_dir($path)) {
//Directory does not exist, so lets create it.
mkdir($path, 0755, true);
}
}
if (!is_file($location)) {
file_put_contents($location, $html);
} else {
($new = fopen($location, 'w')) or die('Unable to open file!');
fwrite($new, $html);
fclose($new);
}
}
public static function deleteFolder($path)
{
if (!empty($path) && is_dir($path)) {
$dir = new \RecursiveDirectoryIterator(
$path,
\RecursiveDirectoryIterator::SKIP_DOTS
); //upper dirs are not included,otherwise DISASTER HAPPENS :)
$files = new \RecursiveIteratorIterator(
$dir,
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $f) {
if (is_file($f)) {
unlink($f);
} else {
$empty_dirs[] = $f;
}
}
if (!empty($empty_dirs)) {
foreach ($empty_dirs as $eachDir) {
rmdir($eachDir);
}
}
rmdir($path);
}
}
public static function objectToMD($object)
{
$markdown = "---\n" .
'id: ' .
$object['id'] .
"\n" .
'uuid: ' .
$object['uuid'] .
"\n" .
'title: ' .
"'" .
$object['title'] .
"'" .
"\n" .
'feature: ' .
$object['imageList'] .
"\n" .
'files: ' .
$object['fileList'] .
"\n" .
'path: ' .
$object['path'] .
"\n" .
'layout: ' .
$object['layout'] .
"\n" .
'tags: ' .
$object['tags'] .
"\n" .
'author: ' .
$object['author'] .
"\n" .
'created: ' .
$object['created'] .
"\n" .
'updated: ' .
$object['updated'] .
"\n" .
'deleted: ' .
$object['deleted'] .
"\n" .
'slug: ' .
$object['slug'] .
"\n" .
'menu: ' .
$object['menu'] .
"\n" .
'published: ' .
$object['published'] .
"\n" .
'featured: ' .
$object['featured'] .
"\n---\n" .
$object['content'];
return $markdown;
}
}

View file

@ -1,32 +0,0 @@
<?php
use Psr\Http\Message\UploadedFileInterface;
//include "brain/data/Auth.inc.php";
define("MAXIMUM_FILESIZE", "10485760"); //10 MB
class FileUploader
{
public static function uploadFile(string $directory, $file)
{
$response = [];
try {
if (!is_dir($directory)) {
//Directory does not exist, so lets create it.
mkdir($directory, 0755, true);
}
//$upload = move_uploaded_file($file->getClientFileName(), $directory);
//$extension = pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
// see http://php.net/manual/en/function.random-bytes.php
//$basename = bin2hex(random_bytes(8));
//$filename = sprintf("%s.%0.8s", $basename, $extension);
//echo "**FILE** " . $file->getClientFileName();
$file->moveTo($directory . "/" . $file->getClientFileName());
} catch (RuntimeException $e) {
echo "ERROR " . $e->getMessage();
//echo "failed to upload image: " . $e->getMessage();
//throw new Error("Failed to upload image file");
}
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace brain\utility;
class FileUploader
{
public static function uploadFile(string $directory, $file)
{
$response = [];
try {
if (!is_dir($directory)) {
// Directory does not exist, so lets create it.
mkdir($directory, 0755, true);
}
// $upload = move_uploaded_file($file->getClientFileName(), $directory);
// $extension = pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
// see http://php.net/manual/en/function.random-bytes.php
// $basename = bin2hex(random_bytes(8));
// $filename = sprintf("%s.%0.8s", $basename, $extension);
// echo "**FILE** " . $file->getClientFileName();
$file->moveTo($directory . '/' . urlencode($file->getClientFileName()));
} catch (RuntimeException $e) {
echo 'ERROR ' . $e->getMessage();
// echo "failed to upload image: " . $e->getMessage();
// throw new Error("Failed to upload image file");
}
}
}

View file

@ -1,51 +0,0 @@
<?php
class handleCors
{
public function __construct()
{
//check settings to see if external api access is allowed
$config = new Settings();
$settings = $config->getSettings();
if ($settings["global"]["externalAPI"]) {
//echo "API STATUS: " . $settings["global"]["externalAPI"];
if ($settings["global"]["externalAPI"] == "true") {
//echo "API ACCESS ACTIVE";
// checks to see if origin is set
if (isset($_SERVER["HTTP_ORIGIN"])) {
// You can decide if the origin in $_SERVER['HTTP_ORIGIN'] is something you want to allow, or as we do here, just allow all
header("Access-Control-Allow-Origin: {$_SERVER["HTTP_ORIGIN"]}");
} else {
//No HTTP_ORIGIN set, so we allow any. You can disallow if needed here
//never allow just any domain, so turn CORS off if no No HTTP_ORIGIN is set
//header("Access-Control-Allow-Origin: *");
}
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Max-Age: 600"); // cache for 10 minutes
if ($_SERVER["REQUEST_METHOD"] == "OPTIONS") {
if (isset($_SERVER["HTTP_ACCESS_CONTROL_REQUEST_METHOD"])) {
header(
"Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT"
);
} //Make sure you remove those you do not want to support
if (isset($_SERVER["HTTP_ACCESS_CONTROL_REQUEST_HEADERS"])) {
header(
"Access-Control-Allow-Headers: {$_SERVER["HTTP_ACCESS_CONTROL_REQUEST_HEADERS"]}"
);
}
//Just exit with 200 OK with the above headers for OPTIONS method
exit(0);
}
} else {
//echo "API ACCESS ACTIVE";
}
} else {
//value doesn't exist, so whatevs
//echo "API ACCESS VALUE NOT PRESENT";
}
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace brain\utility;
use brain\data\Settings;
class HandleCors
{
public function __construct()
{
//look to see if settings file exists. kinda important
if (file_exists('../config/settings.json')) {
//check settings to see if external api access is allowed
$config = new Settings();
$settings = $config->getSettings();
if ($settings['global']['externalAPI']) {
//echo "API STATUS: " . $settings["global"]["externalAPI"];
if ($settings['global']['externalAPI'] == 'true') {
//echo "API ACCESS ACTIVE";
// checks to see if origin is set
if (isset($_SERVER['HTTP_ORIGIN'])) {
// You can decide if the origin in $_SERVER['HTTP_ORIGIN']
//is something you want to allow, or as we do here, just allow all
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
} else {
//No HTTP_ORIGIN set, so we allow any. You can disallow if needed here
//never allow just any domain, so turn CORS off if no No HTTP_ORIGIN is set
//header("Access-Control-Allow-Origin: *");
}
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 600'); // cache for 10 minutes
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
header(
'Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT'
);
} //Make sure you remove those you do not want to support
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
header(
"Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"
);
}
//Just exit with 200 OK with the above headers for OPTIONS method
exit(0);
}
} else {
//echo "API ACCESS ACTIVE";
}
} else {
//value doesn't exist, so whatevs
//echo "API ACCESS VALUE NOT PRESENT";
}
} else {
//init state, so chill
}
}
}

View file

@ -1,94 +0,0 @@
<?php
use Slim\Views\Twig;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
class Mailer
{
public static function sendMail($body)
{
$config = new Settings();
$settings = $config->getSettings();
$mailConfig = $settings["email"];
$mail = new PHPMailer();
switch ($body["mail_task"]) {
case "TESTING":
$html =
"<h1>Hi! It's Fipamo!</h1><br>" .
"<strong>It's just a test</strong><br>" .
$body["content"];
$member = Session::get("member");
$mail->addAddress($member["email"], ""); //pull email address from current user
$mail->Subject = "A test email";
break;
case "SEND_SECRET":
$html =
"<h1>Hi! It's Fipamo!</h1><br>" .
"<strong>This is your secret key.</strong><br><br>" .
"<h3>" .
$body["secret"] .
"</h3>" .
"<br> Use this key to reset your password.";
$mail->addAddress($body["email"], ""); //pull email address from current user
$mail->Subject = "Shhhh! It's a secret!";
break;
default:
return $result = [
"type" => "noMailService",
"message" => "Mail task is undefined. What are you doing??",
];
break;
}
//set values based on current active protocol
switch ($mailConfig["active"]) {
case "option-smtp":
$mail->setFrom($mailConfig["smtp"]["email"], "System Email");
$mail->Host = "playvicio.us";
$mail->Username = $mailConfig["smtp"]["email"];
$mail->Password = $mailConfig["smtp"]["password"];
break;
case "option-mg":
$mail->setFrom($mailConfig["mailgun"]["domain"], "No Reply");
$mail->Host = "smtp.mailgun.org";
$mail->Username = $mailConfig["mailgun"]["domain"];
$mail->Password = $mailConfig["mailgun"]["key"];
break;
default:
//no mail service
return $result = [
"type" => "noMailService",
"message" => "Mail is not configured. Handle that.",
];
break;
}
$mail->Body = $html;
$mail->IsHTML(true);
$mail->isSMTP();
$mail->SMTPAuth = true;
$mail->SMTPSecure = "ssl";
$mail->Port = 465;
// Uncomment for debug info
//$mail->SMTPDebug = 4;
/* Finally send the mail. */
try {
$mail->send();
$result = ["type" => "mailSent", "message" => "Message Away!"];
} catch (Exception $e) {
//echo $e->errorMessage();
$result = [
"type" => "mailNotSent",
"message" => "Message Not Away!",
"error" => $e->errorMessage(),
];
}
return $result;
}
}

95
brain/utility/Mailer.php Normal file
View file

@ -0,0 +1,95 @@
<?php
namespace brain\utility;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use brain\data\Settings;
use brain\data\Session;
class Mailer
{
public static function sendMail($body)
{
$config = new Settings();
$settings = $config->getSettings();
$mailConfig = $settings['email'];
$mail = new PHPMailer();
switch ($body['mail_task']) {
case 'TESTING':
$html = "<h1>Hi! It's Fipamo!</h1><br>" .
"<strong>It's just a test</strong><br>" .
$body['content'];
$member = Session::get('member');
$mail->addAddress($member['email'], ''); //pull email address from current user
$mail->Subject = 'A test email';
break;
case 'SEND_SECRET':
$html = "<h1>Hi! It's Fipamo!</h1><br>" .
'<strong>This is your secret key.</strong><br><br>' .
'<h3>' .
$body['secret'] .
'</h3>' .
'<br> Use this key to reset your password.';
$mail->addAddress($body['email'], ''); //pull email address from current user
$mail->Subject = "Shhhh! It's a secret!";
break;
default:
return $result = [
'type' => 'noMailService',
'message' => 'Mail task is undefined. What are you doing??',
];
break;
}
//set values based on current active protocol
switch ($mailConfig['active']) {
case 'option-smtp':
$mail->setFrom($mailConfig['smtp']['email'], 'System Email');
$mail->Host = 'playvicio.us';
$mail->Username = $mailConfig['smtp']['email'];
$mail->Password = $mailConfig['smtp']['password'];
break;
case 'option-mg':
$mail->setFrom($mailConfig['mailgun']['domain'], 'No Reply');
$mail->Host = 'smtp.mailgun.org';
$mail->Username = $mailConfig['mailgun']['domain'];
$mail->Password = $mailConfig['mailgun']['key'];
break;
default:
//no mail service
return $result = [
'type' => 'noMailService',
'message' => 'Mail is not configured. Handle that.',
];
break;
}
$mail->Body = $html;
$mail->IsHTML(true);
$mail->isSMTP();
$mail->SMTPAuth = true;
$mail->SMTPSecure = 'ssl';
$mail->Port = 465;
// Uncomment for debug info
//$mail->SMTPDebug = 4;
/* Finally send the mail. */
try {
$mail->send();
$result = ['type' => 'mailSent', 'message' => 'Message Away!'];
} catch (Exception $e) {
//echo $e->errorMessage();
$result = [
'type' => 'mailNotSent',
'message' => 'Message Not Away!',
'error' => $e->errorMessage(),
];
}
return $result;
}
}

View file

@ -1,99 +0,0 @@
<?php
class Maintenance
{
public function __construct()
{
}
public static function makeBackup()
{
//make sure back directory is there
if (!is_dir("../config/backups")) {
mkdir("../config/backups", 0755, true);
}
//creat backup zip
$zip = new ZipArchive();
$zip->open(
"../config/backups/latest_back.zip",
ZipArchive::CREATE | ZipArchive::OVERWRITE
);
//gather data and path info for md pages
$pagePath = "../content/pages";
$yearPaths = glob($pagePath . "/*", GLOB_ONLYDIR);
foreach ($yearPaths as $years) {
$year = explode("/", $years);
//grap the index and save it
if (trim($year[3]) == "start") {
$options = [
"add_path" => "content/pages/" . $year[3] . "/",
"remove_all_path" => true,
];
$zip->addGlob($years . "/*.md", GLOB_BRACE, $options);
}
$monthsPath = glob($pagePath . "/" . $year[3] . "/*", GLOB_ONLYDIR);
foreach ($monthsPath as $months) {
$month = explode("/", $months);
//once info is collected, add md pages to zip
$options = [
"add_path" => "content/pages/" . $year[3] . "/" . $month[4] . "/",
"remove_all_path" => true,
];
$zip->addGlob($months . "/*.md", GLOB_BRACE, $options);
}
}
//gather data and path info for blog images
$blogImagesPath = "../public/assets/images/blog";
$yearPaths = glob($blogImagesPath . "/*", GLOB_ONLYDIR);
foreach ($yearPaths as $years) {
$year = explode("/", $years);
$monthsPath = glob($blogImagesPath . "/" . $year[5] . "/*", GLOB_ONLYDIR);
foreach ($monthsPath as $months) {
$month = explode("/", $months);
//once info is collected, add images pages to zip
$options = [
"add_path" =>
"public/assets/images/blog/" . $year[5] . "/" . $month[6] . "/",
"remove_all_path" => true,
];
$zip->addGlob($months . "/*.*", GLOB_BRACE, $options);
}
}
//gather data and path info for user images
$userImagesPath = "../public/assets/images/user";
$yearPaths = glob($userImagesPath . "/*", GLOB_ONLYDIR);
foreach ($yearPaths as $years) {
$year = explode("/", $years);
$monthsPath = glob($userImagesPath . "/" . $year[5] . "/*", GLOB_ONLYDIR);
foreach ($monthsPath as $months) {
$month = explode("/", $months);
//once info is collected, add images pages to zip
$options = [
"add_path" =>
"public/assets/images/user/" . $year[5] . "/" . $month[6] . "/",
"remove_all_path" => true,
];
$zip->addGlob($months . "/*.*", GLOB_BRACE, $options);
}
}
//add directory for settings and save them
$zip->addEmptyDir("settings");
$zip->addFile("../config/settings.json", "settings/settings.json");
$zip->addFile("../config/folks.json", "settings/folks.json");
$zip->addFile("../config/tags.json", "settings/tags.json");
//save zip file
$zip->close();
//update settings file with latest back up date
$updated = new \Moment\Moment();
Settings::updateGlobalData(
"last_backup",
$updated->format("Y-m-d\TH:i:sP")
);
$result = ["message" => "Backup created. THIS IS A SAFE SPACE!"];
return $result;
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace brain\utility;
use brain\data\Settings;
use Carbon\Carbon;
class Maintenance
{
public function __construct()
{
}
public static function makeBackup()
{
//make sure back directory is there
if (!is_dir('../config/backups')) {
mkdir('../config/backups', 0755, true);
}
//creat backup zip
$zip = new \ZipArchive();
$zip->open(
'../config/backups/latest_back.zip',
\ZipArchive::CREATE | \ZipArchive::OVERWRITE
);
//gather data and path info for md pages
$pagePath = '../content/pages';
$yearPaths = glob($pagePath . '/*', GLOB_ONLYDIR);
foreach ($yearPaths as $years) {
$year = explode('/', $years);
//grap the index and save it
if (trim($year[3]) == 'start') {
$options = [
'add_path' => 'content/pages/' . $year[3] . '/',
'remove_all_path' => true,
];
$zip->addGlob($years . '/*.md', GLOB_BRACE, $options);
}
$monthsPath = glob($pagePath . '/' . $year[3] . '/*', GLOB_ONLYDIR);
foreach ($monthsPath as $months) {
$month = explode('/', $months);
//once info is collected, add md pages to zip
$options = [
'add_path' => 'content/pages/' . $year[3] . '/' . $month[4] . '/',
'remove_all_path' => true,
];
$zip->addGlob($months . '/*.md', GLOB_BRACE, $options);
}
}
//gather data and path info for blog images
$blogImagesPath = '../public/assets/images/blog';
$yearPaths = glob($blogImagesPath . '/*', GLOB_ONLYDIR);
foreach ($yearPaths as $years) {
$year = explode('/', $years);
$monthsPath = glob($blogImagesPath . '/' . $year[5] . '/*', GLOB_ONLYDIR);
foreach ($monthsPath as $months) {
$month = explode('/', $months);
//once info is collected, add images pages to zip
$options = [
'add_path' => 'public/assets/images/blog/' . $year[5] . '/' . $month[6] . '/',
'remove_all_path' => true,
];
$zip->addGlob($months . '/*.*', GLOB_BRACE, $options);
}
}
//gather data and path info for user images
$userImagesPath = '../public/assets/images/user';
$yearPaths = glob($userImagesPath . '/*', GLOB_ONLYDIR);
foreach ($yearPaths as $years) {
$year = explode('/', $years);
$monthsPath = glob($userImagesPath . '/' . $year[5] . '/*', GLOB_ONLYDIR);
foreach ($monthsPath as $months) {
$month = explode('/', $months);
//once info is collected, add images pages to zip
$options = [
'add_path' => 'public/assets/images/user/' . $year[5] . '/' . $month[6] . '/',
'remove_all_path' => true,
];
$zip->addGlob($months . '/*.*', GLOB_BRACE, $options);
}
}
//add directory for settings and save them
$zip->addEmptyDir('settings');
$zip->addFile('../config/settings.json', 'settings/settings.json');
$zip->addFile('../config/folks.json', 'settings/folks.json');
$zip->addFile('../config/tags.json', 'settings/tags.json');
//save zip file
$zip->close();
//update settings file with latest back up date
$updated = Carbon::now();
Settings::updateGlobalData(
'last_backup',
$updated->format("Y-m-d\TH:i:sP")
);
$result = ['message' => 'Backup created. THIS IS A SAFE SPACE!'];
return $result;
}
}

View file

@ -1,201 +0,0 @@
<?php
use function _\find;
class SetUp
{
public static function status()
{
if (file_exists("../config/settings.json")) {
return true;
} else {
return false;
}
}
public static function init($body)
{
//grab template files
$newFolks = json_decode(
file_get_contents("../config/init/folks-template.json"),
true
);
$newSettings = json_decode(
file_get_contents("../config/init/settings-template.json"),
true
);
//get form values
//$body = $request->getParsedBody();
$handle = $body["new_member_handle"];
$email = $body["new_member_email"];
$pass = $body["new_member_pass"];
$title = $body["new_member_title"];
$now = new \Moment\Moment();
//setup folks config
$hash = password_hash($pass, PASSWORD_DEFAULT);
$newFolks[0]["id"] = 0;
$newFolks[0]["handle"] = $handle;
$newFolks[0]["email"] = $email;
$newFolks[0]["password"] = $hash;
$newFolks[0]["key"] = password_hash($email, PASSWORD_DEFAULT);
$newFolks[0]["secret"] = StringTools::randomString(12);
$newFolks[0]["role"] = "hnic";
$newFolks[0]["created"] = $now->format("Y-m-d\TH:i:sP");
$newFolks[0]["updated"] = $now->format("Y-m-d\TH:i:sP");
//set up settings config
$newSettings["global"]["title"] = $title;
//create index file
//$rightNow = $now->format("Y-m-d\TH:i:sP");
//var_dump($now->format("Y-m-d\TH:i:sP"));
$index = [
"id" => 1,
"uuid" => StringTools::createUUID(),
"title" => "FIRST!",
"feature" => "/assets/images/global/default-bg.jpg",
"path" => "content/pages/start",
"layout" => "index",
"tags" => "start, welcome",
"author" => $handle,
"created" => $now->format("Y-m-d\TH:i:sP"),
"updated" => $now->format("Y-m-d\TH:i:sP"),
"deleted" => "false",
"slug" => "first",
"menu" => "false",
"featured" => "false",
"published" => "true",
"content" =>
"# F**k Yes \n\nIf you're seeing this, you're up and running. NICE WORK!\n\nFrom here, feel free to start dropping pages to your heart's content.\n\nFor some tips about using Fipamo, check out the ![docs](https://code.playvicio.us/Are0h/Fipamo/wiki/02-Usage)\n\nAll good? Feel free to edit this page to whatever you want!\n\nYOU'RE THE CAPTAIN NOW.",
];
$freshIndex = DocTools::objectToMD($index);
//once all files created, write down
DocTools::writeSettings("../config/settings.json", $newSettings);
DocTools::writeSettings("../config/folks.json", $newFolks);
DocTools::writeSettings("../config/tags.json", []);
DocTools::writePages(
"create",
"start",
"../content/pages/start/index.md",
$freshIndex
);
//if there is an older session file, get rid of it
if (is_file("../content/.session")) {
unlink("../content/.session");
}
$result = ["type" => "blogInitGood", "message" => "Site Created"];
return $result;
}
public static function restore($request)
{
$result = [
"type" => "requestLame",
"message" => "Still working on it.",
];
$body = $request->getParsedBody();
$backup = $request->getUploadedFiles();
$file = $backup["backup-upload"];
$name = $file->getClientFileName();
//park it so it can be read
$file->moveTo("../content" . "/" . $name);
//open it and get files to verify user
$zip = new ZipArchive();
if ($zip->open("../content" . "/" . $name) === true) {
$folks = json_decode($zip->getFromName("settings/folks.json"), true);
$found = find($folks, ["handle" => $body["restore_member_handle"]]);
//if member is found in back up, check pass
if ($found) {
if (password_verify($body["restore_member_pass"], $found["password"])) {
//backup verified, restore site
//set new secret key for older folks configs
$newFolks = [];
if (!isset($found["secret"])) {
$found["secret"] = StringTools::randomString(12);
}
array_push($newFolks, $found);
//dump files in folder
$zip->extractTo("../content");
//move to appropriate spots
rename(
"../content/settings/settings.json",
"../config/settings.json"
);
//rename("../content/settings/folks.json", "../config/folks.json");
DocTools::writeSettings("../config/folks.json", $newFolks);
rename("../content/settings/tags.json", "../config/tags.json");
rename(
"../content/public/assets/images/blog",
"../public/assets/images/blog"
);
rename(
"../content/public/assets/images/user",
"../public/assets/images/user"
);
rename("../content/content/pages/", "../content/pages");
//legacy check for old file structure
if (is_file("../content/pages/index.md")) {
if (!is_dir("../content/pages/start")) {
//Directory does not exist, so lets create it.
mkdir("../content/pages/start", 0755, true);
//move start page to appropriate spot
rename(
"../content/pages/index.md",
"../content/pages/start/index.md"
);
}
} else {
//chill
}
//clean up
DocTools::deleteFolder("../content/settings");
DocTools::deleteFolder("../content/public");
DocTools::deleteFolder("../content/content");
$result = [
"type" => "requestGood",
"message" => "Site Restored! Redirecting",
];
} else {
$result = [
"type" => "requestLame",
"message" => "Check that password, champ.",
];
}
} else {
$result = [
"type" => "requestLame",
"message" => "No member found by that name, hoss",
];
}
$zip->close();
$zipPath = "../content/" . $name;
//trash zip when done
unlink($zipPath);
} else {
$result = [
"type" => "requestLame",
"message" => "Could not open backup. RATS!",
];
}
return $result;
}
}

230
brain/utility/Setup.php Normal file
View file

@ -0,0 +1,230 @@
<?php
namespace brain\utility;
use Carbon\Carbon;
use function _\find;
class SetUp
{
public static function status()
{
if (file_exists('../config/settings.json')) {
return true;
} else {
return false;
}
}
public static function init($body)
{
//grab template files
$newFolks = json_decode(
file_get_contents('../config/init/folks-template.json'),
true
);
$newSettings = json_decode(
file_get_contents('../config/init/settings-template.json'),
true
);
//get form values
//$body = $request->getParsedBody();
$handle = $body['new_member_handle'];
$email = $body['new_member_email'];
$pass = $body['new_member_pass'];
$title = $body['new_member_title'];
$now = Carbon::now();
//setup folks config
$hash = password_hash($pass, PASSWORD_DEFAULT);
$newFolks[0]['id'] = 0;
$newFolks[0]['handle'] = $handle;
$newFolks[0]['email'] = $email;
$newFolks[0]['password'] = $hash;
$newFolks[0]['key'] = password_hash($email, PASSWORD_DEFAULT);
$newFolks[0]['secret'] = StringTools::randomString(12);
$newFolks[0]['role'] = 'hnic';
$newFolks[0]['created'] = $now->format("Y-m-d\TH:i:sP");
$newFolks[0]['updated'] = $now->format("Y-m-d\TH:i:sP");
//set up settings config
$newSettings['global']['title'] = $title;
//create index file
//$rightNow = $now->format("Y-m-d\TH:i:sP");
//var_dump($now->format("Y-m-d\TH:i:sP"));
$index = [
'id' => 1,
'uuid' => StringTools::createUUID(),
'title' => 'FIRST!',
'imageList' => '/assets/images/global/default-bg.jpg',
'fileList' => '',
'path' => 'content/pages/start',
'layout' => 'index',
'tags' => 'start, welcome',
'author' => $handle,
'created' => $now->format("Y-m-d\TH:i:sP"),
'updated' => $now->format("Y-m-d\TH:i:sP"),
'deleted' => 'false',
'slug' => 'first',
'menu' => 'false',
'featured' => 'false',
'published' => 'true',
'content' => "# F**k Yes \n\nIf you're seeing this, you're up and running. NICE WORK!\n\nFrom here, feel free to start dropping pages to your heart's content.\n\nFor some tips about using Fipamo, check out the ![docs](https://code.playvicio.us/Are0h/Fipamo/wiki/02-Usage)\n\nAll good? Feel free to edit this page to whatever you want!\n\nYOU'RE THE CAPTAIN NOW.",
];
$freshIndex = DocTools::objectToMD($index);
//once all files created, write down
DocTools::writeSettings('../config/settings.json', $newSettings);
DocTools::writeSettings('../config/folks.json', $newFolks);
DocTools::writeSettings('../config/tags.json', []);
DocTools::writePages(
'create',
'start',
'../content/pages/start/index.md',
$freshIndex
);
//if there is an older session file, get rid of it
if (is_file('../content/.session')) {
unlink('../content/.session');
}
$result = ['type' => 'blogInitGood', 'message' => 'Site Created'];
return $result;
}
public static function restore($request)
{
$result = [
'type' => 'requestLame',
'message' => 'Still working on it.',
];
$body = $request->getParsedBody();
$backup = $request->getUploadedFiles();
$file = $backup['backup-upload'];
//NOTE: If this fails check 'post_max_size' in php.ini
$size = $file->getSize();
$name = $file->getClientFileName();
//park it so it can be read
$file->moveTo('../content' . '/' . $name);
//open it and get files to verify user
$zip = new \ZipArchive();
if ($zip->open('../content' . '/' . $name) === true) {
$folks = json_decode($zip->getFromName('settings/folks.json'), true);
$found = find($folks, ['handle' => $body['restore_member_handle']]);
//if member is found in back up, check pass
if ($found) {
if (password_verify($body['restore_member_pass'], $found['password'])) {
//backup verified, restore site
//set new secret key for older folks configs
$newFolks = [];
if (!isset($found['secret'])) {
$found['secret'] = StringTools::randomString(12);
}
array_push($newFolks, $found);
//dump files in folder
$zip->extractTo('../content');
//move to appropriate spots
/*
rename(
"../content/settings/settings.json",
"../config/settings.json"
);
*/
//load up old config file
$newConfig = json_decode(
file_get_contents('../content/settings/settings.json'),
true
);
//check for key, add if not there
if (!isset($newConfig['global']['externalAPI'])) {
$newConfig['global']['externalAPI'] = 'false';
}
//write new config file
DocTools::writeSettings('../config/settings.json', $newConfig);
//rename("../content/settings/folks.json", "../config/folks.json");
DocTools::writeSettings('../config/folks.json', $newFolks);
rename('../content/settings/tags.json', '../config/tags.json');
//images path for blog and user
$blogImagePath = '../public/assets/images/blog';
$userImagePath = '../public/assets/images/user';
//check to see if image dirs are empty, if not chill
if ($globs = glob($blogImagePath . '/*')) {
//directory not empty, relax
} else {
rename('../content/public/assets/images/blog', $blogImagePath);
}
if ($globs = glob($userImagePath . '/*')) {
//directory not empty, relax
} else {
rename('../content/public/assets/images/user', $userImagePath);
}
rename('../content/content/pages/', '../content/pages');
//legacy check for old file structure
if (is_file('../content/pages/index.md')) {
if (!is_dir('../content/pages/start')) {
//Directory does not exist, so lets create it.
mkdir('../content/pages/start', 0755, true);
//move start page to appropriate spot
rename(
'../content/pages/index.md',
'../content/pages/start/index.md'
);
}
} else {
//chill
}
//clean up
DocTools::deleteFolder('../content/settings');
DocTools::deleteFolder('../content/public');
DocTools::deleteFolder('../content/content');
$result = [
'type' => 'requestGood',
'message' => 'Site Restored! Redirecting',
];
} else {
$result = [
'type' => 'requestLame',
'message' => 'Check that password, champ.',
];
}
} else {
$result = [
'type' => 'requestLame',
'message' => 'No member found by that name, hoss',
];
}
$zip->close();
$zipPath = '../content/' . $name;
//trash zip when done
unlink($zipPath);
} else {
$result = [
'type' => 'requestLame',
'message' => 'Could not open backup. RATS!',
];
}
return $result;
}
}

View file

@ -1,96 +0,0 @@
<?php
use function _\find;
use function _\filter;
class Sorting
{
private static $_tags = [];
private static $_archive = [];
public function __construct()
{
}
public static function tags()
{
$pages = (new Book("../content/pages"))->getContents();
foreach ($pages as $page) {
$temp = [];
$temp = explode(",", $page["tags"]);
foreach ($temp as $tag) {
$label = trim($tag);
if (!find(self::$_tags, ["tag_name" => $label])) {
array_push(self::$_tags, [
"tag_name" => $label,
"slug" => StringTools::safeString($label),
"pages" => self::tagPages($label, $pages),
]);
}
}
}
return self::$_tags;
}
private static function tagPages($tag, $pages)
{
$tagged = [];
foreach ($pages as $page) {
if (strpos($page["tags"], $tag) !== false) {
array_push($tagged, [
"title" => $page["title"],
"slug" => $page["slug"],
"path" => $page["path"],
]);
}
}
return $tagged;
}
public static function archive()
{
$pages = (new Book("../content/pages"))->getContents();
$years = [];
$archive = [];
foreach ($pages as $page) {
//$year = date("Y", date($page["rawCreated"]));
$date = explode("/", $page["path"]);
//echo $page["title"] . " : " . $year . "\n";
if (!find($years, ["year" => trim($date[0])])) {
$findPages = filter($pages, ["createdYear" => trim($date[0])]);
//var_dump($findPages);
array_push($years, [
"year" => trim($date[0]),
"count" => count($findPages),
]);
}
}
foreach ($years as $year) {
$sorted = [];
$filtered = filter($pages, ["createdYear" => $year["year"]]);
foreach ($filtered as $obj) {
$month = date("m", date($obj["rawCreated"]));
if (!find($sorted, ["month" => $month])) {
$perMonth = filter($pages, [
"path" => $year["year"] . "/" . $month,
"deleted" => false,
"published" => true,
"layout" => "page",
]);
array_push($sorted, [
"month" => $month,
"full_month" => date("F", date($obj["rawCreated"])),
"count" => count($perMonth),
"pages" => $perMonth,
]);
}
}
array_push(self::$_archive, [
"year" => $year["year"],
"year_data" => $sorted,
]);
}
return self::$_archive;
}
}

269
brain/utility/Sorting.php Normal file
View file

@ -0,0 +1,269 @@
<?php
namespace brain\utility;
use brain\data\Book;
use brain\data\Settings;
use Mni\FrontYAML\Parser;
use function _\filter;
use function _\find;
class Sorting
{
private static $p_tags = [];
private static $p_archive = [];
public function __construct()
{
}
public static function tags()
{
$pages = (new Book('../content/pages'))->getContents();
foreach ($pages as $page) {
$temp = [];
if (isset($page['tags'])) {
$temp = explode(',', $page['tags']);
foreach ($temp as $tag) {
$label = trim($tag);
if (!find(self::$p_tags, ['tag_name' => $label])) {
array_push(self::$p_tags, [
'tag_name' => $label,
'slug' => StringTools::safeString($label),
'pages' => self::tagPages($label, $pages),
]);
}
}
}
}
return self::$p_tags;
}
private static function tagPages($tag, $pages)
{
$tagged = [];
foreach ($pages as $page) {
if (isset($page['tags'])) {
if (strpos($page['tags'], $tag) !== false) {
array_push($tagged, [
'title' => $page['title'],
'slug' => $page['slug'],
'path' => $page['path'],
'feature' => $page['feature'],
]);
}
}
}
return $tagged;
}
public static function archive()
{
$pages = (new Book('../content/pages'))->getContents();
$years = [];
$archive = [];
foreach ($pages as $page) {
// $year = date("Y", date($page["rawCreated"]));
$date = explode('/', $page['path']);
// echo $page["title"] . " : " . $year . "\n";
if (!find($years, ['year' => trim($date[0])])) {
$findPages = filter($pages, ['createdYear' => trim($date[0])]);
// var_dump($findPages);
array_push(
$years,
[
'year' => trim($date[0]),
'count' => count($findPages),
]
);
}
}
foreach ($years as $year) {
$sorted = [];
$filtered = filter($pages, ['createdYear' => $year['year']]);
foreach ($filtered as $obj) {
$month = date('m', date($obj['rawCreated']));
if (!find($sorted, ['month' => $month])) {
$perMonth = filter(
$pages,
[
'path' => $year['year'] . '/' . $month,
'deleted' => false,
'published' => true,
'layout' => 'page',
]
);
array_push($sorted, [
'month' => $month,
'full_month' => date('F', date($obj['rawCreated'])),
'count' => count($perMonth),
'pages' => $perMonth,
]);
}
}
array_push(self::$p_archive, [
'year' => $year['year'],
'year_data' => $sorted,
]);
}
return self::$p_archive;
}
public static function page($page)
{
$config = new Settings();
$settings = $config->getSettings();
$pageOption = [];
$pageInfo = [
'keywords' => isset($settings['global']['keywords'])
? $settings['global']['keywords']
: 'fipamo, blog, jamstack, php, markdown, js',
'description' => $settings['global']['descriptions'],
'image' => $settings['global']['base_url'] . $settings['global']['background'],
'baseURL' => $settings['global']['base_url'],
];
$tags = [];
if (isset($page['tags'])) {
$taglist = explode(',', $page['tags']);
foreach ($taglist as $tag) {
$label = trim($tag);
array_push($tags, [
'label' => $label . ' ',
'slug' => StringTools::safeString($label),
]);
}
}
$meta = [
'who' => $page['author'],
'when' => $page['created'],
'tags' => $tags,
];
// render markdown content and clean it
$parser = new Parser();
$rendered = $parser->parse($page['content']);
$sanitizer = \HtmlSanitizer\Sanitizer::create([
'extensions' => ['basic', 'image', 'list', 'code'],
'tags' => [
'img' => [
'allowed_attributes' => ['src', 'alt', 'title', 'class'],
'allowed_hosts' => null,
],
],
]);
$preclean = $sanitizer->sanitize($rendered->getContent());
// just clean renderd string for now, Sanitize doesn't like relative img urls
// so another option is needed
$cleaned = strip_tags($rendered->getContent(), [
'a',
'br',
'p',
'strong',
'br',
'img',
'iframe',
'ul',
'li',
'i',
'em',
'h1',
'h2',
'h3',
'pre',
'code',
]);
// if page feature isn't empty, find image from list and set it as background image
// if it is empty, just use global background
if ($page['feature'] != '' || $page['feature'] != null) {
$media = explode(',', $page['feature']);
$set = false;
foreach ($media as $file) {
$item = trim($file);
$ext = pathinfo($item, PATHINFO_EXTENSION);
if ($ext != 'mp4' && !$set) {
$pageInfo['image'] = $pageInfo['baseURL'] . $item;
$set = true;
}
}
}
if ($page['layout'] == 'index') {
// $template = $this->theme . "/index.twig";
// $location = "../public/index.html";
// $dir = null;
$recent = [];
$featured = [];
$limit = 4;
$pages = (new Book())->getContents();
foreach ($pages as $item) {
if (!$item['deleted'] &&
$item['published'] &&
$item['menu'] != 'true'
) {
if (count($recent) < $limit) {
array_push($recent, [
'path' => $item['path'],
'slug' => $item['slug'],
'title' => $item['title'],
'feature' => $item['feature'],
]);
}
if ($item['featured'] == true) {
if (count($featured) < $limit) {
array_push($featured, [
'path' => $item['path'],
'slug' => $item['slug'],
'title' => $item['title'],
'feature' => $item['feature'],
]);
}
}
}
}
$pageOptions = [
'title' => $page['title'],
'background' => $page['feature'],
'content' => $page['html'], // $cleaned,
'meta' => $meta,
'recent' => $recent,
'featured' => $featured,
'info' => $pageInfo,
'menu' => $settings['menu'],
'dynamicRender' => $settings['global']['dynamicRender'],
'media' => $page['media'],
'files' => $page['docs'],
];
} else {
// $template = $this->theme . "/page.twig";
// $location = "../public/" . $page["path"] . "/" . $page["slug"] . ".html";
// $dir = "../public/" . $page["path"];
$pageOptions = [
'title' => $page['title'],
'background' => $page['feature'],
'content' => $page['html'], // $cleaned,
'meta' => $meta,
'info' => $pageInfo,
'menu' => $settings['menu'],
'dynamicRender' => $settings['global']['dynamicRender'],
'media' => $page['media'],
'files' => $page['docs'],
];
}
return $pageOptions;
}
}

View file

@ -1,130 +0,0 @@
<?php
use ReallySimpleJWT\Token;
use ReallySimpleJWT\Exception\BuildException;
use Mni\FrontYAML\Parser;
//include "brain/data/Auth.inc.php";
class StringTools
{
public static function createUUID()
{
if (function_exists("com_create_guid") === true) {
return trim(com_create_guid(), "{}");
}
return sprintf(
"%04X%04X-%04X-%04X-%04X-%04X%04X%04X",
mt_rand(0, 65535),
mt_rand(0, 65535),
mt_rand(0, 65535),
mt_rand(16384, 20479),
mt_rand(32768, 49151),
mt_rand(0, 65535),
mt_rand(0, 65535),
mt_rand(0, 65535)
);
}
public static function sanitizeContent($entry)
{
$parser = new Parser();
$rendered = $parser->parse($entry);
$sanitizer = HtmlSanitizer\Sanitizer::create([
"extensions" => ["basic", "image", "list", "code"],
"tags" => [
"img" => [
"allowed_attributes" => ["src", "alt", "title", "class"],
"allowed_hosts" => null,
],
],
]);
$preclean = $sanitizer->sanitize($rendered->getContent());
$cleaned = strip_tags($rendered->getContent(), [
"a",
"br",
"p",
"strong",
"br",
"img",
"iframe",
"ul",
"li",
"i",
"h1",
"h2",
"h3",
"pre",
"code",
]);
return $cleaned;
}
public static function safeString($string)
{
return strtolower(
trim(
preg_replace(
"~[^0-9a-z]+~i",
"_",
html_entity_decode(
preg_replace(
"~&([a-z]{1,2})(?:acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i",
'$1',
htmlentities($string, ENT_QUOTES, "UTF-8")
),
ENT_QUOTES,
"UTF-8"
)
),
"-"
)
);
}
public static function randomString(int $length)
{
$alphanum =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
$special = '*&!@%^#$';
$alphabet = $alphanum . $special;
$random = openssl_random_pseudo_bytes($length);
$alphabet_length = strlen($alphabet);
$string = "";
for ($i = 0; $i < $length; ++$i) {
$string .= $alphabet[ord($random[$i]) % $alphabet_length];
}
//secret needs to be a valid token
if ($length == 12) {
try {
$secret = Token::create(12, $string, time() + 3600, "localhost");
return $string;
} catch (BuildException $e) {
//bad secret, so try agiain
//echo "BAD STRING";
return self::randomString(12);
}
if (Token::validate($key, $string)) {
return $string;
} else {
return self::randomString(12);
}
}
}
private static function checkSpecial($string)
{
$specials = ["*", "&", "!", "@", "%", "^", "#", "$"];
$valid = false;
foreach ($specials as $item) {
if (strpos($string, $item)) {
return $valid = true;
}
}
return $valid;
}
}

View file

@ -0,0 +1,131 @@
<?php
namespace brain\utility;
use ReallySimpleJWT\Token;
use ReallySimpleJWT\Exception\BuildException;
use Mni\FrontYAML\Parser;
class StringTools
{
public static function createUUID()
{
if (function_exists('com_create_guid') === true) {
return trim(com_create_guid(), '{}');
}
return sprintf(
'%04X%04X-%04X-%04X-%04X-%04X%04X%04X',
mt_rand(0, 65535),
mt_rand(0, 65535),
mt_rand(0, 65535),
mt_rand(16384, 20479),
mt_rand(32768, 49151),
mt_rand(0, 65535),
mt_rand(0, 65535),
mt_rand(0, 65535)
);
}
public static function sanitizeContent($entry)
{
$parser = new Parser();
$rendered = $parser->parse($entry);
$sanitizer = HtmlSanitizer\Sanitizer::create([
'extensions' => ['basic', 'image', 'list', 'code'],
'tags' => [
'img' => [
'allowed_attributes' => ['src', 'alt', 'title', 'class'],
'allowed_hosts' => null,
],
],
]);
$preclean = $sanitizer->sanitize($rendered->getContent());
$cleaned = strip_tags($rendered->getContent(), [
'a',
'br',
'p',
'strong',
'br',
'img',
'iframe',
'ul',
'li',
'i',
'h1',
'h2',
'h3',
'pre',
'code',
]);
return $cleaned;
}
public static function safeString($string)
{
return strtolower(
trim(
preg_replace(
'~[^0-9a-z]+~i',
'_',
html_entity_decode(
preg_replace(
'~&([a-z]{1,2})(?:acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i',
'$1',
htmlentities($string, ENT_QUOTES, 'UTF-8')
),
ENT_QUOTES,
'UTF-8'
)
),
'-'
)
);
}
public static function randomString(int $length)
{
$alphanum = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$special = '*&!@%^#$';
$alphabet = $alphanum . $special;
$random = openssl_random_pseudo_bytes($length);
$alphabet_length = strlen($alphabet);
$string = '';
for ($i = 0; $i < $length; ++$i) {
$string .= $alphabet[ord($random[$i]) % $alphabet_length];
}
//secret needs to be a valid token
if ($length == 12) {
try {
$secret = Token::create(12, $string, time() + 3600, 'localhost');
return $string;
} catch (BuildException $e) {
//bad secret, so try agiain
//echo "BAD STRING";
return self::randomString(12);
}
if (Token::validate($key, $string)) {
return $string;
} else {
return self::randomString(12);
}
}
}
private static function checkSpecial($string)
{
$specials = ['*', '&', '!', '@', '%', '^', '#', '$'];
$valid = false;
foreach ($specials as $item) {
if (strpos($string, $item)) {
return $valid = true;
}
}
return $valid;
}
}

View file

@ -2,6 +2,8 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="theme-color" content="#FFFFFF"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
{% block title %}
{{ title }}
@ -10,51 +12,38 @@
{% block stylesheets %}{% endblock %}
</head>
<body>
<div id="notifications" class="notifications">
<div id="notifyMessage" class="notifyMessage">
<div id="notify-good" class="notify-icon">
<svg viewbox="0 0 20 20" class="icons"><use xlink:href="/assets/images/global/sprite.svg#entypo-emoji-flirt"/></svg>
</div>
<div id="notify-lame" class="notify-icon">
<svg viewbox="0 0 20 20" class="icons"><use xlink:href="/assets/images/global/sprite.svg#entypo-emoji-sad"/></svg>
</div>
<div id="notify-working" class="notify-icon">
<svg viewbox="0 0 20 20" class="icons"><use xlink:href="/assets/images/global/sprite.svg#entypo-cog"/></svg>
</div>
<p id="message-text"></p>
</div>
</div>
<div id="main-content" class="main-container">
<section id="dash-index-content">
{% if status %}
<header id="header">
<div id="wrapper">
<header>
{% apply spaceless %}
<div id="left">
<nav role="top-nav">
<div role="nav-left">
<a href="/dashboard"><img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/></a>
</div>
<div id="right">
<div role="title">
<h1>{{ title }}</h1>
</div>
<div role="nav-right">
{% if status %}
{% apply spaceless %}
{{ include("dash/partials/navigation.twig") }}
{% endapply %}
{% endif %}
</div>
</nav>
<div role="notify">
{% apply spaceless %}
{{ include("dash/partials/notifications.twig") }}
{% endapply %}
</div>
</header>
{% endapply %}
{% endif %}
</header>
<main>
{% apply spaceless %}
{% block mainContent %}{% endblock %}
{% endapply %}
</section>
</div>
</main>
<footer></footer>
{% block javascripts %}{% endblock %}
<script type="module" src="/assets/scripts/dash.js"></script>
</body>
</html>

View file

@ -5,103 +5,65 @@
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=adfdfa">
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css?=vdthg">
{% endblock %}
{% block mainContent %}
<div id="post-index">
<div id="post-index-wrapper">
<div id="post-index-header">
<div id="post-index-header-left">
{{ filter }} Pages
<section role="book-index-header">
<div role="book-index-header-left">
{{ filter }}
Pages
</div>
<div id="post-index-header-right">
<div role="book-index-header-right">
<a href="/dashboard/pages/all" title="view all pages">
<button>
<svg >
<use xlink:href="/assets/images/global/sprite.svg#entypo-archive"/>
</svg>
<i class="ti ti-clipboard-list"></i>
{{ stats['all'] }}
</button>
</a>
<a href="/dashboard/pages/published" title="view publised pages">
<button>
<svg >
<use xlink:href="/assets/images/global/sprite.svg#entypo-globe"/>
</svg>
<i class="ti ti-clipboard-check"></i>
{{ stats['published'] }}
</button>
</a>
<a href="/dashboard/pages/deleted" title="view deleted pages">
<button>
<svg >
<use xlink:href="/assets/images/global/sprite.svg#entypo-circle-with-cross"/>
</svg>
<i class="ti ti-clipboard-off"></i>
{{ stats['deleted'] }}
</button>
</a>
</div>
</div>
<div id="posts-list">
</section>
<section role="book-index-pages">
{% for page in pages %}
<a class="page-link" href="/dashboard/page/edit/{{ page.uuid }}">
<div class="page-bg" style="background: url({{ page.feature }}) no-repeat center center / cover">
<label>
{{ page.title }}
</label>
{% if page.media[0].type == 'mp4' %}
<a href="/dashboard/page/edit/{{ page.uuid }}" id="{{ page.uuid }}" class="page-link">
<div class="page-video">
<video class="post-video" loop muted autoplay>
<source src="{{ page.media[0].file }}" type="video/mp4">
Sorry, your browser doesn't support embedded videos.
</video>
<div id="meta">
<div id="options">
{% if page.menu == 'true' %}
{% set menu = "true" %}
{% else %}
{% set menu = "false" %}
{% endif %}
{% if page.published == 'true' %}
{% set published = "true" %}
{% else %}
{% set published = "false" %}
{% endif %}
{% if page.featured == 'true' %}
{% set featured = "true" %}
{% else %}
{% set featured = "false" %}
{% endif %}
<div id="option-left">
<button data-active="{{ menu }}">
<svg>
<use xlink:href="/assets/images/global/sprite.svg#entypo-add-to-list"/>
</svg>
</button>
<button data-active="{{ published }}">
<svg>
<use xlink:href="/assets/images/global/sprite.svg#entypo-globe"/>
</svg>
</button>
<button data-active="{{ featured }}">
<svg>
<use xlink:href="/assets/images/global/sprite.svg#entypo-star"/>
</svg>
</button>
</div>
<div id="option-right">
<span>
{{ page.updated }}
</span>
</div>
</div>
{{ include("dash/partials/recent-meta.twig") }}
</div>
</div>
</a>
{% else %}
<a class="page-link" href="/dashboard/page/edit/{{ page.uuid }}">
<div class="page-bg" style="background: url({{ page.media[0].file }}) no-repeat center center / cover #fc6399">
<div id="meta">
{{ include("dash/partials/recent-meta.twig") }}
</div>
</div>
</a>
{% endif %}
{% endfor %}
{% if numOfPages > 1 %}
<div class="paginate">
<div role="paginate">
<a class="page-btns" href="/dashboard/pages/{{ paginate['sort'] }}/{{ paginate['prevPage'] }}">
<svg viewbox="0 0 20 20" class="icons"><use xlink:href="/assets/images/global/sprite.svg#entypo-chevron-left"/></svg>
<i class="ti ti-square-arrow-left"></i>
</a>
<span class="count">
{{ currentPage }}
@ -109,16 +71,10 @@
{{ numOfPages }}
</span>
<a class="page-btns" href="/dashboard/pages/{{ paginate['sort'] }}/{{ paginate['nextPage'] }}">
<svg viewbox="0 0 20 20" class="icons"><use xlink:href="/assets/images/global/sprite.svg#entypo-chevron-right"/></svg>
<i class="ti ti-square-arrow-right"></i>
</a>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script src="/assets/scripts/dash.min.js" type="text/javascript"></script>
</section>
{% endblock %}

View file

@ -0,0 +1,16 @@
<div>
<a href="/dashboard">
<img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/>
</a>
</div>
<form id="init-restore" method="POST">
<input type="text" name="restore_member_handle" id="restore_member_handle" placeholder="handle"/><input type="password" name="restore_member_pass" id="restore_member_pass" placeholder="password"/>
<div>
<label>Grab your backup zip</label>
<input id="backup-upload" type="file" name="backup-upload" placeholder="Backup Zip"/>
</div>
<br/><br/>
<button id="blog-restore" data-action='blog-restore' type='submit'>RESTORE</button>
<br/><br/>
<button class="init-option" id="init-switch-fresh">OR INSTALL FROM SCRATCH</button>
</form>

View file

@ -0,0 +1,15 @@
<div>
<a href="/dashboard">
<img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/>
</a>
</div>
<form id="init-form" method="POST" onsubmit="return false;">
<input type="text" name="new_member_handle" id="new_member_handle" placeholder="handle"/>
<input type="text" name="new_member_email" id="new_member_email" placeholder="email"/>
<input type="text" name="new_member_pass" id="new_member_pass" placeholder="password"/>
<input type="text" name="new_member_pass2" id="new_member_pass2" placeholder="password confirm"/>
<input type="text" name="new_member_title" id="new_member_title" placeholder="title"/>
<button id="init-blog" data-action='blog-init' type='submit'>SET UP YOUR SITE</button>
<br/><br/>
<button class="init-option" id="init-switch-restore">RESTORE FROM BACKUP</button>
</form>

View file

@ -1,13 +1,13 @@
<div id="dash-login">
<div id="dash-form" class="dash-form">
<section role="login">
<div>
<img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/>
<form id="login" class='login' name="login" action="/@/dashboard/login" method="POST">
<input type="text" name="handle" class="form-control" placeholder="Handle" required ">
<input type="password" name="password" class="form-control" placeholder="Password" required">
<button id="login-btn" class='login-btn' type='submit'>
</div>
<form id="login" class='login' name="login" method="POST" onsubmit="return false;">
<input type="text" name="handle" class="form-control" placeholder="Handle" required/>
<input type="password" name="password" class="form-control" placeholder="Password" required/>
<button id="login-btn" class='login-btn'>
ID, PLEASE
</button><br /><br />
<a href="/dashboard/reset-password"> Forgot Password?</a>
</button>
<a href="/dashboard/reset-password">?</a>
</form>
</div>
</div>
</section>

View file

@ -0,0 +1,21 @@
<div>
<a href="/dashboard">
<img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/>
</a>
</div>
<form id="reset" class='login' name="reset" action="/dashboard/login" method="POST">
<input type="password" id="new_password" name="new_password" class="form-control" placeholder="New Password" required/>
<input type="password" id="new_password2" name="new_password2" class="form-control" placeholder="New Password Confirm" required">
<input type="password" id="secret" name="secret" class="form-control" placeholder="Account Secret" required/>
<button id="reset-btn" class='login-btn' type='submit'>
RESET PASSWORD
</button><br/>
<p>
Use this to get your secret to verify it's you. If your email is set up, the secret will be sent there. If not, the form will be updated automatically(but please set up your email, once you reset your password).
</p>
<input type="text" id="email" name="email" class="form-control" placeholder="email to verify" required/>
<button id="get-secret-btn" class='login-btn' type='submit'>
VERIFY EMAIL
</button><br/><br/>
</form>

View file

@ -5,44 +5,20 @@
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=adfa">
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css">
{% endblock %}
{% block mainContent %}
<div id="dash-index">
<div id="dash-index-wrapper">
<div id="dash-init" class="dash-init">
<form id="init-form">
<img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/>
<input type="text" name="new_member_handle" id="new_member_handle" placeholder="handle"/>
<input type="text" name="new_member_email" id="new_member_email" placeholder="email"/>
<input type="text" name="new_member_pass" id="new_member_pass" placeholder="password"/>
<input type="text" name="new_member_pass2" id="new_member_pass2" placeholder="password confirm"/>
<input type="text" name="new_member_title" id="new_member_title" placeholder="title"/>
<button id="init-blog" data-action='blog-init' type='submit'>SET UP YOUR SITE</button>
<br /><br />
<button class="init-option" id="init-switch-restore">RESTORE FROM BACKUP</button>
</form>
</div>
<div id="dash-restore" class="dash-restore">
<form id="init-restore">
<img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/>
<input type="text" name="restore_member_handle" id="restore_member_handle" placeholder="handle"/><input type="text" name="restore_member_pass" id="restore_member_pass" placeholder="password"/>
<div>
<label>Grab your backup zip</label>
<input id="backup-upload" type="file" name="backup-upload" placeholder="Backup Zip"/>
</div>
<br /><br />
<button id="blog-restore" data-action='blog-restore' type='submit'>RESTORE</button>
<br /><br />
<button class="init-option" id="init-switch-fresh">OR INSTALL FROM SCRATCH</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script src="/assets/scripts/dash.min.js" type="text/javascript"></script>
<article role="site-restore">
<section role="restore-fresh">
{% apply spaceless %}
{{ include("dash/forms/init-fresh.twig") }}
{% endapply %}
</section>
<section role="restore-backup">
{% apply spaceless %}
{{ include("dash/forms/init-backup.twig") }}
{% endapply %}
</section>
</article>
{% endblock %}

View file

@ -5,39 +5,26 @@
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=sdsdsds">
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css?=sdsdsds">
{% endblock %}
{% block mainContent %}
<div id="nav-index">
<div id="nav-index-wrapper">
<div id="nav-pages">
<article role="navigation">
<section id="nav-items">
{% for item in menu %}
<div id="{{ item.id }}" class="nav-item" data-slug="{{ item.slug }}" data-uuid="{{ item.uuid }}" data-path="{{ item.path }}">
<svg id="item-arrows">
<use xlink:href="/assets/images/global/sprite.svg#entypo-select-arrows"/>
</svg>
<i class="ti ti-arrows-move-vertical"></i>
<label>{{ item.title }}</label>
<div id="nav-btns">
<button id="edit-item" class="nav-btn" data-id="{{ item.uuid }}" title="edit page">
<svg>
<use xlink:href="/assets/images/global/sprite.svg#entypo-edit"/>
</svg>
<i class="ti ti-edit"></i>
</button>
<button id="remove-item" class="nav-btn" data-uuid="{{ item.uuid }}" data-id="{{ item.id }}" title="delete from menu">
<svg>
<use xlink:href="/assets/images/global/sprite.svg#entypo-cross"/>
</svg>
<i class="ti ti-x"></i>
</button>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script src="/assets/scripts/dash.min.js" type="text/javascript"></script>
</section>
</article>
{% endblock %}

View file

@ -9,10 +9,12 @@
{% set slug = page['slug'] %}
{% set layout = page['layout'] %}
{% set feature = page['feature'] %}
{% set _title = page['title'] %}
{% set tags = page['tags'] %}
{% set content = page['content'] %}
{% set date = page['created'] %}
{% set updated = page['updated'] %}
{% set media = page['media'] %}
{% set files = page['docs'] %}
{% else %}
{% set id = '' %}
{% set uuid = '' %}
@ -23,6 +25,9 @@
{% set tags = '' %}
{% set content = '' %}
{% set date = '' %}
{% set updated = '' %}
{% set media = '' %}
{% set files = '' %}
{% endif %}
{% block title %}
@ -30,69 +35,172 @@
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=adfdf">
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css?=vdthg">
{% endblock %}
{% block mainContent %}
<div id="post-edit-index" data-index="{{ id }}" data-uuid="{{ uuid }}" data-slug="{{ slug }}" data-layout="{{ layout }}">
<div id="post-edit-index-wrapper">
<div id="post-feature">
<section data-index="{{ id }}" data-uuid="{{ uuid }}" data-slug="{{ slug }}" data-layout="{{ layout }}" role="file-manager">
{% if page['feature'] == null %}
<div id="featured-image-drop">
DRAG AND DROP IMAGE OR <label for="featured-image-upload">CLICK TO CHOSE</label>
<div role="file-drop">
<label for="page-files-upload">DRAG AND DROP FILES OR CLICK TO SELECT</label>
</div>
<label role="list-title">IMAGES AND VIDEO</label>
<div role="page-images-list"></div>
<label role="list-title">FILES</label>
<div role="page-files-list"></div>
{% else %}
<div id="featured-new-image-btn">
<button id="new-feature-upload">
<svg id="new-feature-upload" viewbox="0 0 20 20" class="icons"><use xlink:href="/assets/images/global/sprite.svg#entypo-image-inverted"/></svg>
<div role="file-drop">
<label for="page-files-upload">DRAG AND DROP FILES OR CLICK TO SELECT</label>
</div>
<label role="list-title">IMAGES AND VIDEO</label>
<div role="page-images-list">
{% if media|length > 1 %}
{% for item in media %}
{% set fileName = item.file|split('/') %}
{% if item.type == "mp4" %}
<div id="{{ loop.index0 }}" class="video-item" data-source="{{ item.file }}">
<video>
<source src="{{ item.file }}"/>
</video>
<button id="{{ loop.index0 }}" class="media-remove">
<i class="ti ti-x"></i>
</button>
</div>
<div id="featured-image-drop">
<img id="featured-image" src="{{ feature }}"/>
{% else %}
<div id="{{ loop.index0 }}" class="img-item" data-source="{{ item.file }}" style="background: url({{ item.file }}) no-repeat center center / cover">
<button id="{{ loop.index0 }}" class="media-remove">
<i class="ti ti-x"></i>
</button>
</div>
{% endif %}
{% endfor %}
{% else %}
{% if media[0] != '' %}
{% set fileName = media[0].file|split('/') %}
{% if media[0].type == "mp4" %}
<div id="0" class="video-item" data-source="{{ media[0].file }}">
<button id="{{ loop.index0 }}" class="media-remove">X</button>
</div>
{% else %}
<div id="0" class="img-item" data-source="{{ media[0].file }}" style="background: url({{ media[0].file }}) no-repeat center center / cover">
<button id="{{ loop.index0 }}" class="media-remove">
<i class="ti ti-x"></i>
</button>
</div>
{% endif %}
{% else %}
{% endif %}
{% endif %}
</div>
<label role="list-title">FILES</label>
<div role="page-files-list">
{% if files|length > 1 %}
{% for item in files %}
{% set fileName = item.file|split('/') %}
{% if item.type == "mp3" %}
<div id="{{ loop.index0 }}" class="audio-item" data-source="{{ item.file }}">
<audio controls>
<source src="{{ item.file }}"/>
</audio>
<button id="{{ loop.index0 }}" class="media-remove">
<i class="ti ti-x"></i>
</button>
</div>
{% else %}
<div id="{{ loop.index0 }}" class="file-item" data-source="{{ item.file }}">
<a href="{{ item.file }}" target="_blank">{{ fileName[6] }}"</a>
<button id="{{ loop.index0 }}" class="media-remove">
<i class="ti ti-x"></i>
</button>
</div>
{% endif %}
{% endfor %}
{% else %}
{% if files[0] != '' %}
{% set fileName = files[0].file|split('/') %}
{% if files[0].type == "mp3" %}
<div id="0" class="audio-item" data-source="{{ files[0].file }}">
<audio controls>
<source src="{{ files[0].file }}"/>
</audio>
<button id="{{ loop.index0 }}" class="media-remove">
<i class="ti ti-x"></i>
</button>
</div>
{% else %}
<div id="0" class="file-item" data-source="{{ files[0].file }}">
<a href="{{ item.file }}" target="_blank">{{ fileName[6] }}"</a>
<button id="{{ loop.index0 }}" class="media-remove">
<i class="ti ti-x"></i>
</button>
</div>
{% endif %}
{% else %}
{% endif %}
{% endif %}
</div>
<div id="post-header">
<div id="post-header-wrapper" class="columns">
<div id="post-title" class="column">
<label>TITLE</label>
<textarea id="post_title" type="text" name="post_title" class="post-edit" placeholder="TITLE">
{{- _title -}}
</textarea>
<label>CREATED</label>
<br/>
<span id="post-date" type="text">
{{ date }}
</span>
{% endif %}
</section>
<section role="page-meta">
<div role="page-meta-wrapper">
<div role="page-title">
<strong>TITLE</strong>
<textarea id="post-title-text" type="text" name="post-title-text" class="post-edit" placeholder="TITLE">{{ title }}</textarea>
</div>
<div id="post-meta" class="column">
<label>TAGS</label>
<textarea id="post_tags" type="text" name="post_tags" class="form-control" placeholder="tags [comma seperated]">
{{- tags -}}
</textarea>
<label>OPTIONS</label>
<div role="page-tags">
<strong>TAGS</strong>
<textarea id="post-tags" type="text" name="post-tags" class="form-control" placeholder="tags [comma seperated]">{{ tags }}</textarea>
</div>
<div role="page-layouts">
<strong>LAYOUTS</strong>
<select id="page-templates">
{% for view in views %}
{% if view == page['layout'] %}
<option value={{ view }} selected>{{ view }}</option>
{% else %}
<option value={{ view }}>{{ view }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div role="page-options">
<strong>OPTIONS</strong>
{% apply spaceless %}
{{ include("dash/partials/options.twig") }}
{% endapply %}
<input id="featured-image-upload" type="file" name="featured-image-upload"/>
</div>
<div role="page-updated">
<strong>UPDATED</strong>
<span id="post-date" type="text">
{{ updated }}
</span>
</div>
<div role="page-created">
<strong>CREATED</strong>
<span id="post-date" type="text">
{{ date }}
</span>
<input id="page-files-upload" type="file" name="page-files-upload" multiple/>
<input id="post-image-upload" type="file" name="post-image-upload"/>
<input id="form_token" name="token" type="hidden" value="{{ token }}"></div>
</div>
</div>
</div>
<div id="edit-post">
</section>
<section role="text-editor">
{% apply spaceless %}
{{ include("dash/partials/editor.twig") }}
{% endapply %}
<div id="edit-post-wrapper">
<pre><code id="edit-post-text" contenteditable="true"> {{- content -}}</code></pre>
</div>
</div>
</div>
<div role="edit-post-wrapper">
<textarea id="edit" spellcheck="false" class="language-md">{{ content }}</textarea>
<pre id="highlight">
<code id="highlight-content" class="language-md"></code>
</pre>
</div>
{% endblock %}
{% block javascripts %}
<script src="/assets/scripts/dash.min.js" type="text/javascript"></script>
</section>
{% endblock %}

View file

@ -1,45 +1,38 @@
<div id="edit-control">
<button id="edit-bold" class="content-editor-btn-text editor-button" title="bold">B</button>
<button id="edit-italic" class="content-editor-btn-text editor-button" title="italic">I</button>
<button id="edit-strikethrough" class="content-editor-btn-text editor-button" title="strikethrough">D</button>
<button id="edit-link" class="content-editor-btn-icon editor-button" title="insert link">
<svg id="edit-link" viewbox="0 0 20 20" class="icons">
<use id="edit-link" xlink:href="/assets/images/global/sprite.svg#entypo-link"/>
</svg>
<div role="text-editor-control">
<button id="edit-bold" class="content-editor-btn-text editor-button" title="bold">
<i id="edit-bold" class="ti ti-bold"></i>
</button>
<button id="edit-italic" class="content-editor-btn-text editor-button" title="italic">
<i id="edit-italic" class="ti ti-italic"></i>
</button>
<button id="edit-strikethrough" class="content-editor-btn-text editor-button" title="strikethrough">
<i id="edit-strikethrough" class="ti ti-strikethrough"></i>
</button>
<button id="edit-link" class="content-editor-btn-icon editor-button" title="insert link">
<i id="edit-link" class="ti ti-link"></i>
</button>
<button id="edit-header1" class="content-editor-btn-text editor-button" title="header 1">
<i id="edit-header1" class="ti ti-h-1"></i>
</button>
<button id="edit-header2" class="content-editor-btn-text editor-button" title="header 2">
<i id="edit-header2" class="ti ti-h-2"></i>
</button>
<button id="edit-header3" class="content-editor-btn-text editor-button" title="header 3">
<i id="edit-header3" class="ti ti-h-3"></i>
</button>
<button id="edit-header1" class="content-editor-btn-text editor-button" title="header 1">H1</button>
<button id="edit-header2" class="content-editor-btn-text editor-button" title="header 2">H2</button>
<button id="edit-header3" class="content-editor-btn-text editor-button" title="header 3">H3</button>
<button id="edit-image" class="content-editor-btn-icon editor-button" title="insert image">
<svg id="edit-image" viewbox="0 0 20 20" class="icons">
<use id="edit-image" xlink:href="/assets/images/global/sprite.svg#entypo-image"/>
</svg>
<i id="edit-image" class="ti ti-photo"></i>
</button>
{% if mode == "edit" %}
<button id="edit-update" class="post-sumbit-btn submit-start editor-button" data-action='blog-update' data-id=page.id type='submit' title="bold">
<svg id="submit-update" viewbox="0 0 20 20" class="icons">
<use id="submit-update" xlink:href="/assets/images/global/sprite.svg#entypo-save" data-action='blog-update' data-id="{{ page['uuid'] }}"/>
</svg>
<svg id="submit-good" class="icon-hide" viewbox="0 0 20 20" class="icons">
<use xlink:href="/assets/images/global/sprite.svg#entypo-thumbs-up"/>
</svg>
<svg id="submit-error" class="icon-hide" viewbox="0 0 20 20" class="icons">
<use xlink:href="/assets/images/global/sprite.svg#entypo-thumbs-down"/>
</svg>
<button id="edit-update" class="post-sumbit-btn submit-start editor-button" data-action='blog-update' data-id="{{ page['uuid'] }} type='submit' title=" bold">
<i id="edit-update" class="ti ti-device-floppy"></i>
</button>
<button id="edit-delete" class="content-editor-btn-icon editor-button submit-delete" for="post-delete" title='delete post'>
<svg id="edit-delete" viewbox="0 0 20 20" class="icons">
<use id="edit-delete" xlink:href="/assets/images/global/sprite.svg#entypo-cross"/>
</svg>
<i id="edit-delete" class="ti ti-x"></i>
</button>
{% else %}
<button id="edit-save" class="post-sumbit-btn submit-start editor-button" data-action='blog-add' type='submit'>
<svg id="submit-save" viewbox="0 0 20 20" class="icons">
<use id="submit-save" xlink:href="/assets/images/global/sprite.svg#entypo-plus"/>
</svg>
<i id="edit-save" class="ti ti-file-plus"></i>
</button>
{% endif %}
</div>

View file

@ -1,81 +1,30 @@
<div id="dash-recent">
<div id="recent-list">
<div class="recent-header">
<div class="index-header-left">
Recent
<section role="index-header">
<div role="index-header-left">
<h1>Recent</h1>
</div>
<div class="index-header-right">
<a href='/dashboard/pages' title="view pages">
<button>
<svg class="page-link">
<use xlink:href="/assets/images/global/sprite.svg#entypo-archive"/>
</svg>
</button>
</a>
<a href='/dashboard/page/add/new' title="add new page">
<button>
<svg class="page-link">
<use xlink:href="/assets/images/global/sprite.svg#entypo-plus"/>
</svg>
</button>
</a>
</div>
</div>
<br/>
<div role="index-header-right"></div>
</section>
<section role="index-recent-pages">
{% if data["entryCount"] != 0 %}
{% for page in data['pages'] %}
<a href="/dashboard/page/edit/{{ page.uuid }}" id="{{ page.uuid }}" class="post-link" style="background: url({{ page.feature }}) no-repeat center center / cover">
<label>
{{ page.title }}
</label>
<div id="options">
{% if page.menu == 'true' %}
{% set menu = "true" %}
{% else %}
{% set menu = "false" %}
{% endif %}
{% if page.published == 'true' %}
{% set published = "true" %}
{% else %}
{% set published = "false" %}
{% endif %}
{% if page.featured == 'true' %}
{% set featured = "true" %}
{% else %}
{% set featured = "false" %}
{% endif %}
<div id="option-left">
{% if page.media[0].type == 'mp4' %}
<button data-active="{{ menu }}">
<svg>
<use xlink:href="/assets/images/global/sprite.svg#entypo-add-to-list"/>
</svg>
</button>
<button data-active="{{ published }}">
<svg>
<use xlink:href="/assets/images/global/sprite.svg#entypo-globe"/>
</svg>
</button>
<button data-active="{{ featured }}">
<svg>
<use xlink:href="/assets/images/global/sprite.svg#entypo-star"/>
</svg>
</button>
</div>
<div id="option-right">
<span>
{{ page.updated }}
</span>
</div>
</div>
<a href="/dashboard/page/edit/{{ page.uuid }}" id="{{ page.uuid }}" class="post-video-link recent-link">
{{ include("dash/partials/recent-meta.twig") }}
<video class="post-video" loop muted autoplay>
<source src="{{ page.media[0].file }}" type="video/mp4">
Sorry, your browser doesn't support embedded videos.
</video>
</a>
{% else %}
<a href="/dashboard/page/edit/{{ page.uuid }}" id="{{ page.uuid }}" class="post-link recent-link" style="background: url({{ page.media[0].file }}) no-repeat center center / cover #fc6399">
{{ include("dash/partials/recent-meta.twig") }}
</a>
{% endif %}
{% endfor %}
{% else %}
There are no pages
{% endif %}
</div>
</div>
</section>

View file

@ -1,23 +1,57 @@
<div id="dash-menu">
<div role="menu">
{% if title == "Settings" %}
{% apply spaceless %}
{{ include("dash/partials/submenu_settings.twig") }}
{% endapply %}
{% elseif title=="Start" %}
{% apply spaceless %}
{{ include("dash/partials/submenu_start.twig") }}
{% endapply %}
{% endif %}
<a id="settings" href="/dashboard/settings" title="settings">
<button>
<svg class="menu">
<use xlink:href="/assets/images/global/sprite.svg#entypo-sound-mix"/>
</svg>
<i class="ti ti-home-cog"></i>
</button>
</a> .
</a>
<a id="navigation" href="/dashboard/navigation" title="edit navigation">
<button>
<svg class="menu">
<use xlink:href="/assets/images/global/sprite.svg#entypo-list"/>
</svg>
<i class="ti ti-arrow-autofit-height"></i>
</button>
</a> .
</a>
<a id="navigation" href="/dashboard/logout" title="log out">
<button>
<svg class="menu">
<use xlink:href="/assets/images/global/sprite.svg#entypo-log-out"/>
</svg>
<i class="ti ti-logout"></i>
</button>
</a>
</div>
<button role="menu-toggle">
<i class="ti ti-menu-2"></i>
</button>
<div role="mobile-menu">
{% if title == "Settings" %}
{% apply spaceless %}
{{ include("dash/partials/submenu_settings.twig") }}
{% endapply %}
{% elseif title=="Start" %}
{% apply spaceless %}
{{ include("dash/partials/submenu_start.twig") }}
{% endapply %}
{% endif %}
<a id="settings" href="/dashboard/settings" title="settings">
<button>
<i class="ti ti-home-cog"></i>
</button>
</a>
<a id="navigation" href="/dashboard/navigation" title="edit navigation">
<button>
<i class="ti ti-arrow-autofit-height"></i>
</button>
</a>
<a id="navigation" href="/dashboard/logout" title="log out">
<button>
<i class="ti ti-logout"></i>
</button>
</a>
</div>

View file

@ -0,0 +1,10 @@
<div role="notify-message">
<div role="notify-icons">
<i class="ti ti-mood-smile" role="notify-good"></i>
<i class="ti ti-mood-sad" role="notify-notgood"></i>
<i class="ti ti-settings" role="notify-working"></i>
</div>
<div role="notify-text">
<span role="response-text">Hey Hey</span>
</div>
</div>

View file

@ -15,21 +15,26 @@
{% else %}
{% set published = 'false' %}
{% endif %}
<div id="post-options">
<br>
<button id="option-menu-pin" class="option-inactive post-option-btn" data-active="{{ menu }}" title='Pin to Menu'>
<svg id="option-page-icon" viewbox="0 0 20 20" class="icons">
<svg id="option-page-icon" role="icon">
<use id="option-page-icon" xlink:href="/assets/images/global/sprite.svg#entypo-add-to-list"/>
</svg>
</button>
<button id="option-feature" class="option-inactive post-option-btn" data-active="{{ featured }}" title='Feature'>
<svg id="option-feature-icon" viewbox="0 0 20 20" class="icons">
<svg id="option-feature-icon" role="icon">
<use id="option-feature-icon" xlink:href="/assets/images/global/sprite.svg#entypo-star"/>
</svg>
</button>
<button id="option-published" class="option-inactive post-option-btn" data-active="{{ published }}" title='Published'>
<svg id="option-published-icon" viewbox="0 0 20 20" class="icons">
<use id="option-published-icon" xlink:href="/assets/images/global/sprite.svg#entypo-eye"/>
<svg id="option-published-icon" role="icon">
<use id="option-published-icon" xlink:href="/assets/images/global/sprite.svg#entypo-globe"/>
</svg>
</button>
</div>
<a href="/dashboard/page/preview/{{ uuid }}" target="_blank">
<button id="option-preview" class="option-inactive post-option-btn" data-active="false" title='preview page'>
<svg id="option-preview-icon" role="icon">
<use id="option-preview-icon" xlink:href="/assets/images/global/sprite.svg#entypo-eye"/>
</svg>
</button>
</a>

View file

@ -0,0 +1,37 @@
{% if page.menu == 'true' %}
{% set menu = "true" %}
{% else %}
{% set menu = "false" %}
{% endif %}
{% if page.published == 'true' %}
{% set published = "true" %}
{% else %}
{% set published = "false" %}
{% endif %}
{% if page.featured == 'true' %}
{% set featured = "true" %}
{% else %}
{% set featured = "false" %}
{% endif %}
<aside>
<strong>
{{ page.updated }}
</strong>
<hr/>
<strong>
{{ page.title }}
</strong>
<hr/>
<button data-active="{{ menu }}">
<i class="ti ti-navigation"></i>
</button>
<button data-active="{{ published }}">
<i class="ti ti-world"></i>
</button>
<button data-active="{{ featured }}">
<i class="ti ti-star"></i>
</button>
</aside>

View file

@ -0,0 +1,11 @@
<div role="submenu">
<button id="save-toggle" title="save settings">
<i class="ti ti-device-floppy"></i>
</button>
<button id="publish-pages" title="publish site">
<i class="ti ti-world-upload"></i>
</button>
<button id="render-toggle" title="render on save toggle" data-render="{{ renderOnSave }}">
<i class="ti ti-circle-dashed"></i>
</button>
</div>

View file

@ -0,0 +1,12 @@
<div role="submenu">
<a href='/dashboard/pages' title="view pages">
<button>
<i class="ti ti-book-2"></i>
</button>
</a>
<a href='/dashboard/page/add/new' title="add new page">
<button>
<i class="ti ti-square-plus"></i>
</button>
</a>
</div>

View file

@ -5,37 +5,14 @@
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=dfdfdf">
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css">
{% endblock %}
{% block mainContent %}
<div id="dash-index">
<div id="dash-index-wrapper">
<div id="dash-login">
<div id="dash-reset" class="dash-reset">
<img id="the-logo" src="/assets/images/global/fipamo-logo.svg"/>
<form id="reset" class='login' name="reset" action="/@/dashboard/login" method="POST">
<section role="password-reset">
{% apply spaceless %}
{{ include("dash/forms/reset.twig") }}
{% endapply %}
<input type="password" id="new_password"name="new_password" class="form-control" placeholder="New Password" required">
<input type="password" id="new_password2" name="new_password2" class="form-control" placeholder="New Password Confirm" required">
<input type="password" id="secret" name="secret" class="form-control" placeholder="Account Secret" required">
<button id="reset-btn" class='login-btn' type='submit'>
RESET PASSWORD
</button><br />
<p>
Use this to get your secret to verify it's you. If your email is set up, the secret will be sent there. If not, the form will be updated automatically(but please set up your email, once you reset your password).
</p>
<input type="text"id="email" name="email" class="form-control" placeholder="email to verify" required">
<button id="get-secret-btn" class='login-btn' type='submit'>
VERIFY EMAIL
</button><br /><br />
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script src="/assets/scripts/dash.min.js" type="text/javascript"></script>
</section>
{% endblock %}

View file

@ -1,101 +1,91 @@
{% extends "dash/_frame.twig" %}
{% if render %}
{% set renderOnSave = 'true' %}
{% else %}
{% set renderOnSave = 'false' %}
{% endif %}
{% block title %}
{{ title }}
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=asdfdf">
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css?=cvnbm">
{% endblock %}
{% block mainContent %}
<div id="settings-actions">
<div id="buttons">
<button id="save-toggle" title="save settings">
<svg id="submit-update" class="icons">
<use id="submit-update" xlink:href="/assets/images/global/sprite.svg#entypo-save"/>
</svg>
</button>
<button id="publish-pages" title="publish site">
<svg id="submit-update" class="icons">
<use id="submit-update" xlink:href="/assets/images/global/sprite.svg#entypo-publish"/>
</svg>
</button>
</div>
</div>
<div id="site-background">
<label>Site Header</label>
<img id="background" src="{{background}}" alt="image for site background" for="background-upload"/>
<input id="background-upload" type="file" name="backgrond-upload" />
</div>
<div id="settings-index">
<div id="settings-index-wrapper">
<div id="member-settings" class="columns">
<div id="member-settings-1" class="column is-one-third">
<div id="member-avatar-drop">
<img id="avatar" src="{{member['avatar']}}" for="avatar-upload"/>
<article role="settings">
<section role="member-settings">
<div role="member-avatar">
<div role="avatar" style="background: url({{ member['avatar'] }} ) no-repeat center center / cover"></div>
<input id="avatar-upload" type="file" name="avatar-upload"/>
</div>
<div role="site-background">
<div role="background" style="background: url({{ background }} ) no-repeat center center / cover"></div>
<input id="background-upload" type="file" name="backgrond-upload"/>
</div>
<div class="column is-three-fifths">
<div class="columns">
<div id="member-settings-2" class="column">
<div>
<input type='text' name='handle' id='settings-handle' placeholder='handle' value="{{ member['handle'] }}" autofocus/>
<input type='text' name='email' id='settings-email' placeholder='email' value="{{ member['email'] }}" autofocus/>
</div>
<div id="member-settings-3" class="column">
<input type='text' name='base-url' id='settings-url' placeholder='url' value="{{ baseUrl }}" autofocus/>
<input type='text' name='base-title' id='settings-title' placeholder='site title' value="{{ siteTitle }}" autofocus/>
<textarea id="settings-desc" type='text' name='settings_desc' class='settings-dec' placeholder='description stuff' , autofocus>{{ desc }}</textarea>
</div>
</div>
<div class="columns">
<div class="column is-full">
<textarea id="settings-desc" type='text' name='settings_desc' class='settings-dec' placeholder='description stuff', autofocus>{{desc}}</textarea><br />
<label>YOUR API KEY</label><br />
<span id="key">{{member['key']}}</span>
</div>
</div>
</div>
</div>
<div id="member-utils" class="columns">
<div id="util-1" class="column is-one-third">
<button id="create-backup">BACK UP YOUR SITE</button><br />
</div>
<div id="util-2" class="column is-three-fifths">
<div>
<button id="create-backup">
<i class="ti ti-disc"></i>
<span>BACK UP YOUR SITE</span>
</button><br/>
{% if lastBackup != '' %}
<div class="backup-meta">
LAST BACK UP <a href="/api/v1/files">{{lastBackup}}</a><br />
</div>
LAST BACK UP<br/>
<a href="/api/v1/files">{{ lastBackup }}</a><br/>
{% else %}
<span>span No back ups. Frowny face.</span>
{% endif %}
</div>
<div role="features">
{% if apiStatus is defined and apiStatus == "true" %}
<button id="api-access-toggle" title="allow external api" data-enabled="true">
<i class="ti ti-phone-incoming"></i>
<span id="api-status">API ACCESS ENABLED</span>
</button>
{% else %}
<button id="api-access-toggle" title="allow external api" data-enabled="false">
<i class="ti ti-phone-incoming"></i>
<span id="api-status">API ACCESS NOT ENABLED</span>
</button>
{% endif %}
{% if dynamicRenderStatus is defined and dynamicRenderStatus == "true" %}
<button id="dynamic-render-toggle" title="allow external api" data-enabled="true">
<i class="ti ti-arrow-merge"></i>
<span id="dynamic-render-status">DYNAMIC PAGE RENDERING</span>
</button>
{% else %}
<button id="dynamic-render-toggle" title="allow external api" data-enabled="false">
<i class="ti ti-arrow-merge"></i>
<span id="dynamic-render-status">STATIC PAGE RENDERING</span>
</button>
{% endif %}
</div>
<div id="option-settings" class="columns">
<div id="theme-settings" class="column">
<label>THEMES</label>
<div role="theme">
<label>Themes</label><br/>
{% for theme in themes %}
{% if theme.name == currentTheme %}
<a href="#" id="{{theme.name}}" class="theme-select" data-enabled="true">{{theme['display-name']}}</a>
<button id="{{ theme.name }}" class="theme-select" data-enabled="true">
<i class="ti ti-brush"></i>
<span>{{ theme['display-name'] }}</span>
</button>
{% else %}
<a href="#" id="{{theme.name}}" class="theme-select" data-enabled="false">{{theme['display-name']}}</a>
<button href="#" id="{{ theme.name }}" class="theme-select" data-enabled="false">
<i class="ti ti-brush-off"></i>
<span>{{ theme['display-name'] }}</span>
</button>
{% endif %}
{% endfor %}
</div>
<div id="mail-settings" class="column">
<label>EMAIL</label>
<div role="mail">
<label>Email</label><br/>
{% if mailOption == "option-none" or mailOption == "" %}
<a href="#" class="mail-option" id="option-none" data-enabled="true">NONE</a>
{% else %}
@ -115,36 +105,19 @@
{% apply spaceless %}
{{ include("dash/partials/mailforms.twig") }}
{% endapply %}
<button id="send-mail">TEST MAIL</button>
<br /><br />
<label>API SETTINGS</label><br />
<div id="settings-api">
{% if apiStatus is defined and apiStatus == "true" %}
<button id="api-access-toggle" title="allow external api" data-enabled="true">
<svg id="api-access-toggle" class="icons">
<use id="api-access-toggle" xlink:href="/assets/images/global/sprite.svg#entypo-landline"/>
</svg>
<button id="send-mail">
<i class="ti ti-mailbox"></i>
<span>TEST MAIL</span>
</button>
<span id="api-status">EXTERNAL API ACCESS ENABLED</span>
{% else %}
<button id="api-access-toggle" title="allow external api" data-enabled="false">
<svg id="api-access-toggle" class="icons">
<use id="api-access-toggle" xlink:href="/assets/images/global/sprite.svg#entypo-landline"/>
</svg>
</button>
<span id="api-status">EXTERNAL API ACCESS NOT ENABLED</span>
{% endif %}
</div>
</div>
</div>
</div>
<div>
<label>API KEY</label>
<br/>
{{ member['key'] }}
<br/>
<label>FORM TOKEN</label><br/>
{{ ftoken }}
</div>
{% endblock %}
{% block javascripts %}
<script src="/assets/scripts/dash.min.js" type="text/javascript"></script>
</section>
</article>
{% endblock %}

View file

@ -5,12 +5,10 @@
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=adsfdfdf">
<link rel="stylesheet" type="text/css" href="/assets/css/dash/start.css?=dfdfrtr">
{% endblock %}
{% block mainContent %}
<div id="dash-index">
<div id="dash-index-wrapper">
{% if status %}
{% apply spaceless %}
{{ include("dash/partials/index.twig") }}
@ -18,10 +16,4 @@
{% else %}
{{ include("dash/forms/login.twig") }}
{% endif %}
</div>
</div>
{% endblock %}
{% block javascripts %}
<script src="/assets/scripts/dash.min.js" type="text/javascript"></script>
{% endblock %}

View file

@ -1,7 +1,7 @@
{
"name": "are0h/fipamo",
"descriptions": "The most chill no database blog framework ever.",
"version": "2.1.1-beta",
"version": "2.6.1-beta",
"homepage": "https://fipamo.blog",
"authors": [
{
@ -10,9 +10,9 @@
}
],
"support": {
"source": "https://code.playvicio.us/Are0h/Fipamo",
"wiki": "https://code.playvicio.us/Are0h/Fipamo/wiki/_pages",
"issues": "https://code.playvicio.us/Are0h/Fipamo/issues"
"source": "https://koodu.ubiqueros.com/are0h/Fipamo",
"wiki": "https://koodu.ubiqueros.com/are0h/Fipamo/wiki/?action=_pages",
"issues": "https://koodu.ubiqueros.com/are0h/Fipamo/issues"
},
"require": {
"slim/slim": "4.*",
@ -22,8 +22,16 @@
"mnapoli/front-yaml": "^1.8",
"lodash-php/lodash-php": "^0.0.7",
"rbdwllr/reallysimplejwt": "^4.0",
"fightbulc/moment": "^1.33",
"tgalopin/html-sanitizer": "^1.4",
"phpmailer/phpmailer": "^6.4"
"phpmailer/phpmailer": "^6.4",
"league/commonmark": "^2.1",
"symfony/yaml": "^5.4",
"olegatro/html-sanitizer-relative": "^1.0",
"nesbot/carbon": "^2.62"
},
"scripts": {
"start": [
"@php -S localhost:8000 -t public/"
]
}
}

1419
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,9 @@
"renderOnSave": "false",
"theme": "fipamo-default",
"display_limit": 5,
"last_backup": null
"last_backup": null,
"externalAPI": "false",
"dynamicRender": "false"
},
"library_stats": {
"current_index": 1

View file

@ -1,4 +1,4 @@
{% extends "fipamo-default/frame.twig" %}
{% extends "frame.twig" %}
{% block title %}
{{ title }}
@ -23,7 +23,16 @@
{{data.full_month}}
</span>
{% for page in data.pages %}
{% if dynamicRender is defined %}
{% if dynamicRender == 'true' %}
<a href="{{ "/"~item.year~"/"~data.month~"/"~page.slug }}">{{page.title}}</a><br />
{% else %}
<a href="{{ "/"~item.year~"/"~data.month~"/"~page.slug~".html" }}">{{page.title}}</a><br />
{% endif %}
{% else %}
<a href="{{ "/"~item.year~"/"~data.month~"/"~page.slug~".html" }}">{{page.title}}</a><br />
{% endif %}
{% endfor %}
</div>

View file

@ -1,31 +1,32 @@
/**
-------------------------------
-- Typography
-------------------------------
**/
/**
-------------------------------
-- Colors
-------------------------------
**/
/**
-------------------------------
-- Mixins
-------------------------------
**/
/**
-------------------------------
-- Normalize
-------------------------------
**/
h1, h2, h3 {
color: #ebe5d4;
}
h1 {
font-size: 2em;
font-weight: 400;
}
h2 {
font-size: 1.75em;
font-weight: 400;
}
h3 {
font-size: 1.5em;
font-weight: 300;
}
html {
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
}
article,
aside,
footer,
@ -34,62 +35,75 @@ nav,
section {
display: block;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
line-height: 1em;
}
figcaption,
figure,
main {
display: block;
}
figure {
margin: 1em 40px;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
pre {
font-family: monospace, monospace;
font-size: 1em;
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
a:active,
a:hover {
outline-width: 0;
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted;
}
b,
strong {
font-weight: inherit;
font-weight: bolder;
}
code,
kbd,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
dfn {
font-style: italic;
}
mark {
background-color: #ff0;
color: #000;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 60%;
@ -97,31 +111,38 @@ sup {
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.55em;
background: #bdcbdb;
background: #151d26;
color: #151d26;
border-radius: 2px;
padding: 0 2px 0 2px;
margin: 0 2px 0 0;
}
audio,
video {
display: inline-block;
}
audio:not([controls]) {
display: none;
height: 0;
}
img {
border-style: none;
}
svg:not(:root) {
overflow: hidden;
}
button,
input,
optgroup,
@ -132,38 +153,44 @@ textarea {
line-height: 1.15;
margin: 0;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
html [type="button"],
[type="reset"],
[type="submit"] {
button, html [type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner,
[type=button]::-moz-focus-inner,
[type=reset]::-moz-focus-inner,
[type=submit]::-moz-focus-inner,
button::-moz-focus-inner {
border-style: none;
padding: 0;
}
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring,
[type=button]:-moz-focusring,
[type=reset]:-moz-focusring,
[type=submit]:-moz-focusring,
button:-moz-focusring {
outline: 1px dotted ButtonText;
}
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
legend {
box-sizing: border-box;
color: inherit;
@ -172,55 +199,140 @@ legend {
padding: 0;
white-space: normal;
}
progress {
display: inline-block;
vertical-align: baseline;
}
textarea {
overflow: auto;
}
[type="checkbox"],
[type="radio"] {
[type=checkbox],
[type=radio] {
box-sizing: border-box;
padding: 0;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
[type=number]::-webkit-inner-spin-button,
[type=number]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
[type=search]::-webkit-search-cancel-button,
[type=search]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
details,
menu {
display: block;
}
summary {
display: list-item;
}
canvas {
display: inline-block;
}
template {
display: none;
}
[hidden] {
display: none;
}
/**
-------------------------------
-- Main Structure
-------------------------------
**/
form {
display: inline-block;
}
form a {
color: #151d26;
}
form p {
background: #e8c33e;
color: #151d26;
padding: 5px;
display: block;
border-radius: 5px;
text-align: left;
}
input[type=email], input[type=password], input[type=text] {
border: 0;
border-radius: 5px;
padding: 5px;
margin: 10px 5px 0 0;
font: 18px Helvetica, Arial, sans-serif;
display: inline-block;
background: #151d26;
color: #e8c33e;
}
textarea {
border: 0;
border-radius: 3px;
color: #ebe5d4;
font: 15px Helvetica, Arial, sans-serif;
background: #151d26;
}
button, input[type=submit] {
background: #7ED07E;
color: #151d26;
font: 20px Helvetica, Arial, sans-serif;
border-radius: 5px;
position: relative;
cursor: pointer;
border: 0;
padding: 10px 0 5px 0;
transition: all 0.3s linear;
}
select {
font: 14px Helvetica, Arial, sans-serif;
border: 1px solid #FC6399;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
color: #151d26;
}
::-webkit-input-placeholder {
font: 25px Helvetica, Arial, sans-serif;
color: #ebe5d4;
}
:-moz-placeholder {
/* Firefox 18- */
font: 25px Helvetica, Arial, sans-serif;
color: #ebe5d4;
}
::-moz-placeholder {
/* Firefox 19+ */
font: 15px Helvetica, Arial, sans-serif;
color: #ebe5d4;
}
:-ms-input-placeholder {
font: 25px Helvetica, Arial, sans-serif;
color: #ebe5d4;
}
html {
margin: 0;
padding: 0;
@ -229,7 +341,7 @@ html {
overflow: hidden;
font: 400 1.2em/1.4em Helvetica, Arial, sans-serif;
}
body {
html body {
background: #ebe5d4;
margin: 0;
padding: 0;
@ -240,69 +352,88 @@ body {
overflow-y: scroll;
overflow-x: hidden;
}
a {
html body a {
color: #151d26;
text-decoration: none;
border-bottom: 1px solid #7ed07e;
-moz-transition: all 0.2s linear;
-webkit-transition: all 0.2s linear;
-o-transition: all 0.2s linear;
transition: all 0.2s linear;
border-bottom: 1px solid #7ED07E;
}
a:hover {
border-bottom: 1px solid #fc6399;
html body a:hover {
border-bottom: 1px solid #FC6399;
}
code {
html body code {
background: #32302f;
color: #7ed07e;
color: #7ED07E;
border-radius: 3px;
padding: 3px;
}
pre {
html body pre {
background: #32302f;
color: #7ed07e;
color: #7ED07E;
border-radius: 3px;
padding: 3px;
}
pre code {
color: #fc6399;
html body code {
color: #FC6399;
background: none;
}
svg.icons {
html body svg.icons {
width: 25px;
fill: #ebe5d4;
}
header {
html body header {
background: #151d26;
height: 90%;
width: 100%;
border-top: #ebe5d4 3px solid;
}
header nav {
html body header #media {
width: 100%;
height: 90%;
position: absolute;
}
html body header #media .slide {
transition: all 0.7s linear;
width: 100%;
height: 100%;
position: absolute;
}
html body header #media .hide {
opacity: 0;
}
html body header #media .show {
opacity: 1;
}
html body header #media video {
width: 100%;
height: 100%;
object-fit: cover;
}
html body header nav {
width: 97%;
margin: 10px auto;
z-index: 1000;
position: relative;
color: #151d26;
}
header nav .left,
header nav .right {
html body header nav .left, html body header nav .right {
width: 50%;
display: inline-block;
vertical-align: top;
}
header nav .left a.logo-link {
html body header nav .left a.logo-link {
border-bottom: none;
margin: 0 0 20px 0;
display: block;
}
header nav .left a.logo-link #logo {
html body header nav .left a.logo-link #logo {
width: 50px;
border-bottom: none;
}
header nav .right {
html body header nav .right {
text-align: right;
}
header nav .right a.menu-link {
background: #fc6399;
html body header nav .right a.menu-link {
background: #FC6399;
margin-bottom: 4px;
padding: 3px;
border-radius: 2px;
@ -310,10 +441,10 @@ header nav .right a.menu-link {
font-size: 0.8em;
border-bottom: none;
}
header nav .right a.menu-link:hover {
background: #feb1cc;
html body header nav .right a.menu-link:hover {
background: #FC6399;
}
.container {
html body .container {
z-index: 2;
background: #ebe5d4;
line-height: 30px;
@ -321,7 +452,7 @@ header nav .right a.menu-link:hover {
width: 100%;
color: #32302f;
}
.container article {
html body .container article {
position: relative;
width: 80%;
height: 80%;
@ -331,225 +462,139 @@ header nav .right a.menu-link:hover {
color: #32302f;
margin: 0 auto;
}
.container article .index,
.container article .page {
html body .container article .index, html body .container article .page {
padding: 0 0 15px 0;
}
.container article .index img,
.container article .page img {
html body .container article .index img, html body .container article .page img {
display: block;
width: 100%;
}
.container article .index h1,
.container article .page h1 {
html body .container article .index h1, html body .container article .index h2, html body .container article .page h1, html body .container article .page h2 {
color: #151d26;
}
.container article .index p,
.container article .page p {
html body .container article .index p, html body .container article .page p {
font: 300 1.25em/1.6em Helvetica, Arial, sans-serif;
}
.container article .index .meta,
.container article .page .meta {
html body .container article .index .page_files .page_doc a, html body .container article .page .page_files .page_doc a {
background: #32302f;
border-radius: 3px;
color: #ebe5d4;
padding: 3px;
margin: 0 5px 0 0;
}
html body .container article .index .meta, html body .container article .page .meta {
font: 500 0.8em/1.3em Helvetica, Arial, sans-serif;
padding: 5px 0 0 0;
border-top: 1px solid #151d26;
background: #ede8d8;
background: #ebe5d4;
}
.container article .index .meta a,
.container article .page .meta a {
html body .container article .index .meta a, html body .container article .page .meta a {
font-size: 0.8em;
font-weight: 400;
}
.container article .index .archive-item,
.container article .page .archive-item {
html body .container article .index .archive-item, html body .container article .page .archive-item {
padding: 15px 0 20px 0;
}
.container article .index .archive-item span.year,
.container article .page .archive-item span.year {
html body .container article .index .archive-item span.year, html body .container article .page .archive-item span.year {
font-size: 1.5em;
font-weight: 500;
padding: 5px;
display: block;
color: #151d26;
}
.container article .index .archive-item .archive-month,
.container article .page .archive-item .archive-month {
html body .container article .index .archive-item .archive-month, html body .container article .page .archive-item .archive-month {
display: inline-block;
vertical-align: top;
width: 30%;
padding: 5px;
}
.container article .index .archive-item .archive-month span.month,
.container article .page .archive-item .archive-month span.month {
color: #fc6399;
html body .container article .index .archive-item .archive-month span.month, html body .container article .page .archive-item .archive-month span.month {
color: #FC6399;
font-size: 1.5em;
font-weight: 300;
padding: 5px;
display: block;
}
.container section {
html body .container section {
padding: 0 0 20px 0;
background: #151d26;
}
.container section a {
html body .container section a {
color: #ebe5d4;
}
.container section .index-lists,
.container section .page-title {
html body .container section .index-lists, html body .container section .page-title {
max-width: 840px;
width: 80%;
margin: 0 auto;
padding: 20px 0 0 0;
}
.container section .index-lists span,
.container section .page-title span {
font: 600 2em/1.5 Helvetica, Arial, sans-serif;
color: #ebe5d4;
html body .container section .index-lists span, html body .container section .page-title span {
font-size: 2em;
color: #FC6399;
font-weight: 400;
width: 80%;
margin: 0 auto;
padding: 20px 0 0 0;
}
.container section .index-lists .recent,
.container section .page-title .recent,
.container section .index-lists .featured,
.container section .page-title .featured {
html body .container section .index-lists .recent, html body .container section .index-lists .featured, html body .container section .page-title .recent, html body .container section .page-title .featured {
display: inline-block;
width: 50%;
vertical-align: top;
}
.container section .index-lists label,
.container section .page-title label {
html body .container section .index-lists label, html body .container section .page-title label {
background: #32302f;
color: #ebe5d4;
font-size: 1.5em;
line-height: 1.3;
}
footer {
background: #e4dcc5;
html body footer {
background: #ebe5d4;
padding: 10px;
color: #151d26;
font-size: 0.8em;
font-weight: 600;
height: 100px;
}
footer .inner {
html body footer .inner {
margin: 20px auto;
width: 80%;
max-width: 840px;
}
footer .inner a {
color: #fc6399;
html body footer .inner a {
color: #FC6399;
}
@media only screen and (max-width: 640px) {
header nav {
html body header nav {
width: 98%;
}
header span {
html body header span {
font-size: 2.5em;
}
.container article .index .archive-item .archive-month,
.container article .page .archive-item .archive-month {
html body header .container article .index .archive-item .archive-month, html body header .container article .page .archive-item .archive-month {
width: 45%;
}
}
@media only screen and (max-width: 480px) {
header nav {
html body header nav {
width: 96%;
}
.container article .index,
.container article .page {
html body header .container article .index, html body header .container article .page {
margin: 0;
}
.container article .index p,
.container article .page p {
html body header .container article .index p, html body header .container article .page p {
font: 300 1em/1.6em Helvetica, Arial, sans-serif;
}
.container section .index-lists .recent,
.container section .index-lists .featured {
html body header .container section .index-lists .recent, html body header .container section .index-lists .featured {
width: 100% !important;
}
}
@media only screen and (max-width: 375px) {
header nav {
html body header nav {
width: 95%;
}
.container article .index,
.container article .page {
html body .container article .index, html body .container article .page {
margin: 0;
}
.container article .index p,
.container article .page p {
html body .container article .index p, html body .container article .page p {
font: 300 0.9em/1.7em Helvetica, Arial, sans-serif;
}
.container article .index .archive-item .archive-month,
.container article .page .archive-item .archive-month {
html body .container article .index .archive-item .archive-month, html body .container article .page .archive-item .archive-month {
width: 95%;
}
}
/**
-------------------------------
-- Forms
-------------------------------
**/
form {
display: inline-block;
}
input[type=email],
input[type=password],
input[type=text] {
border: 0;
border-radius: 5px;
padding: 5px;
margin: 10px 5px 0 0;
font: 15px 'RobotoMono';
display: inline-block;
}
textarea {
border: 0;
border-radius: 3px;
color: $type02;
font: 15px 'RobotoMono';
}
button,
input[type=submit] {
background: #fc6399;
color: #ebe5d4;
font: 14px Helvetica, Arial, sans-serif;
border-radius: 5px;
position: relative;
cursor: pointer;
border: 0;
padding: 5px 5px 0 5px;
-moz-transition: all 0.3s linear;
-webkit-transition: all 0.3s linear;
-o-transition: all 0.3s linear;
transition: all 0.3s linear;
}
button:hover,
input[type=submit]:hover {
background: #fc7ca9;
}
select {
font: 14px 'RobotoMono';
border: 1px solid #fc6399;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
color: #151d26;
}
::-webkit-input-placeholder {
font: 14px 'RobotoMono';
color: #837e7c;
}
:-moz-placeholder {
/* Firefox 18- */
font: 14px 'RobotoMono';
color: #837e7c;
}
::-moz-placeholder {
/* Firefox 19+ */
font: 14px 'RobotoMono';
color: #837e7c;
}
:-ms-input-placeholder {
font: 14px 'RobotoMono';
color: #837e7c;
}
/*# sourceMappingURL=base.css.map */

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
(()=>{class e{constructor(){this.currentSlide=0,this.slides=document.querySelectorAll("#media .slide"),this.start()}start(){this.slides.length>1&&(this.slideInterval=setInterval((()=>{this.slides[this.currentSlide].className="slide hide",this.currentSlide=(this.currentSlide+1)%this.slides.length,this.slides[this.currentSlide].className="slide show"}),3e3))}}document.addEventListener("DOMContentLoaded",(function(){new e}),!1)})();
//# sourceMappingURL=ThemeStart.js.map

View file

@ -0,0 +1 @@
{"mappings":"4BAKIA,KAAKC,aAAe,EACpBD,KAAKE,OAASC,SAASC,iBAAiB,iBACxCJ,KAAKK,QAEPA,QACML,KAAKE,OAAOI,OAAS,IACvBN,KAAKO,cAAgBC,aAAW,KAC9BR,KAAKE,OAAOF,KAAKC,cAAcQ,UAAY,aAC3CT,KAAKC,cAAgBD,KAAKC,aAAe,GAAKD,KAAKE,OAAOI,OAC1DN,KAAKE,OAAOF,KAAKC,cAAcQ,UAAY,eAC1C,OCbTN,SAASO,iBACP,oBACA,WACa,IAAIC,KAEjB","sources":["src/themes/theme-fipamo-default/com/Base.js","src/themes/theme-fipamo-default/com/ThemeStart.js"],"sourcesContent":["export default class Base {\n //--------------------------\n // constructor\n //--------------------------\n constructor() {\n this.currentSlide = 0;\n this.slides = document.querySelectorAll(\"#media .slide\");\n this.start();\n }\n start() {\n if (this.slides.length > 1) {\n this.slideInterval = setInterval(() => {\n this.slides[this.currentSlide].className = \"slide hide\";\n this.currentSlide = (this.currentSlide + 1) % this.slides.length;\n this.slides[this.currentSlide].className = \"slide show\";\n }, 3000);\n }\n }\n //--------------------------\n // methods\n //--------------------------\n\n //--------------------------\n // event handlers\n //--------------------------\n}\n","import Base from \"./Base.js\";\n\ndocument.addEventListener(\n \"DOMContentLoaded\",\n function () {\n var base = new Base();\n },\n false\n);\n"],"names":["this","currentSlide","slides","document","querySelectorAll","start","length","slideInterval","setInterval","className","addEventListener","$b8d4b81eabebe07b$export$2e2bcd8739ae039"],"version":3,"file":"ThemeStart.js.map"}

View file

@ -1,2 +0,0 @@
!function(){"use strict";function n(n,e){for(var t=0;t<e.length;t++){var o=e[t];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(n,o.key,o)}}var e=function(){function e(){!function(n,e){if(!(n instanceof e))throw new TypeError("Cannot call a class as a function")}(this,e),this.start()}var t,o,r;return t=e,(o=[{key:"start",value:function(){console.log("Connected like F.E.")}}])&&n(t.prototype,o),r&&n(t,r),e}();document.addEventListener("DOMContentLoaded",(function(){new e}),!1)}();
//# sourceMappingURL=start.min.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,18 @@
{% extends "frame.twig" %}
{% block title %}
{{ title }}
{% endblock %}
{% block mainContent %}
<section>
<div class="page-title">
<span>{{title}}</span>
</div>
</section>
<article>
<div class="page">
<p>{{content | raw}}</p>
</div>
</article>
{% endblock %}

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
{% if debug is defined %}
{% set assetPath = '/src/themes/theme-fipamo-default/fipamo-default/assets/' %}
{% set assetPath = '/src/themes/theme-'~theme~'/'~theme~'/assets/' %}
{% else %}
{% set assetPath = '/assets/' %}
{% endif %}
@ -20,10 +20,47 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<meta property="og:image" content="{{info["image"]}}"/>
<meta name="twitter:image" content="{{info["image"]}}"/>
<link rel="stylesheet" type="text/css" href="{{ assetPath~"css/base.css?=adfafd" }}">
<link rel="stylesheet" type="text/css" href="{{ assetPath~"css/base.css?=dfvbghh" }}">
</head>
<body>
<!--
<header style="background: url({{ background }}) no-repeat center center; background-size: cover">
-->
<header>
<div id="media">
{% if media|length > 1 %}
{% for item in media %}
{% if item.type == "mp4"%}
<div id="{{loop.index0}}" class="slide">
<video controls autoplay muted>
<source src="{{item.file}}" type="video/mp4">
Please get a better browser. They're free.
</video>
</div>
{% else %}
<div id="{{loop.index0}}" class="slide hide" style="background: url({{ item.file }}) no-repeat center center / cover"></div>
{% endif %}
{% endfor %}
{% else %}
{% if media[0] != '' %}
{% if media[0].type == "mp4"%}
<div id="0" class="slide">
<video controls autoplay muted>
<source src="{{media[0].file}}" type="video/mp4">
Please get a better browser. They're free.
</video>
</div>
{% else %}
<div id="0" class="slide" style="background: url({{ media[0].file }}) no-repeat center center / cover"></div>
{% endif %}
{% else %}
{% endif %}
{% endif %}
</div>
<nav>
{% apply spaceless %}
<div class="left">
@ -34,7 +71,15 @@
<div class="right">
{% if menu is defined %}
{% for link in menu %}
<a href="{{"/"~link.path~"/"~link.slug~".html"}}" class="menu-link">{{link.title}}</a><br />
{% if dynamicRender is defined %}
{% if dynamicRender == 'true' %}
<a href="{{"/"~link.slug}}" class="menu-link">{{link.title}}</a><br/>
{% else %}
<a href="{{"/"~link.slug~".html"}}" class="menu-link">{{link.title}}</a><br/>
{% endif %}
{% else %}
<a href="{{"/"~link.slug~".html"}}" class="menu-link">{{link.title}}</a><br/>
{% endif %}
{% endfor %}
{% endif %}
</div>
@ -50,10 +95,18 @@
<footer>
<div class="inner">
{% if dynamicRender is defined %}
{% if dynamicRender == 'true' %}
<a href="/archives">Archives</a><br/>
{% else %}
<a href="/archives.html">Archives</a><br/>
{% endif %}
{% else %}
<a href="/archives.html">Archives</a><br/>
{% endif %}
© 2020 By Fipamo
</div>
</footer>
<script src="{{ assetPath~"scripts/start.min.js" }}" type="text/javascript"></script>
<script src="{{ assetPath~"scripts/ThemeStart.js" }}" type="text/javascript"></script>
</body>
</html>

View file

@ -1,4 +1,4 @@
{% extends "fipamo-default/frame.twig" %}
{% extends "frame.twig" %}
{% block title %}
{{ title }}
@ -17,14 +17,32 @@
<div class="recent">
<span>RECENT</span><br />
{% for item in recent %}
{% if dynamicRender is defined %}
{% if dynamicRender == 'true' %}
<a href="{{ "/"~item.path~"/"~item.slug}}"> {{item.title}} </a><br />
{% else %}
<a href="{{ "/"~item.path~"/"~item.slug~".html" }}"> {{item.title}} </a><br />
{% endif %}
{% else %}
<a href="{{ "/"~item.path~"/"~item.slug~".html" }}"> {{item.title}} </a><br />
{% endif %}
{% endfor %}
</div>
<div class="featured">
<span>FEATURED</span><br />
{% for item in featured %}
{% if dynamicRender is defined %}
{% if dynamicRender == 'true' %}
<a href="{{ "/"~item.path~"/"~item.slug}}"> {{item.title}} </a><br />
{% else %}
<a href="{{ "/"~item.path~"/"~item.slug~".html" }}"> {{item.title}} </a><br />
{% endif %}
{% else %}
<a href="{{ "/"~item.path~"/"~item.slug~".html" }}"> {{item.title}} </a><br />
{% endif %}
{% endfor %}
</div>

View file

@ -0,0 +1,60 @@
{% extends "frame.twig" %}
{% block title %}
{{ title }}
{% endblock %}
{% block mainContent %}
<section>
<div class="page-title">
<span>{{title}}</span><br>
This is a custom temlate
</div>
</section>
<article>
<div class="page">
<p>{{content | raw}}</p>
<div>
<div class="page_files">
<div class="page_doc">
<strong>Files</strong><br/>
{% for doc in files %}
{% if doc.type != "mp3" %}
{% set path = doc.file|split('/') %}
<a href="{{doc.file}}">{{path[6]}}</a>
{% endif %}
{% endfor %}
</div>
<div class="page_sounds">
<strong>Sounds</strong><br/>
{% for doc in files %}
{% if doc.type == "mp3" %}
<audio controls>
<source src="{{doc.file}}" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
{% endif %}
{% endfor %}
</div>
</div>
<br/>
{{meta['who']}} dropped this {{ meta['when'] }}<br />
<strong>tags: </strong>
{% for tag in meta['tags'] %}
{% if dynamicRender is defined %}
{% if dynamicRender == 'true' %}
<a href="{{ "/tags/"~tag.slug }}">{{ tag.label }}</a>
{% else %}
<a href="{{ "/tags/"~tag.slug~".html" }}">{{ tag.label }}</a>
{% endif %}
{% else %}
<a href="{{ "/tags/"~tag.slug~".html" }}">{{ tag.label }}</a>
{% endif %}
{% endfor %}
</div>
</div>
</article>
{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends "fipamo-default/frame.twig" %}
{% extends "frame.twig" %}
{% block title %}
{{ title }}
@ -14,13 +14,49 @@
<div class="page">
<p>{{content | raw}}</p>
<div>
<div class="page_files">
<div class="page_doc">
<strong>Files</strong><br/>
{% for doc in files %}
{% if doc.type != "mp3" %}
{% set path = doc.file|split('/') %}
<a href="{{doc.file}}">{{path[6]}}</a>
{{meta['who']}} dropped this {{ meta['when'] }}<br />
<strong>tags: </strong>
{% for tag in meta['tags'] %}
<a href="{{ "/tags/"~tag.slug~".html" }}">{{ tag.label }}</a>
{% endif %}
{% endfor %}
</div>
<div class="page_sounds">
<strong>Sounds</strong><br/>
{% for doc in files %}
{% if doc.type == "mp3" %}
<audio controls>
<source src="{{doc.file}}" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
{% endif %}
{% endfor %}
</div>
</div>
<br/>
{{meta['who']}}
dropped this
{{ meta['when'] }}<br/>
<strong>tags:
</strong>
{% for tag in meta['tags'] %}
{% if dynamicRender is defined %}
{% if dynamicRender == 'true' %}
<a href="{{ "/tags/"~tag.slug }}">{{ tag.label }}</a>
{% else %}
<a href="{{ "/tags/"~tag.slug~".html" }}">{{ tag.label }}</a>
{% endif %}
{% else %}
<a href="{{ "/tags/"~tag.slug~".html" }}">{{ tag.label }}</a>
{% endif %}
{% endfor %}
</div>
</div>
</article>
{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends "fipamo-default/frame.twig" %}
{% extends "frame.twig" %}
{% block title %}
{{ title }}
@ -13,7 +13,16 @@
<article>
<div class="page">
{% for tag in tag_list %}
{% if dynamicRender is defined %}
{% if dynamicRender == 'true' %}
<a href="{{"/"~tag.path~"/"~tag.slug}}">{{tag.title}}</a><br />
{% else %}
<a href="{{"/"~tag.path~"/"~tag.slug~".html"}}">{{tag.title}}</a><br />
{% endif %}
{% else %}
<a href="{{"/"~tag.path~"/"~tag.slug~".html"}}">{{tag.title}}</a><br />
{% endif %}
{% endfor %}
</div>

8025
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

34
package.json Normal file
View file

@ -0,0 +1,34 @@
{
"name": "fipamo-dash",
"version": "2.6.1-beta",
"private": true,
"apidoc": {
"name": "Fipamo API",
"version": "1.0.0",
"description": "The most chill API for the most chill blog framework"
},
"source": "src/com/Start.js",
"main": "public/assets/scripts/dash.js",
"targets": {
"main": {
"includeNodeModules": true
}
},
"scripts": {
"watch": "parcel watch",
"build": "parcel build"
},
"devDependencies": {
"@babel/core": "^7.21.3",
"babel-plugin-prismjs": "^2.1.0",
"parcel": "^2.8.3",
"prettier": "^2.8.4",
"stylelint": "^15.3.0",
"stylelint-config-standard": "^31.0.0"
},
"dependencies": {
"animejs": "^3.2.1",
"prismjs": "^1.29.0",
"sortablejs": "^1.15.0"
}
}

Some files were not shown because too many files have changed in this diff Show more