Core Boxx is an open-source PHP framework that comes with an installable PWA, a PDO database, a pretty URL, and an API out of the box. But the idea is not to be a bloated framework with tons of unused features. It is built with the concept of modularity, loading only what you need – It also has plenty of “predefined system modules” that you can add on later.
TABLE OF CONTENTS
DOWNLOAD & INSTALLATION
First, here are the download links and a quick “setup guide” for the impatient folks who don’t want to read through everything.
LICENSE & DOWNLOAD
Core Boxx is released under the MIT License. You are free to use it for personal and commercial projects, and modify it as you see fit. On the condition that the software is provided “as-is”. There are no warranties provided and “no strings attached”. Code Boxx and the authors are not liable for any claims, damages, or liabilities.
Download Core Boxx | GitHub | Source Forge
SYSTEM REQUIREMENTS
- LAMP/WAMP/MAMP/XAMPP
- Apache Mod Rewrite
- PHP MYSQL PDO Extension
- At least PHP 8.0
INSTALLATION
- Just access
http://YOUR-SITE.com
in your browser to launch the installer. - It will automatically create a project database with a
settings
table and generate the necessary.htaccess
file.
Done! You now have a basic skeleton framework. Do at least run through the quick tutorial below.
REFERENCE & MODULES
Here are some links to the library references plus more Core Boxx modules.
LIBRARY REFERENCES
- Core Library
- Database Library
- Route Library
- Mail, Pagination, Settings Library
- HTML Javascript Template
CORE BOXX MODULES
Download, plugin, and expand your system.
- Users – User register, login, logout, forgot password, and admin functions.
- One Time Password – For added security.
- Resumable Upload – Resumable uploads with FlowJS and FlowPHP.
- Dynamic Content – Store and retrieve contents from the database.
- Likes & Dislikes Reaction – Or was it “upvote and downvote”?
- Comments – Allow registered users to comment on your posts/products/images/videos.
- Calendar – Show and manage events.
- Web Push – Use the force. Send push notifications.
BASIC MECHANICS
If you are curious about the basic mechanics behind Core Boxx – Check out this post.
A QUICK TUTORIAL
Don’t worry, Core Boxx does not have some crazy documentation that takes months to read. Let us walk through a quick example of creating a dummy content module.
TUTORIAL CODE DOWNLOAD
Click here to download the tutorial code.
FOLDERS OVERVIEW
Core Boxx only has 3 folders:
assets
Public CSS, Javascript, images, and whatever else.pages
HTML pages and templates.lib
Core, library, API, and SQL files should be kept in this folder.
STEP 1) RUN THE INSTALLER
If you have not already done so, download and unzip Core Boxx into your HTTP folder. Access http://YOUR-SITE.com
and let the installer do its thing. A couple of small notes.
lib/CORE-Config.php
Contains the system settings.lib/CORE-Routes.php
Manual overrides for the pretty URL. Optional, but if you want to set it, it’s right here.
STEP 2) CREATE DATABASE TABLES
CREATE TABLE `content` (
`id` int(11) NOT NULL,
`content` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `content`
ADD PRIMARY KEY (`id`);
ALTER TABLE `content`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
Next, create the database tables. Let’s keep things simple with a dummy content table:
id
Primary key and auto-increment.content
The content text itself.
STEP 3) DEVELOP THE LIBRARY MODULES
<?php
class Content extends Core {
// (A) SAVE CONTENT
function save ($content, $id=null) {
if ($id===null) {
$this->DB->insert("content", ["content"], [$content]);
} else {
$this->DB->update("content", ["content"], "`id`=?", [$content, $id]);
}
return true;
}
// (B) GET ALL CONTENT
function getAll () {
return $this->DB->fetchAll("SELECT * FROM `content`");
}
}
With the database table in place, the next step is to create the library itself.
- Since we are working with a content module, we create a corresponding
lib/LIB-Content.php
– Take note of the capitalC
. - Set the class name to the same as the file name, and extend the core –
class Content extends Core
. - Add the library functions:
function save()
Add or update content.function getAll()
Get all content.
- Take note of how we “link” and use functions from the database library
$this->DB->insert()
,$this->DB->update()
,$this->DB->fetch()
. - If you are curious about how this works, study
function load()
andclass Core
inlib/LIB-Core.php
in your own free time… If not, just know that you can use$this->Core->XXX
to access the core library.
P.S. Modules can also access each other. For example, we can add a new function to send a piece of content to a specified email:
function mailTo ($email, $id) {
$this->Core->load("Mail");
return $this->Mail->send([
"to" => $email,
"subject" => "Test",
"body" => $this->get($id)
]);
}
Yep, that is the whole idea of a modular framework – We load modules only when required, and use them to help each other.
STEP 4) DEVELOP THE API ENDPOINTS
<?php
switch ($_REQ) {
// (A) INVALID REQUEST
default:
$_CORE->respond(0, "Invalid request", null, null, 400);
break;
// (B) GET ALL
case "getAll":
$_CORE->autoGETAPI("Content", "getAll");
break;
// (C) SAVE CONTENT
case "save":
$_CORE->autoAPI("Content", "save");
break;
}
}
- To call the API in Core Boxx, we send a POST or GET request to
http://site.com/api/MODULE/REQUEST
–MODULE
will be parsed into$_MOD
andREQUEST
into$_REQ
. - In this example, the content endpoint
http://site.com/api/content
will map tolib/API-content.php
. - The request
$_REQ
is to be manually handled in the script itself. As above, we use aswitch ($_REQ)
to specify the available commands. - The rest is automatic magic.
$_CORE->autoAPI("Content", "save")
calls$_CORE->Content->save()
, maps all the post parameters, and replies with the system’s standard JSON response$_CORE->respond()
.$_CORE->autoGETAPI()
is the cousin of$_CORE->autoAPI()
and should be used on functions that return an array of data.
STEP 5) HTML PAGES
5A) THE HTML
<?php
// (A) PAGE META DATA & SCRIPTS
$_PMETA = [
"title" => "Core Boxx Tutorial Page",
"load" => [
["s", HOST_ASSETS . "content.js", "defer"]
//,["s", HOST_ASSETS . "more.js"]
//,["c", HOST_ASSETS . "my.css"]
]
];
// (B) HTML PAGE
require PATH_PAGES . "TEMPLATE-top.php"; ?>
<input type="button" class="btn btn-primary mb-4" value="Add" onclick="content.addedit()">
<ul id="cList" class="list-group"></ul>
<?php require PATH_PAGES . "TEMPLATE-bottom.php"; ?>
- Take note of how the URL-to-file works (otherwise known as “pretty URL”):
pages/PAGE-content.php
can be accessed athttp://site.com/content
.pages/PAGE-hello-world.php
can be accessed athttp://site.com/hello/world
.
- The default Core Boxx HTML template comes with Bootstrap. But feel free to discard and recreate your own HTML template if you want.
P.S. The default Core Boxx HTML template does come with some goodies out of the box though – Loading spinner, dialog box, toast messages, and progressive web app. See PAGE-demo.php
for quick examples.
5B) JAVASCRIPT – LIST CONTENTS
var content = {
// (A) FETCH & LIST CONTENT
list : () => {
// (A1) GET & RESET HTML LIST WRAPPER
let cList = document.getElementById("cList");
cList.innerHTML = "";
// (A2) FETCH & DRAW CONTENT ROWS
cb.api({
mod : "content", req : "getAll", passmsg : false,
onpass : res => {
if (res.data != null) { for (let c of res.data) {
let row = document.createElement("li");
row.id = "content" + c.id;
row.className = "list-group-item";
row.innerHTML = c.content;
row.onclick = () => { content.addedit(c.id); };
cList.appendChild(row);
}}
}
});
},
// ...
};
window.addEventListener("load", content.list);
content.list()
will run on page load.- Remember the
API-content.php
earlier? We are simply making an AJAX call to the API endpoint to fetch the contents, and draw them in an HTML list.
5C) JAVASCRIPT – ADD/EDIT CONTENT
// (B) GENERATE & SHOW ADD/EDIT CONTENT SCREEEN
// id : content id, none for new
addedit : id => {
// (B1) BUILD HTML
let txt = id ? document.getElementById("content"+id).innerHTML : "";
cb.hPages[1].innerHTML =
`<form onsubmit="return content.save()">
<input type="hidden" readonly id="cid" value="${id?id:""}">
<textarea class="form-control" id="ctxt" required>${txt}</textarea>
<div class="mt-3">
<input type="button" class="btn btn-danger" value="Back" onclick="cb.page(0)">
<input type="submit" class="btn btn-primary" value="Save">
</div>
</form>`;
// (B2) SWITCH PAGE SECTION
cb.page(1);
},
- There are five HTML “page sections”
<div id="cb-page-N">
in the default template. - When the user clicks on “add” or “edit” any content,
addedit()
will generate an HTML form in<div id="cb-page-2">
.
5D) JAVASCRIPT – SAVE CONTENT
// (C) SAVE CONTENT
save : () => {
// (C1) GET FORM DATA
let data = {
content : document.getElementById("ctxt").value,
id : document.getElementById("cid").value
};
if (data.id=="") { delete data.id; }
// (C2) API SAVE
cb.api({
mod : "content", req : "save", data : data,
onpass : () => {
cb.page(0);
content.list();
}
});
return false;
}
Lastly, save()
grabs the ID and content text from the HTML form – Submits it to the API endpoint for processing.
FAQ & MORE EXAMPLES
The above simple tutorial covered basic development. Here are a couple more extras and common examples.
CHANGED DOMAIN OR PATH
- Update
HOST_BASE
inlib/CORE-Config.php
. Make sure to include the path (with a trailing slash) if not deployed at the base URL. E.G.http://site.com/coreboxx/
- Delete the existing
.htaccess
file. - Create
init.php
.require "lib/CORE-Go.php";
$_CORE->Route->init();
- Access
http://site.com/init.php
, this will automatically regenerate the.htaccess
file. - Delete
init.php
. - Clear the service worker and browser cache if you want to be “totally safe”.
PAGES ARE NOT RESOLVING OR SHOWING PROPERLY
- Make sure that
MOD_REWRITE
is enabled in Apache. - Make sure that
AllowOverride
is set properly in Apache. - If using
https://
, make sure that your SSL cert and virtual host are properly set. - In Mac/Linux, make sure that Apache has permission to read the files and folders.
Here is a quick “working example” of a test site on my WAMP server:
<VirtualHost site.com:443>
DocumentRoot "D:\http"
ServerName site.com:443
SSLEngine On
SSLCertificateFile "C:/xampp/apache/conf/ssl.crt/server.crt"
SSLCertificateKeyFile "C:/xampp/apache/conf/ssl.key/server.key"
<Directory "D:\http">
Options Indexes FollowSymLinks Includes ExecCGI
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
SUPPORTING CORS FOR API CALLS
define("API_CORS", false); // no cors support
define("API_CORS", true); // any domain + mobile apps
define("API_CORS", "site-a.com"); // this domain only
define("API_CORS", ["site-a.com", "site-b.com"]);
For you guys who have to work with multiple sites, or even support mobile apps – Remember to enable CORS in lib/CORE-Config.php
. The easiest way is to set API_CORS
to true
… But that is somewhat a security risk – You decide.
ONLY OPEN CERTAIN API ENDPOINTS FOR CORS
If you want to enable CORS for certain API endpoints only, create a corresponding lib/CORS-API-MODULE.php
. For example, opening an items API.
<?php
// CHECK ALLOWED DOMAINS
$allowed = ["sitea.com", "siteb.com"];
$access = in_array($_OGN_HOST, $allowed);
// IF YOU WANT TO FURTHER RESTRICT BY THE REQUEST
// E.G. ALLOW CORS TO FETCH ITEMS ONLY
if ($access && $_REQ!="get") { $access = false; }
URL ROUTES
// (A) MANUAL PATH OVERRIDE
$override = function ($path) {
// (A1) EXAMPLE - REDIRECT TO LOGIN PAGE IF NOT SIGNED IN
if (!isset($_SESSION["user"]) && $path!="login/") {
header("Location: " . HOST_BASE . "login/");
exit();
}
// (A2) EXAMPLE - TWEAK PATH BASED ON USER ROLE
if ($path=="products/" && $_SESSION["user"]["role"]=="admin") {
$path = "admin/products/";
}
// (A3) RETURN OVERIDDEN PATH
return $path;
);
// (B) EXACT PATH ROUTING
// EXAMPLE - HTTP://SITE.COM/ WILL LOAD PAGES/MYHOME.PHP
// EXAMPLE - HTTP://SITE.COM/MYPAGE/ WILL LOAD PAGES/MYPAGE.PHP
$routes = [
"/" => "myhome.php",
"mypage/" => "mypage.php",
];
// (C) WILDCARD PATH ROUTING
// EXAMPLE - HTTP://SITE.COM/PRODUCTS/* WILL LOAD PAGES/MYPRODUCTS.PHP
$wild = [
"products/" => "myproducts.php"
];
The default pretty URL behavior is to map http://site.com/PATH
to pages/PAGE-PATH.php
. If you want to tweak that behavior, add your own rules in CORE-Routes.php
.
$override
Has the highest priority.$routes
Exact matches have the next higher priority.$wild
Followed by wildcard matches.- If all of the above fails to match, it falls back to the “default match
PATH
toPAGE-PATH.php
“.
EXAMPLE – CONTROL ACCESS TO ADMIN PAGES
MAP ALL ADMIN PAGES TO A “CHECK PAGE”
$wild = [
"admin/" => "ADM-check.php"
];
Want to restrict some pages to administrators only? Start by doing a wildcard map to an “access check script”.
ADMIN PERMISSION CHECK
// (A) NO PERMISSION - REDIRECT TO LOGIN PAGE
if (!isset($_SESSION["user"])) { $_CORE->redirect("login/"); }
// (B) OK - LOAD PAGE
$_PATH = explode("/", rtrim($_PATH, "/"));
array_shift($_PATH);
$_CORE->Route->load(
count($_PATH)==0 ? "ADM-home.php" : "ADM-" . implode("-", $_PATH) . ".php"
);
Do a check against the session or cookie – Whichever you are using to track user login. Then, use $_CORE->Route->load()
to load the proper page.
EXAMPLE – CATEGORY & PAGINATION
MAP ALL PRODUCT PAGES TO HANDLER SCRIPT
$wild = [
"products/" => "PDT-list.php"
];
Want to use the URL for pagination or show different products? Use the wildcard once again.
PRODUCTS HANDLER SCRIPT
// (A) EXPLODE CURRENT PATH INTO AN ARRAY FIRST
$_PATH = explode("/", rtrim($_PATH, "/"));
// $_PATH[0] - "products"
// $_PATH[1] - category
// $_PATH[2] - page number
// (B) INVALID
if (count($_PATH)!=3) {
$_CORE->Route->load("PAGE-404.php", 404);
exit();
}
// (C) USE PRODUCTS LIBRARY TO GET
$_CORE->load("Products");
$products = $_CORE->Products->get($_PATH[1], $_PATH[2]);
// ... DRAW HTML ...
/*
function get ($category, $page) {
// GET TOTAL NUMBER OF ENTRIES
$entries = $this->DB->fetchCol(
"SELECT COUNT(*) AS `total` FROM `products` WHERE `category`=?", [$category]
);
// PAGINATION CALCULATIONS
// "entries" => total number of entries
// "total" => total number of pages
// "now" => current page
// "x" => to be used for your SQL LIMIT X,Y
// "y" => to be used for your SQL LIMIT X,Y
$page = $this->core->paginator($entries, $page);
// GET ENTRIES
return $this->DB->fetchAll(
"SELECT * FROM `products` WHERE `category`=? LIMIT ?,?",
[$category, $page["x"], $page["y"]]
);
}
*/
Nice and simple framework.. clean! i’ve tried it.
for information, dynamic properties are deprecated in PHP8.2.
if anyone has encountered these error.. just declare the class as inheritance from stdClass.
example at lib-Core.php :
class CoreBoxx{…} -> class CoreBoxx extends stdClass{…}
Thanks for sharing – Will update the library in due time. Another way is to set
#[\AllowDynamicProperties]
.https://www.php.net/manual/en/class.allowdynamicproperties.php
Hello!
Thanks for your work!!!
I haven’t tried it yet though.
I’m not an expert, so I thought it was enough to copy and import the sql files!
Unfortunately, it throws an error when creating the keys.
“Fatal error: Uncaught RuntimeException: Unable to create the key in D:\xampp\htdocs\codeboxx\lib\webpush\web-token\jwt-core\Util\ECKey.php:98 Stack trace: #0 D:\xampp \htdocs\codeboxx\lib\webpush\web-token\jwt-core\Util\ECKey.php(72): Jose\Component\Core\Util\ECKey::createECKeyUsingOpenSSL(‘P-256’)
Could you help me find out where to look for the problem? What else should I install?
Maybe a step-by-step installation guide or install script? (CORE-intall.php also throws this.)
Thank you very much in advance!
Gabor
As it is –
1) Make sure that the OpenSSL extension is installed and enabled in PHP.
2) Step-by-step instructions and documentation of Web Push module – https://code-boxx.com/core-boxx-push-notifications-module/
{“status”:0,”message”:”Call to a member function execute() on bool”,”code”:0,”file”:”C:\\xampp\\htdocs\\box\\lib\\LIB-DB.php”,”line”:40}
I keep getting that, i want to use sqlite as a database.
SQLite works a little differently. You will need to rebuild your own database module.
https://www.sqlitetutorial.net/sqlite-php/
Thank you! I will do my research for that and try to build it. I want SQLite since my most of the project built with this framework will be small projects or prototypes that I want to move fast from server to server or locally.
Thanks much for this. Found a small typo:
In the line below, instead of User.php, it should be Users.php:
Since we are creating a user module, we create a corresponding lib/User.php – Take note of the capital U.
Got it – Updated
good topic
Thanks again for your FREE work. It is helpful & appreciated.
I am attempting to login via the API from external site, but cannot seem to login via cURL. Although I set CURLOPT_POSTFIELDS, the email & password do not seem to be available as $_POST variables in the session.php script under api folder.
To try debug, I have tried to force with test data directly, but even then the mysite/api/users/verify returns {“status”:0,”message”:”Please sign in first”,”data”:null,”page”:null}
https://code-boxx.com/php-curl-cookies/
Save the session cookie on login. Send the cookie on subsequent calls.
This seems a very elegant, starting point. Thanks
Is there a forum or further help facility, for questions not covered here?
Sorry, I don’t have the time nor capacity to manage yet another free help forum… Just report any bugs or post requests here.
https://code-boxx.com/faq/#help