Core Boxx Modular PHP Framework (Plug & Play System)

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

  1. Just access http://YOUR-SITE.com in your browser to launch the installer.
  2. 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 BOXX MODULES

Download, plugin, and expand your system.

  1. Users – User register, login, logout, forgot password, and admin functions.
  2. One Time Password – For added security.
  3. Resumable Upload – Resumable uploads with FlowJS and FlowPHP.
  4. Dynamic Content – Store and retrieve contents from the database.
  5. Likes & Dislikes Reaction – Or was it “upvote and downvote”?
  6. Comments – Allow registered users to comment on your posts/products/images/videos.
  7. Calendar – Show and manage events.
  8. 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

lib/SQL-Contents.sql
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

lib/LIB-Content.php
<?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 capital C.
  • 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() and class Core in lib/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

lib/API-content.php
<?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/REQUESTMODULE will be parsed into $_MOD and REQUEST into $_REQ.
  • In this example, the content endpoint http://site.com/api/content will map to lib/API-content.php.
  • The request $_REQ is to be manually handled in the script itself. As above, we use a switch ($_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

pages/PAGE-content.php
<?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 at http://site.com/content.
    • pages/PAGE-hello-world.php can be accessed at http://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

assets/content.js
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

assets/content.js
// (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

assets/content.js
// (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 in lib/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:

httpd.conf OR httpd-vhosts.conf
<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

lib/CORE-Config.php
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.

lib/CORS-API-items.php
<?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

lib/CORE-Routes.php
// (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 to PAGE-PATH.php“.

 

EXAMPLE – CONTROL ACCESS TO ADMIN PAGES

MAP ALL ADMIN PAGES TO A “CHECK PAGE”

lib/CORE-Routes.php
$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

pages/ADM-check.php
// (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

lib/CORE-Routes.php
$wild = [
  "products/" => "PDT-list.php"
];

Want to use the URL for pagination or show different products? Use the wildcard once again.

 

PRODUCTS HANDLER SCRIPT

pages/PDT-list.php
// (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"]]
  );
}
*/

 

14 thoughts on “Core Boxx Modular PHP Framework (Plug & Play System)”

  1. 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{…}

  2. 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

  3. {“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.

      1. 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.

  4. 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.

  5. 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}

  6. This seems a very elegant, starting point. Thanks
    Is there a forum or further help facility, for questions not covered here?

Leave a Comment

Your email address will not be published. Required fields are marked *