├── .env.example ├── .gitignore ├── .htaccess ├── LICENSE ├── README.md ├── app ├── Controllers │ ├── AuthController.php │ └── Controller.php ├── CssGenerator.php ├── Framework │ ├── Database │ │ └── Database.php │ ├── Helpers │ │ └── EnvGenerator.php │ ├── Migrations │ │ └── Migrator.php │ ├── Model │ │ └── BaseModel.php │ ├── Schema │ │ └── Schema.php │ └── Utilities │ │ ├── BaseUtility.php │ │ ├── Encrypt.php │ │ ├── HttpRequest.php │ │ ├── JwtUtility.php │ │ ├── Log.php │ │ ├── Request.php │ │ ├── Response.php │ │ └── Upload.php ├── Middlewares │ └── Authentication.php ├── Models │ └── UserModel.php └── View.php ├── composer.json ├── core ├── Route.php └── env_loader.php ├── public ├── .htaccess ├── css │ └── style.css └── index.php ├── routes ├── api.php └── web.php ├── server.php ├── star ├── storage ├── private │ └── .gitignore └── public │ └── .gitignore └── views ├── Home.php ├── Test.php ├── layouts └── main.php └── widgets ├── footer.php └── header.php /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Sparkle 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG="true" 5 | APP_URL="http://localhost:8000" 6 | 7 | 8 | MYSQL_HOST=127.0.0.1:3306 9 | MYSQL_DB=sparkle 10 | MYSQL_USERNAME=root 11 | MYSQL_PASSWORD= 12 | 13 | SERVER_SALT="36bybdfs5238nf84498" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /core/Environment.php 3 | .env 4 | .DS_Store 5 | /storage/private/* 6 | /storage/public/* -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | Options -Indexes 2 | 3 | RewriteEngine On 4 | RewriteCond %{REQUEST_URI} !/css 5 | RewriteCond %{REQUEST_URI} !/uploads 6 | RewriteCond %{REQUEST_URI} !/img 7 | RewriteCond %{REQUEST_URI} !/svg 8 | RewriteCond %{REQUEST_URI} !/lofty-admin 9 | RewriteCond %{REQUEST_URI} !/ads.txt 10 | RewriteCond %{REQUEST_URI} !/favicon.ico 11 | RewriteCond %{REQUEST_URI} !/icon.png 12 | RewriteRule ^([^/]+)/? index.php?url=$1 [L,QSA] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gabriel Ikuejawa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sparkle 2 | 3 | ## Introduction 4 | 5 | Sparkle is a free to use open source php MVC framework for building web applications. It's very simple to work with and has a very low learning curve. 6 | 7 | Getting started with sparkle is very easy. All you need to do is have `php version >= 8` and `composer` installed on you computer. 8 | 9 | >composer is a package manager for php 10 | 11 | Then in the root directory of the project, run this command: 12 | 13 | ```bash 14 | composer create-project sparkle/sparkle . 15 | ``` 16 | 17 | Alternatively you can create a new sparkle project in a new directory: 18 | 19 | ```bash 20 | composer create-project sparkle/sparkle my-project 21 | ``` 22 | 23 | To start the server, in the root directory of the project, run this command: 24 | ```bash 25 | php star light 26 | ``` 27 | This would start the php dev server on port 8000 and load the environment variables in the `.env` file if you have one. 28 | 29 | 30 | In production, you can load the environment variables using this command: 31 | ```bash 32 | php star configure 33 | ``` 34 | 35 | Sparkle is built to be light weight and reliable so it doesn't come with any third party packages but you can extend sparkle by installing third party packages via composer. 36 | 37 | 38 | ## Views 39 | Sparkle was designed for REST api development but also supports basic view templating. 40 | 41 | To create a new web page, go to the `views` directory and create a new file. 42 | Then in the `routes/web.php`, add a new route like so: 43 | 44 | ```php 45 | Route::get('/some-route', function() { 46 | View::make("ViewName"); 47 | }); 48 | 49 | ``` 50 | 51 | Inject data into your views by adding an associative array of key value pairs as the second argument like so: 52 | ```php 53 | Route::get('/some-route', function() { 54 | View::make("ViewName", ['site_title'=>env('APP_NAME')]); 55 | }); 56 | ``` 57 | 58 | use the variable in your view file using double curly brackets like so: 59 | ```html 60 |

{{ site_title }}

61 | ``` 62 | 63 | >You could aslo use a controller to handle view rendering 64 | 65 | create a controller by going to the `app/Controllers` directory and creating a new file with a class extending the base controller. 66 | 67 | ## Controllers 68 | ```php 69 | 78 | ``` 79 | 80 | ## Models 81 | Now lets create a model for our user controller. You can do this by going to the `app/Models` directory and create a file, let's say `UserModel.php` 82 | 83 | now add this snippet: 84 | 85 | ```php 86 | id(); 99 | $schema->string('username'); 100 | $schema->string('firstname'); 101 | $schema->string('password'); 102 | $schema->string('email'); 103 | $schema->string('lastname'); 104 | $schema->create(); 105 | } 106 | } 107 | 108 | ?> 109 | ``` 110 | >The name of the table in this model is `users` as defined here `protected static $tableName = 'users'` 111 | >Note that timestamps `date_created` and `date_updated` are added by default to entries to each table; 112 | >You do not need to create extra migration files after creating your models just run the migrate command and you're good to go! 113 | 114 | your user model should look like this: 115 | 116 | ![User model](https://drive.google.com/uc?export=view&id=1K1oVeIN37yKoAflu5zyoCJeqglzdgArw) 117 | 118 | ## Setting up the database 119 | 120 | Now that we have our model set up, we need to make sure our connection to the database is done. 121 | let's configure our environment variables by copying all the contents of `.env.example.php` in the root directory into a new file in the root directory `.env` 122 | 123 | Now create a new database with your desired credentials. You can create a database using a gui like phpmyadmin which comes with xampp or via the mysql cli. 124 | >Once that is done, make sure you start the mysql server. 125 | 126 | 127 | Now edit the values of the .env files with the database credentials you just created. In this tutorial, we're on local host so the `MYSQL_HOST` is `127.0.0.1:3306` 128 | >3306 is the default mysql port 129 | or database name is `sparkle` and our username is `root` the password is blank but you should use your desired credentials. 130 | 131 | ``` 132 | DB_CONNECTION=pdo 133 | MYSQL_HOST=127.0.0.1:3306 134 | MYSQL_DB=sparkle 135 | MYSQL_USERNAME=root 136 | MYSQL_PASSWORD= 137 | ``` 138 | 139 | now that we have our credentials set up, reload the application configuration by running this command: 140 | ```bash 141 | php star configure 142 | ``` 143 | 144 | Now let's migrate our UserModel by running this command: 145 | ```bash 146 | php star migrate 147 | ``` 148 | 149 | A new table with the name users has been created in the database 150 | 151 | you can add new columns to your table if you want to or edit existing columns. 152 | >after changes to your model, you need to run `php star migrate` for your changes to effect 153 | 154 | ## Handling requests 155 | 156 | In this example, we're going to be working with api. 157 | To handle requests, we create methods to our controllers and receive data view the `$request` object parameter like so: 158 | ```php 159 | public function doSomething(Request $request) { 160 | $req_body = $request->body; 161 | $req_query = $request->query; 162 | $req_params = $request->params; 163 | $req_extras = $request->extras; 164 | } 165 | ``` 166 | The request object carries the body, query, params and extras properties added to the request. 167 | 168 | Now let's create a method to add users and fetch users. In your UserController, add this snippet: 169 | 170 | ```php 171 | body; 182 | 183 | $firstname = $req_data->firstname; 184 | $lastname = $req_data->lastname; 185 | $password = $req_data->password; 186 | $username = $req_data->username; 187 | $email = $req_data->email; 188 | 189 | $user = new UserModel(); 190 | 191 | $user->create([ 192 | 'id'=>null, 193 | 'username'=>$username, 194 | 'firstname'=>$firstname, 195 | 'lastname'=>$lastname, 196 | 'email'=>$email, 197 | 'password'=>password_hash($password, PASSWORD_BCRYPT), 198 | ]); 199 | 200 | Response::send(array( 201 | 'message'=>'Users created', 202 | 'status'=>1 203 | ), 400); 204 | } 205 | 206 | public static function getUsers(Request $request) { 207 | $user = new UserModel(); 208 | 209 | $all_users = $user->all(); 210 | 211 | Response::send(array( 212 | 'message'=>'Users fetched successfully', 213 | 'status'=>1, 214 | 'data'=> $all_users 215 | ), 200); 216 | } 217 | } 218 | ``` 219 | 220 | Your controller should look like this: 221 | 222 | ![User controller](https://drive.google.com/uc?export=view&id=1egczTEr-duBKQsUmefgNebMdPYiyqgcn) 223 | 224 | The above snippets creates a new user by creating a new instance of the UserModel and calling the create method like so: 225 | 226 | ```php 227 | $user = new UserModel(); 228 | 229 | $user->create([ 230 | 'id'=>null, 231 | 'username'=>$username, 232 | 'firstname'=>$firstname, 233 | 'lastname'=>$lastname, 234 | 'email'=>$email, 235 | 'password'=>password_hash($password, PASSWORD_BCRYPT), 236 | ]); 237 | ``` 238 | 239 | >kindly note that the values in the request body are gotten from the request made to this endpoint and you should validate your requests. 240 | 241 | Now lets bind an endpoint to this route. In the `routes/api.php`, add this: 242 | ```php 243 | Route::post('/users/add', [App\Controllers\UserController::class, 'addUser']); 244 | Route::get('/users/fetch', [App\Controllers\UserController::class, 'getUsers']); 245 | ``` 246 | 247 | you can also bind your routes like this: 248 | 249 | ```php 250 | use App\Controllers\UserController; 251 | 252 | Route::post('/users/get', function($request) { 253 | $userController = new UserController(); 254 | UserController->getUsers($request); 255 | }); 256 | 257 | ``` 258 | > note that we imported the UserController using this `use App\Controllers\UserController;`. 259 | 260 | 261 | We also fetch all users in the table using this snippet: 262 | 263 | ```php 264 | $user = new UserModel(); 265 | $all_users = $user->all(); 266 | ``` 267 | 268 | 269 | Other methods for working with models are: 270 | ```php 271 | Model::insertMany() 272 | 273 | $model->findById() 274 | 275 | $model->where('balance', 100)->get() 276 | $model->where('email', "examle@gmail.com")->first() 277 | 278 | ``` 279 | 280 | To update your entries in the table, we do it like this: 281 | 282 | ```php 283 | $model = new Model(); 284 | $model->where('email', "examle@gmail.com")->and("first_name", "james")->update("lastname", "felix"); 285 | ``` 286 | This evaluates to `UPDATE model SET lastname=felix WHERE email="example@gmail.com AND first_name = "james"` 287 | 288 | 289 | Request parameters are like variables and can be added to endpoints like so: 290 | 291 | ```php 292 | Route::post('/profile/fetch/:id', [App\Controllers\Authentication::class, 'getProfile']); 293 | ``` 294 | 295 | Where `:id` is a request parameter and can be accessed via the request object like so: 296 | ```php 297 | $request->params->id 298 | ``` 299 | 300 | ## Middlewares 301 | Middlewares are found in the `app/Middlewares` directory and you can create your middlewares there. 302 | Sparkle comes with an Authentication middleware for checking generated tokens generated via the `AuthController`; 303 | Middlewares are basically methods of classes that intercept the request before getting to where the request is handled: 304 | 305 | this is an example: 306 | ```php 307 | Route::get('/v1/profile', [App\Middlewares\Authentication::class, 'check'], [App\Controllers\AuthController::class, 'getProfile']); 308 | ``` 309 | 310 | `[App\Middlewares\Authentication::class, 'check']` is the middleware that runs before the method that handles the request 311 | 312 | The default Authentication middleware validates the token set in the header of the incoming request and sets the user_id property to the request like so: 313 | 314 | ```php 315 | $request->setExtras(['user_id'=> 45]); 316 | ``` 317 | 318 | >note that middlewares must return the request object for the request to continue to the next handler 319 | 320 | This is an image of Authentication middleware: 321 | 322 | 323 | ![Authentication middleware](https://drive.google.com/uc?export=view&id=1Z_O4DcPDWThYRldc7WpQ6N8WEbkimmux) 324 | 325 | 326 | ## Api request 327 | 328 | Sparkle comes with a utility class for sending and listening to api requests. 329 | Create a request by importing the HttpRequest utility to your controller like so: 330 | 331 | ```php 332 | use App\Framework\Utilities\HttpRequest; 333 | 334 | class MyController extends Controller { 335 | public function doSomething($request) { 336 | $res = HttpRequest::post("api.example.com/post", [ 337 | "foo"=>"bar" 338 | ]); 339 | 340 | $res->status_code; // gets http response code 341 | $res->data; // gets the data returned 342 | } 343 | } 344 | 345 | ``` 346 | 347 | You can also add headers to your requests by chaining the withHeaders method passing an associative array as an arguments like so: 348 | 349 | ```php 350 | use App\Framework\Utilities\HttpRequest; 351 | 352 | class MyController extends Controller { 353 | public function doSomething($request) { 354 | $headers = [ 355 | 'Content-Type'=> 'application/json', 356 | 'Accept'=> 'application/json' 357 | ]; 358 | 359 | $res = HttpRequest::withHeaders($headers)::post("api.example.com/post", [ 360 | "foo"=>"bar" 361 | ]); 362 | 363 | $res->status_code; // gets http response code 364 | $res->data; // gets the data returned 365 | } 366 | } 367 | 368 | ``` 369 | 370 | >do not pass data to get requests 371 | 372 | HttpRequest has the following methods for sending requests: 373 | 374 | ```php 375 | HttpRequest::patch("api.example.com/post", []); 376 | HttpRequest::put("api.example.com/post", []); 377 | HttpRequest::post("api.example.com/post", []); 378 | HttpRequest::delete("api.example.com/post", []); 379 | HttpRequest::get("api.example.com/post"); 380 | 381 | ``` 382 | 383 | 384 | ## Author's note 385 | More features are been added to the framework. you can support me by buying me a coffee 386 | 387 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/loftytech) 388 | 389 | If you notice any bugs or you have a feature you want us to include, kindly reachout to me on [twitter](https://twitter.com/loftycodes) 390 | 391 | thanks! -------------------------------------------------------------------------------- /app/Controllers/AuthController.php: -------------------------------------------------------------------------------- 1 | body; 13 | 14 | $firstname = isset($req_data->firstname) ? trim($req_data->firstname) : ""; 15 | $lastname = isset($req_data->lastname) ? trim($req_data->lastname) : ""; 16 | $password = isset($req_data->password) ? $req_data->password : ""; 17 | $username = isset($req_data->username) ? trim(strtolower($req_data->username)) : ""; 18 | $email = isset($req_data->email) ? strtolower(trim($req_data->email)) : ""; 19 | 20 | 21 | if (strlen($firstname) >= 3 && strlen($firstname) < 32) { 22 | if (strlen($lastname) >= 3 && strlen($lastname) < 32) { 23 | if (filter_var($email, FILTER_VALIDATE_EMAIL)) { 24 | $user = new UserModel(); 25 | $ests = $user->where('email', $email)->get(); 26 | if (!$user->where('email', $email)->get()) { 27 | $user->create([ 28 | 'id'=>null, 29 | 'username'=>$username, 30 | 'firstname'=>$firstname, 31 | 'lastname'=>$lastname, 32 | 'email'=>$email, 33 | 'password'=>password_hash($password, PASSWORD_BCRYPT), 34 | ]); 35 | 36 | $newUser = $user->where('email', $email)->first(); 37 | 38 | $user_id = $newUser->id; 39 | $user_data = array('email'=>$email, 'user_id'=>$user_id, 'exp'=>(time() + 86400)); 40 | $auth_token = JwtUtility::get_token($user_data); 41 | Response::send(array( 42 | 'message'=>'user registered successfully', 43 | 'status'=>1, 44 | 'data'=> [ 45 | 'token'=>$auth_token 46 | ] 47 | ), 200); 48 | } else { 49 | Response::send(array( 50 | 'message'=>'Email already registered!', 51 | 'status'=>0 52 | ), 400); 53 | } 54 | } else { 55 | Response::send(array( 56 | 'message'=>'Invalid email!', 57 | 'status'=>0 58 | ), 400); 59 | } 60 | } else { 61 | Response::send(array( 62 | 'message'=>'Invalid lastname character length', 63 | 'status'=>0 64 | ), 400); 65 | } 66 | }else { 67 | Response::send(array( 68 | 'message'=>'Invalid firstname character length', 69 | 'status'=>0 70 | ), 400); 71 | } 72 | } 73 | 74 | public static function login(Request $request) { 75 | $req_data = $request->body; 76 | 77 | $email = isset($req_data->email) ? strtolower($req_data->email) : ""; 78 | $password = isset($req_data->password) ? $req_data->password : ""; 79 | 80 | $user = new UserModel(); 81 | if ($user->where('email', $email)->first()) { 82 | if (password_verify($password, $user->where('email', $email)->first()->password)) { 83 | $user = $user->where('email', $email)->first(); 84 | $user_data = array('email'=>$email, 'user_id'=>$user->id, 'exp'=>(time() + 86400)); 85 | $auth_token = JwtUtility::get_token($user_data); 86 | 87 | Response::send(array( 88 | 'message'=>'user logged in successfully', 89 | 'status'=>1, 90 | 'data'=> [ 91 | 'token'=>$auth_token, 92 | 'firstName'=>$user->firstname, 93 | 'lastName'=>$user->lastname, 94 | 'email'=>$user->email, 95 | ] 96 | ), 200); 97 | } else { 98 | Response::send(array( 99 | 'message'=>'Invalid email or password', 100 | 'status'=>0 101 | ), 400); 102 | } 103 | } else { 104 | Response::send(array( 105 | 'message'=>'Invalid email or password', 106 | 'status'=>0 107 | ), 400); 108 | } 109 | } 110 | 111 | 112 | public static function getProfile(Request $request) { 113 | $req_data = $request->body; 114 | 115 | $user = new UserModel(); 116 | 117 | Response::send(array( 118 | 'message'=>'profile fetched successfully', 119 | 'status'=>1, 120 | 'data'=> $user->findById($request->extras->user_id) 121 | ), 200); 122 | } 123 | } -------------------------------------------------------------------------------- /app/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME'), 11 | ]; 12 | View::useLayout($viewName, $data); 13 | } 14 | 15 | public static function useTemplate($viewName, $params = null) { 16 | $params = (object) $params; 17 | 18 | $data = [ 19 | 'site_title'=>env('APP_NAME'), 20 | ]; 21 | View::make($viewName, $data); 22 | } 23 | 24 | 25 | 26 | public static function testCreateView($viewName, $params = null) { 27 | $params = (object) $params; 28 | 29 | $data = [ 30 | 'site_title'=>env('APP_NAME'), 31 | ]; 32 | View::makeTest($viewName, $data); 33 | } 34 | } 35 | 36 | ?> -------------------------------------------------------------------------------- /app/CssGenerator.php: -------------------------------------------------------------------------------- 1 | scssToNestedArray($scss); 18 | $obj_arr = json_decode($json_string); 19 | 20 | $this->generated_object = $obj_arr; 21 | 22 | $this->nestedArraysTocss($obj_arr); 23 | 24 | $this->generateCss(); 25 | } 26 | 27 | private function scssToNestedArray($scss) { 28 | $processedContent = str_replace('"', "'", $scss); 29 | 30 | // convert selectors to objects 31 | $processedContent = preg_replace_callback("/(.*)(\{)/", function ($matches) { 32 | return '"'.trim($matches[1]).'":'.'{'; 33 | }, $processedContent); 34 | 35 | // convert css properties to keys 36 | $processedContent = preg_replace_callback("/(.*)(:)(|\s+)(.*)(|\s+)(;)/", function ($matches) { 37 | return '"'.trim($matches[1]).'": "'.trim($matches[4]).'",'; 38 | }, $processedContent); 39 | 40 | // cleaning up the json string 41 | $processedContent = preg_replace_callback('/(})(|\s+)(")(.*)/', function ($matches) { 42 | return trim($matches[1]).', "'.trim($matches[4]); 43 | }, $processedContent); 44 | 45 | $processedContent = preg_replace_callback('/(",)(|\s+)(})/', function ($matches) { 46 | return '" }'; 47 | }, $processedContent); 48 | 49 | return "{".$processedContent."}"; 50 | } 51 | 52 | private function nestedArraysTocss($nestedArray, $parent_selector = "") { 53 | foreach ($nestedArray as $key => $value) { 54 | 55 | if (trim($key)[0] == "@") { 56 | 57 | $this->extractMediaQueries($value, $parent_selector, $key); 58 | } else { 59 | if (gettype($value) == "string") { 60 | 61 | $full_selector = $parent_selector; 62 | if ($parent_selector == "") { 63 | $full_selector = $key; 64 | } 65 | 66 | if (trim($key)[0] == ":") { 67 | $full_selector = trim($parent_selector) . trim($key); 68 | } 69 | 70 | if (!array_key_exists($full_selector, $this->compiled_array)) { 71 | $this->compiled_array[$full_selector] = ["$key: $value;"]; 72 | } else { 73 | $style_arr = $this->compiled_array[$full_selector]; 74 | array_push($style_arr, "$key: $value;"); 75 | 76 | $this->compiled_array[$full_selector] = $style_arr; 77 | } 78 | } else { 79 | 80 | foreach ($value as $child_key => $child_value) { 81 | if (gettype($child_value) == "string") { 82 | $full_selector = $parent_selector . " ". $key; 83 | 84 | if ($parent_selector == "") { 85 | $full_selector = $key; 86 | } 87 | 88 | if (trim($key)[0] == ":") { 89 | $full_selector = trim($parent_selector) . trim($key); 90 | } 91 | 92 | if (!array_key_exists($full_selector, $this->compiled_array)) { 93 | $this->compiled_array[$full_selector] = ["$child_key: $child_value;"]; 94 | } else { 95 | $style_arr = $this->compiled_array[$full_selector]; 96 | array_push($style_arr, "$child_key: $child_value;"); 97 | 98 | $this->compiled_array[$full_selector] = $style_arr; 99 | } 100 | } else { 101 | 102 | $trim_key = trim($key); 103 | $trim_child_key = trim($child_key); 104 | $trim_parent_selector = trim($parent_selector); 105 | 106 | $full_selector = $trim_parent_selector . " " . $trim_key . " " . $trim_child_key; 107 | 108 | // if ($parent_selector == "") { 109 | // $full_selector = $trim_key . " " . $trim_child_key; 110 | // } 111 | 112 | $full_child_key = ""; 113 | 114 | $child_key_arr = explode(",", $trim_child_key); 115 | 116 | foreach ($child_key_arr as $arr_key => $child_key_value) { 117 | $trimed_value = trim($child_key_value); 118 | $full_child_key = $full_child_key . ($arr_key > 0 ? ", " : "") . $trim_parent_selector . ($trim_key[0] == ":" ? $trim_key : " " . $trim_key) . ($trimed_value[0] == ":" ? $trimed_value : " " . $trimed_value); 119 | } 120 | 121 | $full_selector = $full_child_key; 122 | 123 | 124 | if ($trim_child_key[0] == "@") { 125 | $this->extractMediaQueries($value->$child_key, $trim_parent_selector . ($trim_key[0] == ":" ? $trim_key : " " . $trim_key), $trim_child_key); 126 | } else { 127 | $this->nestedArraysTocss($value->$child_key, $full_selector); 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | } 135 | } 136 | 137 | private function extractMediaQueries($nestedArray, $parent_selector, $scope_selector = "") { 138 | $this->temp_arr = []; 139 | 140 | $this->resolveCss($nestedArray, $parent_selector); 141 | 142 | array_push($this->media_query_arr, [$scope_selector => $this->temp_arr]); 143 | } 144 | 145 | 146 | private function resolveCss($nestedArray, $parent_selector) { 147 | foreach ($nestedArray as $key => $value) { 148 | if (gettype($value) == "string") { 149 | $full_selector = $parent_selector; 150 | if ($parent_selector == "") { 151 | $full_selector = $key; 152 | } 153 | 154 | if (trim($key)[0] == ":") { 155 | $full_selector = trim($parent_selector) . trim($key); 156 | } 157 | 158 | if (!array_key_exists($full_selector, $this->temp_arr)) { 159 | $this->temp_arr[$full_selector] = ["$key: $value;"]; 160 | } else { 161 | $style_arr = $this->temp_arr[$full_selector]; 162 | array_push($style_arr, "$key: $value;"); 163 | 164 | $this->temp_arr[$full_selector] = $style_arr; 165 | } 166 | } else { 167 | 168 | foreach ($value as $child_key => $child_value) { 169 | if (gettype($child_value) == "string") { 170 | $full_selector = $parent_selector . " ". $key; 171 | 172 | if ($parent_selector == "") { 173 | $full_selector = $key; 174 | } 175 | 176 | if (trim($key)[0] == ":") { 177 | $full_selector = trim($parent_selector) . trim($key); 178 | } 179 | 180 | if (!array_key_exists($full_selector, $this->temp_arr)) { 181 | $this->temp_arr[$full_selector] = ["$child_key: $child_value;"]; 182 | } else { 183 | $style_arr = $this->temp_arr[$full_selector]; 184 | array_push($style_arr, "$child_key: $child_value;"); 185 | 186 | $this->temp_arr[$full_selector] = $style_arr; 187 | } 188 | } else { 189 | 190 | $trim_key = trim($key); 191 | $trim_child_key = trim($child_key); 192 | $trim_parent_selector = trim($parent_selector); 193 | 194 | $full_selector = $trim_parent_selector . " " . $trim_key . " " . $trim_child_key; 195 | 196 | // if ($parent_selector == "") { 197 | // $full_selector = $trim_key . " " . $trim_child_key; 198 | // } 199 | 200 | $full_child_key = ""; 201 | 202 | $child_key_arr = explode(",", $trim_child_key); 203 | 204 | foreach ($child_key_arr as $arr_key => $child_key_value) { 205 | $trimed_value = trim($child_key_value); 206 | $full_child_key = $full_child_key . ($arr_key > 0 ? ", " : "") . $trim_parent_selector . ($trim_key[0] == ":" ? $trim_key : " " . $trim_key) . ($trimed_value[0] == ":" ? $trimed_value : " " . $trimed_value); 207 | } 208 | 209 | $full_selector = $full_child_key; 210 | 211 | $this->resolveCss($value->$child_key, $full_selector); 212 | } 213 | } 214 | } 215 | } 216 | } 217 | 218 | 219 | private function generateCss() { 220 | foreach ($this->compiled_array as $key => $value) { 221 | $this->compiled_css = $this->compiled_css . $key . " {"; 222 | foreach ($value as $property => $style) { 223 | $this->compiled_css = $this->compiled_css . $style; 224 | } 225 | $this->compiled_css = $this->compiled_css . "}"; 226 | } 227 | 228 | foreach ($this->media_query_arr as $key => $value) { 229 | foreach ($value as $media_key => $media_queries) { 230 | $this->compiled_media_queries = $this->compiled_media_queries . $media_key . " {"; 231 | foreach ($media_queries as $styles_key => $styles_value) { 232 | $this->compiled_media_queries = $this->compiled_media_queries . $styles_key . " {"; 233 | foreach ($styles_value as $key => $css_style) { 234 | $this->compiled_media_queries = $this->compiled_media_queries . $css_style; 235 | } 236 | $this->compiled_media_queries = $this->compiled_media_queries . "}"; 237 | } 238 | $this->compiled_media_queries = $this->compiled_media_queries . "}"; 239 | } 240 | } 241 | 242 | $this->compiled_css = $this->compiled_css . " ". $this->compiled_media_queries; 243 | } 244 | 245 | 246 | } 247 | ?> -------------------------------------------------------------------------------- /app/Framework/Database/Database.php: -------------------------------------------------------------------------------- 1 | true)); 21 | self::$pdoInstance->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 22 | } 23 | 24 | return self::$pdoInstance; 25 | } 26 | 27 | public static function query($query, $params = array(), $returnArray = false) { 28 | // echo $query . "\n\n"; 29 | $statement = self::connect()->prepare($query); 30 | $statement->execute($params); 31 | 32 | $queryType = explode(' ', $query)[0]; 33 | 34 | if ($queryType == 'SELECT' || $queryType == 'DESCRIBE') { 35 | 36 | $data = $statement->fetchAll($returnArray == false ? \PDO::FETCH_OBJ : \PDO::FETCH_ASSOC); 37 | return $data; 38 | 39 | } 40 | } 41 | 42 | public static function checkTable($table, $params = array()) { 43 | self::$host = env("MYSQL_HOST"); 44 | self::$dbName = env("MYSQL_DB"); 45 | self::$username = env("MYSQL_USERNAME"); 46 | self::$password = env("MYSQL_PASSWORD"); 47 | 48 | $query = "SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE FROM information_schema.TABLES WHERE TABLE_SCHEMA LIKE '".self::$dbName."' AND TABLE_TYPE LIKE 'BASE TABLE' AND TABLE_NAME = '".$table."'"; 49 | $statement = self::connect()->prepare($query); 50 | $statement->execute($params); 51 | $data = $statement->fetchAll(\PDO::FETCH_OBJ); 52 | return $data; 53 | } 54 | } 55 | ?> -------------------------------------------------------------------------------- /app/Framework/Helpers/EnvGenerator.php: -------------------------------------------------------------------------------- 1 | $value) { 25 | putenv("$key=$value"); 26 | } 27 | } 28 | } 29 | 30 | public static function generate() { 31 | $envPath = __DIR__ . "/../../../.env"; 32 | $handle = fopen($envPath, "r"); 33 | 34 | $envArray = []; 35 | $envArrayString = "[\n"; 36 | if ($handle) { 37 | while (($line = fgets($handle)) !== false) { 38 | $contents = trim($line); 39 | 40 | if (strlen($contents) >= 3) { 41 | if ($contents[0] != "#") { 42 | $data = explode("=", $contents); 43 | $trimedKey = trim($data[0]); 44 | $raw_value = trim($data[1]); 45 | $value = $raw_value; 46 | if (!empty($value)) { 47 | if ($raw_value[0] == '"') { 48 | $value = substr($value, 1); 49 | } 50 | if ($value[strlen($value) -1] == '"') { 51 | $value = substr($value, 0, -1); 52 | } 53 | } 54 | $envArray = array_merge($envArray, array( 55 | $trimedKey=>$value 56 | )); 57 | } 58 | } 59 | } 60 | 61 | fclose($handle); 62 | 63 | $iteration = 0; 64 | foreach ($envArray as $key => $value) { 65 | 66 | if ($iteration != count($envArray)-1) { 67 | $envArrayString = $envArrayString . " \"".$key."\" => \"".$value."\",\n"; 68 | } else { 69 | $envArrayString = $envArrayString . " \"".$key."\" => \"".$value."\"\n];\n"; 70 | } 71 | 72 | $iteration++; 73 | } 74 | 75 | $envFileContentPath = __DIR__ . "/../../../core/Environment.php"; 76 | 77 | $myfile = fopen($envFileContentPath, "w") or die("Unable to open file!"); 78 | fwrite($myfile, " -------------------------------------------------------------------------------- /app/Framework/Migrations/Migrator.php: -------------------------------------------------------------------------------- 1 | isDot()) { 13 | $raw_file_name = $fileinfo->getFilename(); 14 | $file_name = str_replace(".php", "", $raw_file_name); 15 | 16 | array_push($migrationList, [ 17 | $file_name, 18 | ["App\Models\\"."$file_name", 'table'] 19 | ]); 20 | } 21 | } 22 | 23 | foreach($migrationList as $migration) { 24 | if (is_callable($migration[1])) { 25 | $migration[1](); 26 | // echo "\033[32m ".$migration[0]." migrated successfully \033[0m\n"; 27 | } 28 | } 29 | 30 | 31 | } 32 | } 33 | 34 | new Migrator(); 35 | 36 | ?> -------------------------------------------------------------------------------- /app/Framework/Model/BaseModel.php: -------------------------------------------------------------------------------- 1 | $query) { 25 | if (count($query) >= 3) { 26 | if ($key == 0) { 27 | $search = " WHERE "; 28 | } else { 29 | $search = " AND "; 30 | } 31 | $search = $search . $query[0] . $query[1].":".$query[0]; 32 | $param = array( 33 | ":".$query[0] => $query[2] 34 | ); 35 | $query_params = array_merge($query_params, $param); 36 | } 37 | } 38 | 39 | $query_limit = ""; 40 | 41 | if ($limit > 0) { 42 | $query_limit = " LIMIT " . $limit; 43 | } else { 44 | $query_limit = ""; 45 | } 46 | 47 | $sql_query = "SELECT * FROM ".static::$tableName." ".$search .$query_limit; 48 | $data = DB::query($sql_query, $query_params); 49 | 50 | return $data; 51 | } 52 | 53 | public static function findById(int | string $id, string $tableId = "id") { 54 | $sql_query = "SELECT * FROM ".static::$tableName." WHERE ".$tableId." = :row_id"; 55 | $data = DB::query($sql_query, [":row_id"=> (int) $id]); 56 | 57 | if (count($data) > 0) { 58 | return $data[0]; 59 | } else { 60 | return null; 61 | } 62 | } 63 | 64 | public function all() { 65 | $sql_query = "SELECT * FROM ".static::$tableName; 66 | $data = DB::query($sql_query, []); 67 | 68 | return $data; 69 | } 70 | 71 | public static function findOne(array $queries = []) { 72 | $query_params = []; 73 | $search = ""; 74 | 75 | foreach ($queries as $key => $query) { 76 | if (count($query) >= 3) { 77 | if ($key == 0) { 78 | $search = " WHERE "; 79 | } else { 80 | $search = " AND "; 81 | } 82 | $search = $search . $query[0] . $query[1].":".$query[0]; 83 | $param = array( 84 | ":".$query[0] => $query[2] 85 | ); 86 | $query_params = array_merge($query_params, $param); 87 | } 88 | } 89 | 90 | $sql_query = "SELECT * FROM ".static::$tableName." ".$search; 91 | $data = DB::query($sql_query, $query_params); 92 | 93 | if (!$data) { 94 | return false; 95 | } 96 | return $data[0]; 97 | } 98 | 99 | 100 | public static function fetch(array $queries = []): self { 101 | $query_params = []; 102 | $search = ""; 103 | 104 | foreach ($queries as $key => $query) { 105 | if (count($query) >= 3) { 106 | if ($key == 0) { 107 | $search = " WHERE "; 108 | } else { 109 | $search = " AND "; 110 | } 111 | $search = $search . $query[0] . $query[1].":".$query[0]; 112 | $param = array( 113 | ":".$query[0] => $query[2] 114 | ); 115 | $query_params = array_merge($query_params, $param); 116 | } 117 | } 118 | 119 | $sql_query = "SELECT * FROM ".static::$tableName." ".$search; 120 | 121 | return new BaseModel($sql_query, $query_params); 122 | } 123 | 124 | 125 | public function where(string $key, string $inputValue, $extra = "none"): self { 126 | $model = new $this; 127 | $model->param_count = $model->param_count + 1; 128 | $query_params = []; 129 | $search = ""; 130 | 131 | $value = ""; 132 | $operand = "="; 133 | 134 | if ($extra == "none") { 135 | $value = $inputValue; 136 | } else { 137 | $value = $extra; 138 | $operand = $inputValue; 139 | } 140 | 141 | $search = " WHERE "; 142 | 143 | $model->where_sub_query = $search . $key . $operand.":where_".$key; 144 | $param = array( 145 | ":where_".$key => $value 146 | ); 147 | 148 | $model->query_params = array_merge($query_params, $param); 149 | 150 | return $model; 151 | } 152 | 153 | 154 | public function and(string $key, string $inputValue, $extra = "none") { 155 | $this->param_count++; 156 | $query_params = []; 157 | $search = ""; 158 | 159 | $value = ""; 160 | $operand = "="; 161 | 162 | if ($extra == "none") { 163 | $value = $inputValue; 164 | } else { 165 | $value = $extra; 166 | $operand = $inputValue; 167 | } 168 | 169 | $this->where_sub_query = $this->where_sub_query . " AND " .$key . $operand.":where_and_" . $this->param_count . "_" . $key; 170 | $param = array( 171 | ":where_and_" . $this->param_count . "_" . $key => $value 172 | ); 173 | 174 | $this->query_params = array_merge($this->query_params, $param); 175 | 176 | return $this; 177 | } 178 | 179 | public function andNot(string $key, string $inputValue, $extra = "none") { 180 | $this->param_count++; 181 | $query_params = []; 182 | $search = ""; 183 | 184 | $value = ""; 185 | $operand = "<>"; 186 | 187 | if ($extra == "none") { 188 | $value = $inputValue; 189 | } else { 190 | $value = $extra; 191 | $operand = $inputValue; 192 | } 193 | 194 | $this->where_sub_query = $this->where_sub_query . " AND " .$key . $operand.":where_andNot_" . $this->param_count . "_" . $key; 195 | $param = array( 196 | ":where_andNot_" . $this->param_count . "_" . $key => $value 197 | ); 198 | 199 | $this->query_params = array_merge($this->query_params, $param); 200 | 201 | return $this; 202 | } 203 | 204 | public function increment(string $key, string $inputValue, $extra = "none") { 205 | $query_params = []; 206 | $search = ""; 207 | 208 | $value = ""; 209 | $operand = "="; 210 | 211 | if ($extra == "none") { 212 | $value = $inputValue; 213 | } else { 214 | $value = $extra; 215 | $operand = $inputValue; 216 | } 217 | 218 | if (strlen($this->update_search) > 0) { 219 | $this->update_search = $this->update_search . ", " . $key . $operand."$key + :where_".$key; 220 | } else { 221 | $this->update_search = $this->update_search . $key . $operand."$key + :where_".$key; 222 | } 223 | 224 | $param = array( 225 | ":where_".$key => $value 226 | ); 227 | 228 | $this->query_params = array_merge($this->query_params, $param); 229 | 230 | return $this; 231 | } 232 | 233 | public function decrement(string $key, string $inputValue, $extra = "none") { 234 | $query_params = []; 235 | $search = ""; 236 | 237 | $value = ""; 238 | $operand = "="; 239 | 240 | if ($extra == "none") { 241 | $value = $inputValue; 242 | } else { 243 | $value = $extra; 244 | $operand = $inputValue; 245 | } 246 | 247 | if (strlen($this->update_search) > 0) { 248 | $this->update_search = $this->update_search . ", " . $key . $operand."$key - :where_".$key; 249 | } else { 250 | $this->update_search = $this->update_search . $key . $operand."$key - :where_".$key; 251 | } 252 | 253 | $param = array( 254 | ":where_".$key => $value 255 | ); 256 | 257 | $this->query_params = array_merge($this->query_params, $param); 258 | 259 | return $this; 260 | } 261 | 262 | public function or(string $key, string $inputValue, $extra = "none") { 263 | $this->param_count++; 264 | $query_params = []; 265 | $search = ""; 266 | 267 | $value = ""; 268 | $operand = "="; 269 | 270 | if ($extra == "none") { 271 | $value = $inputValue; 272 | } else { 273 | $value = $extra; 274 | $operand = $inputValue; 275 | } 276 | 277 | $this->where_sub_query = $this->where_sub_query . " OR " .$key . $operand.":where_or_" . $this->param_count . "_" . $key; 278 | $param = array( 279 | ":where_or_" . $this->param_count . "_" . $key => $value 280 | ); 281 | 282 | $this->query_params = array_merge($this->query_params, $param); 283 | 284 | return $this; 285 | } 286 | 287 | protected static function getTableName() { 288 | return static::$tableName; 289 | } 290 | 291 | public function update(array $queries = []) { 292 | $query_params = []; 293 | $search = ""; 294 | $iteration = 0; 295 | 296 | foreach ($queries as $key => $query) { 297 | if ($iteration == 0) { 298 | $search = $key . "=".":update_".$key; 299 | } else { 300 | $search = $search . ", " . $key . "=".":update_".$key; 301 | } 302 | $param = array( 303 | ":update_".$key => $query 304 | ); 305 | $this->query_params = array_merge($this->query_params, $param); 306 | 307 | $iteration++; 308 | } 309 | 310 | $search = $search . ", date_updated=now()"; 311 | 312 | $this->update_search = $this->update_search . $search; 313 | 314 | 315 | $this->query_params = array_merge($this->query_params, $query_params); 316 | 317 | $sql_query = "UPDATE ". static::$tableName . " SET " . $this->update_search . $this->where_sub_query . $this->query_order_by . $this->query_limit . $this->query_offset; 318 | 319 | 320 | // print_r($this->query_params); 321 | // echo $sql_query; 322 | // error_log($sql_query); 323 | 324 | // exit; 325 | 326 | 327 | $data = DB::query( $sql_query, $this->query_params); 328 | return $data; 329 | } 330 | 331 | public function get(string ...$columns) { 332 | $slected_columns = "*"; 333 | 334 | if (count($columns) > 0) { 335 | foreach ($columns as $key => $column) { 336 | if ($key == 0) { 337 | $slected_columns = $column; 338 | } else { 339 | $slected_columns = $slected_columns . "," . $column; 340 | } 341 | } 342 | } 343 | 344 | $sql_query = "SELECT ".$slected_columns." FROM ". static::$tableName . $this->where_sub_query . $this->query_order_by . $this->query_limit . $this->query_offset; 345 | 346 | $data = DB::query($sql_query, $this->query_params); 347 | 348 | if (count($data) > 0) { 349 | return $data; 350 | } else { 351 | return []; 352 | } 353 | } 354 | 355 | public function delete() : void { 356 | $sql_query = "DELETE FROM ". static::$tableName . $this->where_sub_query . $this->query_order_by . $this->query_limit . $this->query_offset; 357 | DB::query($sql_query, $this->query_params); 358 | } 359 | 360 | public function remove() : void{ 361 | $sql_query = "DELETE FROM ". static::$tableName . $this->where_sub_query . $this->query_order_by . $this->query_limit . $this->query_offset; 362 | DB::query($sql_query, $this->query_params); 363 | } 364 | 365 | 366 | public function count($row_id = "id") { 367 | $sql_query = "SELECT COUNT($row_id) FROM ". static::$tableName . $this->where_sub_query . $this->query_order_by . $this->query_limit . $this->query_offset; 368 | 369 | $data = DB::query($sql_query, $this->query_params); 370 | 371 | return $data[0]->{'COUNT('.$row_id.')'}; 372 | } 373 | 374 | public function first(string ...$columns) { 375 | $slected_columns = "*"; 376 | 377 | if (count($columns) > 0) { 378 | foreach ($columns as $key => $column) { 379 | if ($key == 0) { 380 | $slected_columns = $column; 381 | } else { 382 | $slected_columns = $slected_columns . "," . $column; 383 | } 384 | } 385 | } 386 | 387 | $sql_query = "SELECT ".$slected_columns." FROM ". static::$tableName . $this->where_sub_query . $this->query_order_by . $this->query_limit . $this->query_offset; 388 | 389 | $data = DB::query($sql_query, $this->query_params); 390 | 391 | if (count($data) > 0) { 392 | return $data[0]; 393 | } else { 394 | return null; 395 | } 396 | } 397 | 398 | public function limit(string $limit) { 399 | if ($limit > 0) { 400 | $this->query_limit = " LIMIT " . $limit; 401 | } else { 402 | $this->query_limit = ""; 403 | } 404 | return $this; 405 | } 406 | 407 | public function skip(string $offset) { 408 | if ($offset > 0) { 409 | $this->query_offset = " OFFSET " . $offset; 410 | } else { 411 | $this->query_offset = ""; 412 | } 413 | return $this; 414 | } 415 | 416 | public function order(string $column = "id", string $order = "DESC") { 417 | 418 | $this->query_order_by = " ORDER BY " . $column . " " .$order . " "; 419 | 420 | return $this; 421 | } 422 | 423 | public static function create(array $data) { 424 | $keys = ""; 425 | $columns = ""; 426 | $params= []; 427 | 428 | $iteration = 0; 429 | 430 | foreach ($data as $key => $value) { 431 | if ($iteration == 0) { 432 | $keys = $keys .":".$key; 433 | $columns = $columns ."".$key; 434 | } else { 435 | $keys = $keys . ", :". $key; 436 | $columns = $columns .", ".$key; 437 | } 438 | 439 | $params = array_merge($params, [":".$key=>$value]); 440 | 441 | $iteration++; 442 | } 443 | 444 | $columns = $columns . ", date_created, date_updated"; 445 | $keys = $keys . ", now(), now()"; 446 | 447 | $sql_query = "INSERT INTO ". static::$tableName ." (".$columns.") VALUES (".$keys.")"; 448 | 449 | // print_r($params); 450 | // echo $sql_query; 451 | // error_log($sql_query); 452 | // exit; 453 | 454 | $data = DB::query( $sql_query, $params); 455 | } 456 | 457 | public static function insertMany(array $input) { 458 | $totalKeys = ""; 459 | $columns = ""; 460 | $generatedColumns = false; 461 | $params= []; 462 | $inputIteration = 1; 463 | 464 | foreach ($input as $data) { 465 | $iteration = 0; 466 | $keys = ""; 467 | 468 | foreach ($data as $key => $value) { 469 | if ($iteration == 0) { 470 | if (!$generatedColumns) { 471 | $keys = $keys ."(:". $inputIteration ."_".$key; 472 | $columns = $columns ."".$key; 473 | } else { 474 | $keys = $keys.", (:". $inputIteration ."_".$key; 475 | } 476 | } else { 477 | $keys = $keys . ", :". $inputIteration ."_".$key; 478 | 479 | if (!$generatedColumns) { 480 | $columns = $columns .", ".$key; 481 | } 482 | } 483 | 484 | 485 | $params = array_merge($params, [":". $inputIteration ."_".$key=>$value]); 486 | 487 | $iteration++; 488 | } 489 | 490 | $keys = $keys . ", now(), now())"; 491 | 492 | $inputIteration++; 493 | 494 | $totalKeys = $totalKeys . " ". $keys; 495 | $generatedColumns = true; 496 | } 497 | 498 | 499 | $columns = $columns . ", date_created, date_updated"; 500 | 501 | $sql_query = "INSERT INTO ". static::$tableName ." (".$columns.") VALUES ".$totalKeys.""; 502 | 503 | // print_r($params); 504 | // echo $sql_query; 505 | // error_log($sql_query); 506 | // exit; 507 | 508 | $data = DB::query( $sql_query, $params); 509 | } 510 | 511 | 512 | public static function query(string $query, array $params = []) { 513 | $data = DB::query($query, $params); 514 | return $data; 515 | } 516 | 517 | public static function analizeTable($table) { 518 | 519 | $data = DB::query("DESCRIBE " . $table); 520 | // if (count($data) > 0) { 521 | // $this->isTableExist = true; 522 | // } 523 | 524 | return $data; 525 | } 526 | 527 | public function resolveTableName() { 528 | if (static::$tableName != "") { 529 | return static::$tableName; 530 | } else { 531 | $fullClassArr = explode("\\", get_class($this)); 532 | $class_name = end($fullClassArr); 533 | $last_class_letter = $class_name[strlen($class_name)-1]; 534 | $plural_letter = "s"; 535 | if ($last_class_letter == "s" || $last_class_letter == "x" || $last_class_letter == "z") { 536 | $plural_letter = "es"; 537 | } 538 | return BaseUtility::toCamelCase($class_name) . $plural_letter; 539 | } 540 | } 541 | } 542 | 543 | ?> -------------------------------------------------------------------------------- /app/Framework/Schema/Schema.php: -------------------------------------------------------------------------------- 1 | useTimestamps = $timestamps; 30 | $this->tableName = $tableName; 31 | $this->checkTable(); 32 | } 33 | 34 | public function disableTimestamps() { 35 | $this->useTimestamps = false; 36 | } 37 | 38 | public function string(string $column, int $length = 64) { 39 | $this->checkTableExists($column); 40 | $this->current_column_index = $this->current_column_index + 1; 41 | array_push($this->newColumnList, [ 42 | "Field" => $column, 43 | "Type" => "varchar($length)", 44 | "Null" => "NO", 45 | "Key" => "", 46 | "Default" => null, 47 | "Extra" => "", 48 | ]); 49 | $this->current_column = $column; 50 | $append_comma = ""; 51 | if (strlen($this->schema_sql) > 1) { 52 | $append_comma = ","; 53 | } 54 | $this->schema_sql = $this->schema_sql . $append_comma ." `". $column ."` varchar(".$length.") NOT NULL"; 55 | $this->current_column = $column; 56 | return $this; 57 | } 58 | 59 | 60 | public function text(string $column) { 61 | $this->checkTableExists($column); 62 | $this->current_column_index = $this->current_column_index + 1; 63 | array_push($this->newColumnList, [ 64 | "Field" => $column, 65 | "Type" => "text", 66 | "Null" => "NO", 67 | "Key" => "", 68 | "Default" => null, 69 | "Extra" => "", 70 | ]); 71 | $this->current_column = $column; 72 | $append_comma = ""; 73 | if (strlen($this->schema_sql) > 1) { 74 | $append_comma = ","; 75 | } 76 | $this->schema_sql = $this->schema_sql . $append_comma ." `". $column ."` text NOT NULL"; 77 | $this->current_column = $column; 78 | return $this; 79 | } 80 | 81 | 82 | public function integer(string $column, int $length = 11) { 83 | $this->checkTableExists($column); 84 | $this->current_column_index = $this->current_column_index + 1; 85 | array_push($this->newColumnList, [ 86 | "Field" => $column, 87 | "Type" => "int($length)", 88 | "Null" => "NO", 89 | "Key" => "", 90 | "Default" => null, 91 | "Extra" => "", 92 | ]); 93 | $this->current_column = $column; 94 | $append_comma = ""; 95 | if (strlen($this->schema_sql) > 1) { 96 | $append_comma = ","; 97 | } 98 | 99 | $this->schema_sql = $this->schema_sql . $append_comma ." `". $column ."` int(".$length.") NOT NULL"; 100 | return $this; 101 | } 102 | 103 | 104 | public function double(string $column, int $length = 11) { 105 | $this->checkTableExists($column); 106 | $this->current_column_index = $this->current_column_index + 1; 107 | array_push($this->newColumnList, [ 108 | "Field" => $column, 109 | "Type" => "double", 110 | "Null" => "NO", 111 | "Key" => "", 112 | "Default" => null, 113 | "Extra" => "", 114 | ]); 115 | $this->current_column = $column; 116 | $append_comma = ""; 117 | if (strlen($this->schema_sql) > 1) { 118 | $append_comma = ","; 119 | } 120 | 121 | $this->schema_sql = $this->schema_sql . $append_comma ." `". $column ."` double NOT NULL"; 122 | return $this; 123 | } 124 | 125 | 126 | 127 | public function datetime(string $column, int $length = 11) { 128 | $this->checkTableExists($column); 129 | $this->current_column_index = $this->current_column_index + 1; 130 | array_push($this->newColumnList, [ 131 | "Field" => $column, 132 | "Type" => "datetime", 133 | "Null" => "NO", 134 | "Key" => "", 135 | "Default" => "current_timestamp()", 136 | "Extra" => "", 137 | ]); 138 | $this->current_column = $column; 139 | $append_comma = ""; 140 | if (strlen($this->schema_sql) > 1) { 141 | $append_comma = ","; 142 | } 143 | 144 | $this->schema_sql = $this->schema_sql . $append_comma ." `". $column ."` datetime NOT NULL"; 145 | return $this; 146 | } 147 | 148 | 149 | public function id(string $column = 'id', int $length = 11) { 150 | $this->checkTableExists($column); 151 | $this->current_column_index = $this->current_column_index + 1; 152 | array_push($this->newColumnList, [ 153 | "Field" => $column, 154 | "Type" => "bigint($length)", 155 | "Null" => "NO", 156 | "Key" => "PRI", 157 | "Default" => null, 158 | "Extra" => "auto_increment", 159 | ]); 160 | $this->current_column = $column; 161 | $append_comma = ""; 162 | if (strlen($this->schema_sql) > 1) { 163 | $append_comma = ","; 164 | } 165 | 166 | $this->schema_sql = $this->schema_sql . $append_comma ." `". $column ."` bigint(".$length.") NOT NULL AUTO_INCREMENT"; 167 | $this->primary_key = ", PRIMARY KEY (".$column.")"; 168 | } 169 | 170 | public function autoIncrement() { 171 | $this->newColumnList[$this->current_column_index]["Extra"] = "auto_increment"; 172 | $this->schema_sql = $this->schema_sql . " AUTO_INCREMENT"; 173 | return $this; 174 | } 175 | 176 | public function primary() { 177 | if ($this->num_primary_keys > 0) { 178 | echo "\e[0;31;40m Aborting migration: Duplicate primary keys detected in ".$this->tableName." table there can only be one auto column and it must be defined as a key \e[0m\n"; 179 | exit(1); 180 | } 181 | $this->newColumnList[$this->current_column_index]["Key"] = "PRI"; 182 | $this->primary_key = ", PRIMARY KEY (".$this->current_column.")"; 183 | $this->num_primary_keys = $this->num_primary_keys+1; 184 | } 185 | 186 | private function checkTableExists ($newColumn) { 187 | if (strlen(trim($newColumn)) < 1) { 188 | echo "\e[0;31;40m Aborting migration: Empty column detected in ".$this->tableName." table. Columns can't be empty \e[0m\n"; 189 | exit(1); 190 | } 191 | foreach ( $this->newColumnList as $column) { 192 | if (in_array($newColumn, $column)) { 193 | echo "\e[0;31;40m Aborting migration: Duplicate colunm `".$newColumn."` detected in ".$this->tableName." table. Columns must be unique \e[0m\n"; 194 | exit(1); 195 | } 196 | } 197 | } 198 | 199 | private function enableTimestamps() { 200 | $this->checkTableExists("date_created"); 201 | $this->checkTableExists("date_updated"); 202 | $this->current_column_index = $this->current_column_index + 1; 203 | array_push($this->newColumnList, [ 204 | "Field" => "date_created", 205 | "Type" => "datetime", 206 | "Null" => "NO", 207 | "Key" => "", 208 | "Default" => "current_timestamp()", 209 | "Extra" => "", 210 | ]); 211 | array_push($this->newColumnList, [ 212 | "Field" => "date_updated", 213 | "Type" => "datetime", 214 | "Null" => "NO", 215 | "Key" => "", 216 | "Default" => "current_timestamp()", 217 | "Extra" => "", 218 | ]); 219 | } 220 | 221 | public function create() { 222 | if (!$this->isTableExist) { 223 | 224 | if ($this->useTimestamps) { 225 | $this->schema_sql = $this->schema_sql . ", `date_created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `date_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP"; 226 | } 227 | 228 | // echo "\e[0;35;40m".$this->tableName." doesn't exist \e[0m\n"; 229 | $create_table_query = "CREATE TABLE IF NOT EXISTS ". $this->tableName . " (". $this->schema_sql . $this->primary_key.") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"; 230 | // error_log("\n\n".$create_table_query."\n\n"); 231 | DB::query($create_table_query); 232 | echo "\033[32m ".$this->tableName." table created successfully \033[0m\n"; 233 | } else { 234 | if ($this->useTimestamps) { 235 | $this->enableTimestamps(); 236 | } 237 | $this->compareColumns(); 238 | foreach ($this->columnDropList as $column_drop_query) { 239 | // Log::warning($column_drop_query["sql"]); 240 | DB::query($column_drop_query["sql"]); 241 | } 242 | foreach ($this->modificationList as $modification) { 243 | if ($modification["operation_type"] == "ALTER_CHANGE_COLUMN") { 244 | if (in_array($modification["from"], $this->replacedColumnList)) { 245 | $checkedColumn = $this->checkColumnExists($modification["to"]); 246 | if ($checkedColumn->exists) { 247 | if ($modification["from"] != $modification["to"]) { 248 | array_push($this->replacedColumnList, $modification["to"]); 249 | $column = $this->getColumnType($checkedColumn->data->Type); 250 | $this->buildAndRunQuery(queryType: "ALTER_CHANGE_COLUMN", from: $modification["to"], to: $modification["to"]."_ALTERED", type: $column->type, limit: $column->limit, fromType: $modification["from_type"], toType: $modification["to_type"], extras: $checkedColumn->data->Extra); 251 | } 252 | } 253 | 254 | $new_sql = str_replace("CHANGE `".$modification["from"]."`", "CHANGE `".$modification["from"]."_ALTERED`", $modification["sql"]); 255 | // echo "\033[32m ".$new_sql." \033[0m\n"; 256 | DB::query($new_sql); 257 | } else { 258 | $checkedColumn = $this->checkColumnExists($modification["to"]); 259 | if ($checkedColumn->exists) { 260 | if ($modification["from"] != $modification["to"]) { 261 | array_push($this->replacedColumnList, $modification["to"]); 262 | $column = $this->getColumnType($checkedColumn->data->Type); 263 | 264 | $this->buildAndRunQuery(queryType: "ALTER_CHANGE_COLUMN", from: $modification["to"], to: $modification["to"]."_ALTERED", type: $column->type, limit: $column->limit, fromType: $modification["from_type"], toType: $modification["to_type"], extras: $checkedColumn->data->Extra); 265 | $this->clearColumn(from: $modification["from"], to: $modification["to"], fromType: $modification["from_type"], toType: $modification["to_type"]); 266 | // Log::neutral($modification["sql"]); 267 | DB::query($modification["sql"]); 268 | } else { 269 | $this->clearColumn(from: $modification["from"], to: $modification["to"], fromType: $modification["from_type"], toType: $modification["to_type"]); 270 | // Log::neutral($modification["sql"]); 271 | DB::query($modification["sql"]); 272 | } 273 | } else { 274 | $this->clearColumn(from: $modification["from"], to: $modification["to"], fromType: $modification["from_type"], toType: $modification["to_type"]); 275 | // Log::neutral($modification["sql"]); 276 | DB::query($modification["sql"]); 277 | } 278 | } 279 | 280 | // Log::neutral($modification["sql"]); 281 | } else { 282 | // Log::neutral($modification["sql"]); 283 | DB::query($modification["sql"]); 284 | } 285 | 286 | } 287 | 288 | if (!$this->skippedMigration) { 289 | echo "\033[32m ".$this->tableName." table migrated successfully \033[0m\n"; 290 | } 291 | } 292 | } 293 | 294 | public function checkTable() { 295 | $data = DB::checkTable($this->tableName); 296 | if (count($data) > 0) { 297 | $this->isTableExist = true; 298 | $this->describeTable(); 299 | } 300 | return $data; 301 | } 302 | 303 | private function checkColumnExists ($column) { 304 | $query = "DESCRIBE ". $this->tableName; 305 | $data = DB::query($query, [], true); 306 | 307 | $colunm_data = ""; 308 | 309 | $column_exists = false; 310 | 311 | foreach ($data as $savedColumnKey => $savedColumn) { 312 | if (in_array($column, $savedColumn)) { 313 | $colunm_data = (object) $data[$savedColumnKey]; 314 | $column_exists = true; 315 | break; 316 | } 317 | } 318 | // echo "\n\n\ncolumn ".$column." exist: " . $column_exists."\n\n\n"; 319 | 320 | $body = (object) ["exists" => $column_exists, "data"=>$colunm_data]; 321 | 322 | return $body; 323 | } 324 | 325 | public function describeTable() { 326 | $query = "DESCRIBE ". $this->tableName; 327 | $data = DB::query($query, [], true); 328 | $table_columns = count($data); 329 | if($this->useTimestamps) { 330 | if ($data[$table_columns-2]["Field"] == "date_created" && $data[$table_columns-1]["Field"] == "date_updated") { 331 | unset($data[$table_columns-1]); 332 | unset($data[$table_columns-2]); 333 | } 334 | } 335 | // Log::success("====== saved columns ======"); 336 | // Log::neutral(print_r($data)); 337 | $this->savedColumnList = array_values($data); 338 | 339 | } 340 | public function removeTimestampsFromNewColumns () { 341 | $query = "DESCRIBE ". $this->tableName; 342 | $data = $this->newColumnList; 343 | $table_columns = count($data); 344 | if($this->useTimestamps) { 345 | if ($data[$table_columns-2]["Field"] == "date_created" && $data[$table_columns-1]["Field"] == "date_updated") { 346 | unset($data[$table_columns-1]); 347 | unset($data[$table_columns-2]); 348 | } 349 | } 350 | // Log::success("====== new columns ======"); 351 | // Log::neutral(print_r($data)); 352 | $this->newColumnList = array_values($data); 353 | } 354 | 355 | public function compareColumns() { 356 | $this->removeTimestampsFromNewColumns(); 357 | if (count($this->newColumnList) == count($this->savedColumnList)) { 358 | $this->compareAndAlterColumns(); 359 | } else { 360 | $this->getNewColumns(); 361 | } 362 | } 363 | 364 | public function compareAndAlterColumns() { 365 | // echo "\n\n\n\n\e[0;35;35m new: ". print_r($this->newColumnList) ." in " .$this->tableName." \e[0m\n\n\n\n\n"; 366 | // echo "\n\n\n\n\e[0;35;35m saved: ". print_r($this->savedColumnList) ." in " .$this->tableName." \e[0m\n\n\n\n\n"; 367 | 368 | $is_columns_match = false; 369 | if (json_encode($this->newColumnList) == json_encode($this->savedColumnList)) { 370 | $is_columns_match = true; 371 | } 372 | 373 | if ($is_columns_match) { 374 | $this->skippedMigration = true; 375 | echo "\e[0;35;35m Skipping ".$this->tableName." table because no changes was found \e[0m\n"; 376 | } else { 377 | foreach ($this->newColumnList as $newColumnkey => $column) { 378 | $new_column = $this->getColumnType($column["Type"]); 379 | 380 | $add_primary_key = ""; 381 | $auto_increment_sub_query = ""; 382 | 383 | $saved_column_to_alter = $this->savedColumnList[$newColumnkey]; 384 | 385 | 386 | if ($column["Extra"] == "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] != "PRI" && $column["Key"] == "PRI") { 387 | $add_primary_key = ", add PRIMARY KEY (`".$column["Field"]."`)"; 388 | $auto_increment_sub_query = " AUTO_INCREMENT"; 389 | 390 | // echo "lofty yessssss ".$saved_column_to_alter["Field"]; 391 | } 392 | 393 | if ($column["Extra"] == "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] == "PRI" && $column["Key"] == "PRI") { 394 | $auto_increment_sub_query = " AUTO_INCREMENT"; 395 | } 396 | 397 | if ($column["Extra"] == "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] == "PRI") { 398 | $auto_increment_sub_query = " AUTO_INCREMENT"; 399 | } 400 | 401 | if ($column["Extra"] == "auto_increment" && $saved_column_to_alter["Extra"] == "auto_increment") { 402 | $auto_increment_sub_query = " AUTO_INCREMENT"; 403 | } 404 | 405 | $mod_sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$column["Field"]."` ".$new_column?->type."(".$new_column->limit.") NOT NULL ".$auto_increment_sub_query." ".$add_primary_key."; "; 406 | 407 | if ($new_column?->type == "text") { 408 | $mod_sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$column["Field"]."` ".$new_column?->type." NOT NULL ".$auto_increment_sub_query." ".$add_primary_key."; "; 409 | } else if ($new_column?->type == "double") { 410 | $mod_sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$column["Field"]."` ".$new_column?->type." NOT NULL ".$auto_increment_sub_query." ".$add_primary_key."; "; 411 | } else if ($new_column?->type == "datetime") { 412 | $mod_sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$column["Field"]."` DATETIME NOT NULL DEFAULT ". $column["Default"]. "; "; 413 | } 414 | 415 | array_push($this->modificationList, 416 | [ 417 | "sql"=>$mod_sql, 418 | "from"=>$saved_column_to_alter["Field"], 419 | "to"=>$column["Field"], 420 | "from_type"=>$saved_column_to_alter["Type"], 421 | "to_type"=>$column["Type"], 422 | "operation_type"=>"ALTER_CHANGE_COLUMN" 423 | ] 424 | ); 425 | 426 | 427 | if ($column["Extra"] != "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] != "PRI" && $column["Key"] == "PRI") { 428 | array_push($this->modificationList, 429 | [ 430 | "sql"=>"ALTER TABLE `".$this->tableName."` ADD PRIMARY KEY(`".$column["Field"]."`);", 431 | "operation_type"=>"ALTER_ADD_PRIMARY_KEY" 432 | ] 433 | ); 434 | } 435 | 436 | if ($column["Extra"] != "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] == "PRI" && $column["Key"] != "PRI") { 437 | 438 | array_push($this->columnDropList, 439 | [ 440 | "sql"=>"ALTER TABLE `".$this->tableName."` DROP PRIMARY KEY;", 441 | "operation_type"=>"ALTER_DROP_PRIMARY_KEY" 442 | ] 443 | ); 444 | } 445 | 446 | /* 447 | * Check if new column has a primary key 448 | */ 449 | 450 | if ($column["Extra"] != "auto_increment" && $saved_column_to_alter["Extra"] == "auto_increment" && $saved_column_to_alter["Key"] == "PRI" && $column["Key"] != "PRI") { 451 | 452 | 453 | /* 454 | * Remove auto increment from current column 455 | */ 456 | array_push($this->columnDropList, 457 | [ 458 | "sql"=>"ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$saved_column_to_alter["Field"]."` ".$saved_column_to_alter["Type"]." NOT NULL; ", 459 | "from"=>$saved_column_to_alter["Field"], 460 | "to"=>$saved_column_to_alter["Field"], 461 | "operation_type"=>"ALTER_CHANGE_COLUMN" 462 | ] 463 | ); 464 | 465 | /* 466 | * Remove primary key from table 467 | */ 468 | array_push($this->columnDropList, 469 | [ 470 | "sql"=>"ALTER TABLE `".$this->tableName."` DROP PRIMARY KEY;", 471 | "operation_type"=>"ALTER_DROP_PRIMARY_KEY" 472 | ] 473 | ); 474 | } 475 | } 476 | } 477 | 478 | // echo print_r($this->modificationList); 479 | } 480 | 481 | public function getColumnType(string $field) { 482 | $field_arry = explode("(", $field); 483 | 484 | if (str_contains($field, "(")) { 485 | return (object) ['type'=>$field_arry[0], 'limit'=> str_replace(")", "", $field_arry[1])]; 486 | } else { 487 | return (object) ['type'=>$field, 'limit'=> 1]; 488 | } 489 | 490 | } 491 | 492 | public function compareAndAlterExsitingColumns() { 493 | // echo "\n\n\n\n\e[0;35;35m new: ". print_r($this->newColumnList) ." in " .$this->tableName." \e[0m\n\n\n\n\n"; 494 | // echo "\n\n\n\n\e[0;35;35m saved: ". print_r($this->savedColumnList) ." in " .$this->tableName." \e[0m\n\n\n\n\n"; 495 | 496 | $this->filterColumnsToDrop(); 497 | 498 | $saved_table_length = count($this->savedColumnList); 499 | foreach ($this->newColumnList as $newColumnkey => $column) { 500 | $new_column = $this->getColumnType($column["Type"]); 501 | 502 | $auto_increment_sub_query = ""; 503 | $add_primary_key = ""; 504 | 505 | if ($newColumnkey < $saved_table_length) { 506 | $saved_column_to_alter = $this->savedColumnList[$newColumnkey]; 507 | 508 | 509 | if ($column["Extra"] == "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] != "PRI" && $column["Key"] == "PRI") { 510 | $add_primary_key = ", add PRIMARY KEY (`".$column["Field"]."`)"; 511 | $auto_increment_sub_query = " AUTO_INCREMENT"; 512 | } 513 | 514 | if ($column["Extra"] == "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] == "PRI" && $column["Key"] == "PRI") { 515 | $auto_increment_sub_query = " AUTO_INCREMENT"; 516 | } 517 | 518 | if ($column["Extra"] == "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] == "PRI") { 519 | $auto_increment_sub_query = " AUTO_INCREMENT"; 520 | } 521 | 522 | if ($column["Extra"] == "auto_increment" && $saved_column_to_alter["Extra"] == "auto_increment") { 523 | $auto_increment_sub_query = " AUTO_INCREMENT"; 524 | } 525 | 526 | $mod_sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$column["Field"]."` ".$new_column?->type."(".$new_column->limit.") NOT NULL ".$auto_increment_sub_query." ".$add_primary_key."; "; 527 | 528 | if ($new_column?->type == "text") { 529 | $mod_sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$column["Field"]."` ".$new_column?->type." NOT NULL ".$auto_increment_sub_query." ".$add_primary_key."; "; 530 | } else if ($new_column?->type == "double") { 531 | $mod_sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$column["Field"]."` ".$new_column?->type." NOT NULL ".$auto_increment_sub_query." ".$add_primary_key."; "; 532 | } else if ($new_column?->type == "datetime") { 533 | $mod_sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$column["Field"]."` DATETIME NOT NULL DEFAULT ". $column["Default"]. "; "; 534 | } 535 | 536 | array_push($this->modificationList, 537 | [ 538 | "sql"=>$mod_sql, 539 | "from"=>$saved_column_to_alter["Field"], 540 | "to"=>$column["Field"], 541 | "from_type"=>$saved_column_to_alter["Type"], 542 | "to_type"=>$column["Type"], 543 | "operation_type"=>"ALTER_CHANGE_COLUMN" 544 | ] 545 | ); 546 | 547 | 548 | if ($column["Extra"] != "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] != "PRI" && $column["Key"] == "PRI") { 549 | array_push($this->modificationList, 550 | [ 551 | "sql"=>"ALTER TABLE `".$this->tableName."` ADD PRIMARY KEY(`".$column["Field"]."`);", 552 | "operation_type"=>"ADD_PRIMARY_KEY" 553 | ] 554 | ); 555 | } 556 | 557 | if ($column["Extra"] != "auto_increment" && $saved_column_to_alter["Extra"] != "auto_increment" && $saved_column_to_alter["Key"] == "PRI" && $column["Key"] != "PRI") { 558 | 559 | array_push($this->modificationList, 560 | [ 561 | "sql"=>"ALTER TABLE `".$this->tableName."` DROP PRIMARY KEY;", 562 | "operation_type"=>"DROP_PRIMARY_KEY" 563 | ] 564 | ); 565 | } 566 | 567 | /* 568 | * Check if new column has a primary key 569 | */ 570 | 571 | if ($column["Extra"] != "auto_increment" && $saved_column_to_alter["Extra"] == "auto_increment" && $saved_column_to_alter["Key"] == "PRI" && $column["Key"] != "PRI") { 572 | 573 | 574 | /* 575 | * Remove auto increment from current column 576 | */ 577 | array_push($this->columnDropList, 578 | [ 579 | "sql"=>"ALTER TABLE `".$this->tableName."` CHANGE `".$saved_column_to_alter["Field"]."` `".$saved_column_to_alter["Field"]."` ".$saved_column_to_alter["Type"]." NOT NULL; ", 580 | "from"=>$saved_column_to_alter["Field"], 581 | "to"=>$saved_column_to_alter["Field"], 582 | "operation_type"=>"ALTER_CHANGE_COLUMN" 583 | ] 584 | ); 585 | 586 | /* 587 | * Remove primary key from table 588 | */ 589 | array_push($this->columnDropList, 590 | [ 591 | "sql"=>"ALTER TABLE `".$this->tableName."` DROP PRIMARY KEY;", 592 | "operation_type"=>"ALTER_DROP_PRIMARY_KEY" 593 | ] 594 | ); 595 | } 596 | } else { 597 | $previous_column_field = end($this->newColumnList)["Field"]; 598 | $alter_after_sub_query = ""; 599 | if ($newColumnkey != 0) { 600 | $previous_column_field = $this->getPreviousColumn($this->newColumnList, $newColumnkey)["Field"]; 601 | $alter_after_sub_query = "AFTER `".$previous_column_field."`"; 602 | } else { 603 | $alter_after_sub_query = "FIRST"; 604 | } 605 | 606 | if ($column["Extra"] == "auto_increment") { 607 | $auto_increment_sub_query = " AUTO_INCREMENT "; 608 | $add_primary_key = " , add PRIMARY KEY (`".$column["Field"]."`)"; 609 | } 610 | 611 | $mod_sql = "ALTER TABLE `".$this->tableName."` ADD `".$column["Field"]."` ".$new_column?->type."(".$new_column->limit.") NOT NULL ".$auto_increment_sub_query." ".$alter_after_sub_query." ". $add_primary_key."; "; 612 | 613 | if ($new_column?->type == "text") { 614 | $mod_sql = "ALTER TABLE `".$this->tableName."` ADD `".$column["Field"]."` ".$new_column?->type." NOT NULL ".$auto_increment_sub_query." ".$alter_after_sub_query." ". $add_primary_key."; "; 615 | } else if ($new_column?->type == "double") { 616 | $mod_sql = "ALTER TABLE `".$this->tableName."` ADD `".$column["Field"]."` ".$new_column?->type." NOT NULL ".$auto_increment_sub_query." ".$alter_after_sub_query." ". $add_primary_key."; "; 617 | } else if ($new_column?->type == "datetime") { 618 | $mod_sql = "ALTER TABLE `".$this->tableName."` ADD `".$column["Field"]."` ".$new_column?->type." NOT NULL DEFAULT CURRENT_TIMESTAMP ".$alter_after_sub_query.";"; 619 | } 620 | 621 | array_push($this->modificationList, 622 | [ 623 | "sql"=>$mod_sql, 624 | "operation_type"=> "ALTER_ADD_COLUMN" 625 | ] 626 | ); 627 | 628 | 629 | if ($column["Extra"] != "auto_increment" && $column["Key"] == "PRI") { 630 | array_push($this->modificationList, 631 | [ 632 | "sql"=> "ALTER TABLE `".$this->tableName."` ADD PRIMARY KEY(`".$column["Field"]."`);", 633 | "operation_type"=>"ALTER_ADD_PRIMARY_KEY" 634 | ] 635 | ); 636 | } 637 | } 638 | } 639 | 640 | // echo print_r($this->modificationList); 641 | } 642 | 643 | public function filterColumnsToDrop() { 644 | // Log::error("Checking for columns to drop"); 645 | $new_table_length = count($this->newColumnList); 646 | if ($new_table_length < count($this->savedColumnList)) { 647 | Log::warning("Some columns would be dropped"); 648 | $colums_to_be_dropped = $this->savedColumnList; 649 | $filtered_column_list = []; 650 | 651 | foreach ($this->savedColumnList as $savedColumnKey => $savedColumn) { 652 | /* 653 | * 654 | * Check for columns that already exists in the table 655 | * 656 | */ 657 | 658 | if ($savedColumnKey < $new_table_length) { 659 | // Log::success($savedColumn["Field"] ." will not be droped"); 660 | array_push($filtered_column_list, $savedColumn); 661 | unset($colums_to_be_dropped[$savedColumnKey]); 662 | } else { 663 | // Log::error($savedColumn["Field"] ." will be droped"); 664 | } 665 | } 666 | 667 | foreach ($colums_to_be_dropped as $colum_to_be_dropped) { 668 | array_push($this->modificationList, 669 | [ 670 | "sql"=>"ALTER TABLE `".$this->tableName."` DROP `".$colum_to_be_dropped["Field"]."; ", 671 | "operation_type"=>"ALTER_DROP" 672 | ] 673 | ); 674 | } 675 | 676 | $this->savedColumnList = $filtered_column_list; 677 | 678 | } else { 679 | // Log::error("Can not perform dropping operation"); 680 | } 681 | 682 | 683 | // Log::error("Found columns ".count($this->columnDropList)." to drop"); 684 | 685 | // echo print_r($this->columnDropList); 686 | } 687 | 688 | public function getPreviousColumn($arr, $key) { 689 | return $arr[$key-1]; 690 | } 691 | 692 | public function getNewColumns() { 693 | $this->compareAndAlterExsitingColumns(); 694 | } 695 | 696 | public function clearColumn(string $from, string $to, string $fromType = "", string $toType = "") { 697 | $raw_fromType = $this->getColumnType($fromType); 698 | $raw_toType = $this->getColumnType($toType); 699 | // echo "to: $to ($raw_toType->type) from: $from ($raw_fromType->type) \n"; 700 | if ($raw_fromType->type != "" && $raw_toType->type != "" && $raw_fromType->type != $raw_toType->type) { 701 | if (in_array(strtolower($raw_toType->type), ["int", "bigint", "double"]) && in_array(strtolower($raw_fromType->type), ["varchar", "text", "datetime"])) { 702 | $update_sql = "UPDATE `".$this->tableName."` SET `".$from."`=0"; 703 | // echo "$$update_sql\n"; 704 | // Log::warning($update_sql); 705 | DB::query($update_sql); 706 | } else { 707 | if (in_array(strtolower($raw_toType->type), ["datetime"])) { 708 | $update_sql = "UPDATE `".$this->tableName."` SET `".$from."`='0000-00-00 00:00:00'"; 709 | // echo "$$update_sql\n"; 710 | // Log::warning($update_sql); 711 | DB::query($update_sql); 712 | } 713 | } 714 | } 715 | } 716 | 717 | public function buildAndRunQuery(string $queryType, string $from, string $to, string $type, string $limit, string $fromType = "", string $toType = "", string $extras = "", string $key = "") { 718 | 719 | $auto_increment_sub_query = ""; 720 | $add_primary_key = ""; 721 | 722 | if ($extras == "auto_increment") { 723 | $add_primary_key = ", add PRIMARY KEY (`".$to."`)"; 724 | $auto_increment_sub_query = " AUTO_INCREMENT"; 725 | } 726 | if ($key == "PRI") { 727 | $add_primary_key = ", add PRIMARY KEY (`".$to."`)"; 728 | $auto_increment_sub_query = " AUTO_INCREMENT"; 729 | } 730 | 731 | if ($queryType == "ALTER_CHANGE_COLUMN") { 732 | $sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$from."` `".$to."` ".$type."(".$limit.") NOT NULL ".$auto_increment_sub_query." ".$add_primary_key."; "; 733 | 734 | if (in_array($type, ["text", "datetime", "double"])) { 735 | $sql = "ALTER TABLE `".$this->tableName."` CHANGE `".$from."` `".$to."` ".$type." NOT NULL ".$auto_increment_sub_query." ".$add_primary_key."; "; 736 | } 737 | } else { 738 | 739 | } 740 | 741 | // $this->clearColumn(from: $from, to: $to, fromType: $fromType, toType: $toType); 742 | 743 | // echo "$sql \n"; 744 | // Log::neutral($sql); 745 | 746 | DB::query($sql); 747 | } 748 | } -------------------------------------------------------------------------------- /app/Framework/Utilities/BaseUtility.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Framework/Utilities/Encrypt.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Framework/Utilities/HttpRequest.php: -------------------------------------------------------------------------------- 1 | headers = $headers; 14 | return $httpRequest; 15 | } 16 | 17 | private function getHeaders() { 18 | return array_map(function($value, $key) { 19 | return $key.': '.$value; 20 | }, array_values($this->headers), array_keys($this->headers)); 21 | } 22 | 23 | public function withBasicAuth(array $basic_auth) : self { 24 | $this->basic_auth = (object) $basic_auth; 25 | return $this; 26 | } 27 | 28 | private function resolveQueryParams(array $params) { 29 | $iteration = 0; 30 | $rsolvedQuery = ""; 31 | forEach ($params as $key => $value) { 32 | if ($iteration == 0) { 33 | $key_encoded = urlencode($key); 34 | $value_encoded = urlencode($value); 35 | $rsolvedQuery = "?$key_encoded=$value_encoded"; 36 | } else { 37 | $key_encoded = urlencode($key); 38 | $value_encoded = urlencode($value); 39 | $rsolvedQuery = $rsolvedQuery . "&$key_encoded=$value_encoded"; 40 | } 41 | $iteration = $iteration + 1; 42 | } 43 | 44 | return $rsolvedQuery; 45 | } 46 | 47 | 48 | public function resolveRequest($type = "GET", array $data = [], $query = []) { 49 | $query = $this->resolveQueryParams($query); 50 | 51 | 52 | $use_url_post_fields = false; 53 | 54 | $headers = (object) $this->headers; 55 | 56 | if (isset($headers?->{'Content-Type'}) && $headers?->{'Content-Type'} == "application/x-www-form-urlencoded") { 57 | $query = substr($query, 1); 58 | $use_url_post_fields = true; 59 | } 60 | 61 | 62 | $init_request = curl_init( !$use_url_post_fields ? $this->request_url .$query : $this->request_url); 63 | 64 | if ($type != "GET") { 65 | $payload = json_encode($data); 66 | 67 | // Response::send(["choke" => $use_url_post_fields ? $query : $payload]); 68 | curl_setopt( $init_request, CURLOPT_POSTFIELDS, $use_url_post_fields ? $query : $payload); 69 | } 70 | 71 | curl_setopt($init_request, CURLOPT_CUSTOMREQUEST, $type); 72 | 73 | if ($this->headers) { 74 | curl_setopt( $init_request, CURLOPT_HTTPHEADER, $this->getHeaders()); 75 | } 76 | 77 | if ($this->basic_auth) { 78 | curl_setopt( $init_request, CURLOPT_USERPWD, $this->basic_auth?->username . ":" . $this->basic_auth?->password); 79 | } 80 | 81 | curl_setopt( $init_request, CURLOPT_RETURNTRANSFER, true ); 82 | $result = curl_exec($init_request); 83 | 84 | $err = curl_error($init_request); 85 | curl_close($init_request); 86 | $status_code = curl_getinfo($init_request, CURLINFO_HTTP_CODE); 87 | 88 | $response_data = json_decode($result); 89 | 90 | if ($err) { 91 | throw new Exception($err); 92 | } else { 93 | return (object) array('data'=>$response_data, 'status_code' => $status_code); 94 | } 95 | } 96 | 97 | public function post(string $url, array $data = [], $query = []) { 98 | $this->request_url = $url; 99 | return $this->resolveRequest("POST", $data, $query); 100 | } 101 | 102 | public function patch(string $url, array $data = [], $query = []) { 103 | $this->request_url = $url; 104 | return $this->resolveRequest("PATCH", $data, $query); 105 | } 106 | 107 | public function put(string $url, array $data = [], $query = []) { 108 | $this->request_url = $url; 109 | return $this->resolveRequest("PUT", $data, $query); 110 | } 111 | 112 | public function delete(string $url, array $data = [], $query = []) { 113 | $this->request_url = $url; 114 | return $this->resolveRequest("DELETE", $data, $query); 115 | } 116 | 117 | 118 | 119 | public function get(string $url, $query = []) { 120 | $this->request_url = $url; 121 | return $this->resolveRequest("GET", [] , $query); 122 | } 123 | } -------------------------------------------------------------------------------- /app/Framework/Utilities/JwtUtility.php: -------------------------------------------------------------------------------- 1 | 'HS256','typ'=>'JWT'); 24 | $payload = $user_data; 25 | 26 | $jwt = self::generate_jwt($headers, $payload, $salt ?? env("SERVER_SALT")); 27 | 28 | return $jwt; 29 | } 30 | 31 | public static function validate_token($token) { 32 | // split the jwt 33 | $tokenParts = explode('.', $token); 34 | 35 | if (count($tokenParts) == 3) { 36 | $header = base64_decode($tokenParts[0]); 37 | $payload = base64_decode($tokenParts[1]); 38 | $signature_provided = $tokenParts[2]; 39 | 40 | if (isset(json_decode($payload)->exp)) { 41 | // check the expiration time - note this will cause an error if there is no 'exp' claim in the jwt 42 | $expiration = json_decode($payload)->exp; 43 | $is_token_expired = ($expiration - time()) < 0; 44 | 45 | // build a signature based on the header and payload using the secret 46 | $base64_url_header = self::base64url_encode($header); 47 | $base64_url_payload = self::base64url_encode($payload); 48 | $signature = hash_hmac('SHA256', $base64_url_header . "." . $base64_url_payload, env("SERVER_SALT"), true); 49 | $base64_url_signature = self::base64url_encode($signature); 50 | 51 | // verify it matches the signature provided in the jwt 52 | $is_signature_valid = ($base64_url_signature === $signature_provided); 53 | 54 | if ($is_token_expired || !$is_signature_valid) { 55 | return false; 56 | } else { 57 | return true; 58 | } 59 | } else { 60 | return false; 61 | } 62 | } else { 63 | return false; 64 | } 65 | } 66 | 67 | public static function getUserDetails($token) { 68 | // split the jwt 69 | $tokenParts = explode('.', $token); 70 | 71 | if (count($tokenParts) == 3) { 72 | $payload = base64_decode($tokenParts[1]); 73 | $user_details = json_decode($payload); 74 | return $user_details; 75 | } 76 | 77 | return false; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/Framework/Utilities/Log.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Framework/Utilities/Request.php: -------------------------------------------------------------------------------- 1 | params = (object) $params; 15 | $this->query = (object) $query; 16 | $this->headers = (object) $headers; 17 | $this->path = $path; 18 | $this->method = strtolower($method); 19 | $this->body = (object) json_decode(json_encode($body), false); 20 | $this->extras = (object) []; 21 | } 22 | 23 | public function setExtras(array $data) { 24 | $this->extras = (object) $data; 25 | } 26 | } 27 | 28 | ?> -------------------------------------------------------------------------------- /app/Framework/Utilities/Response.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Framework/Utilities/Upload.php: -------------------------------------------------------------------------------- 1 | $userid, ':source'=>$fileNameNew)); 31 | } 32 | 33 | if ($imgType == "fearured_image_update") { 34 | $fileDestination = '../img/'.$fileNameNew; 35 | move_uploaded_file($fileTmpName, $fileDestination); 36 | DB::query('UPDATE posts SET featured_image=:source WHERE id=:postid', array(':postid'=>$userid, ':source'=>$fileNameNew)); 37 | } 38 | 39 | if ($imgType == "profilePicture") { 40 | $fileDestination = 'img/'.$fileNameNew; 41 | move_uploaded_file($fileTmpName, $fileDestination); 42 | DB::query('UPDATE users SET avatar=:source WHERE id=:userid', array(':userid'=>$userid, ':source'=>$fileNameNew)); 43 | } 44 | 45 | if ($imgType == "contestUserPhoto") { 46 | $fileDestination = 'img/'.$fileNameNew; 47 | move_uploaded_file($fileTmpName, $fileDestination); 48 | DB::query('UPDATE applications SET contest_user_photo=:source WHERE user_id=:userid AND contest_id=:contestid AND status = "pending" ORDER BY id DESC', array(':userid'=>$userid, ':source'=>$fileNameNew, ':contestid'=>intval($contestid))); 49 | } 50 | 51 | if ($imgType == "profilePictureAdmin") { 52 | $fileDestination = '../img/'.$fileNameNew; 53 | move_uploaded_file($fileTmpName, $fileDestination); 54 | DB::query('UPDATE users SET avatar=:source WHERE id=:userid', array(':userid'=>$userid, ':source'=>$fileNameNew)); 55 | } 56 | 57 | if ($imgType == "profilePictureAdminContest") { 58 | $fileDestination = '../img/'.$fileNameNew; 59 | move_uploaded_file($fileTmpName, $fileDestination); 60 | DB::query('UPDATE contests SET photo=:source WHERE id=:userid', array(':userid'=>$userid, ':source'=>$fileNameNew)); 61 | } 62 | 63 | if ($imgType == "contestPhoto") { 64 | $fileDestination = '../img/'.$fileNameNew; 65 | move_uploaded_file($fileTmpName, $fileDestination); 66 | DB::query('UPDATE contests SET photo=:source WHERE id=:userid', array(':userid'=>$userid, ':source'=>$fileNameNew)); 67 | } 68 | 69 | } else { 70 | echo "Your file is too big!"; 71 | } 72 | } else { 73 | echo "There was an error uploading your file"; 74 | } 75 | } else { 76 | //echo "You cannot upload files of this type"; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /app/Middlewares/Authentication.php: -------------------------------------------------------------------------------- 1 | 'Unauthorized user', 13 | 'status'=>2 14 | ); 15 | 16 | return Response::send($response, 401); 17 | } 18 | 19 | if (count(explode(' ', apache_request_headers()['Authorization'])) <= 1) { 20 | $response = array( 21 | 'message'=>'Could not find the token', 22 | 'status'=>2, 23 | ); 24 | 25 | return Response::send($response, 401); 26 | } 27 | 28 | $req_token = explode(' ', apache_request_headers()['Authorization'])[1]; 29 | 30 | if (JwtUtility::validate_token($req_token)) { 31 | $user_id = JwtUtility::getUserDetails($req_token)->user_id; 32 | 33 | $request->setExtras(['user_id'=> $user_id]); 34 | return $request; 35 | } else { 36 | $response = array( 37 | 'message'=>'Unauthorized user', 38 | 'status'=>2 39 | ); 40 | return Response::send($response, 401); 41 | } 42 | 43 | } 44 | } 45 | 46 | ?> -------------------------------------------------------------------------------- /app/Models/UserModel.php: -------------------------------------------------------------------------------- 1 | id(); 14 | $schema->string('username'); 15 | $schema->string('firstname'); 16 | $schema->string('lastname'); 17 | $schema->string('password'); 18 | $schema->string('email'); 19 | $schema->create(); 20 | } 21 | } 22 | 23 | ?> -------------------------------------------------------------------------------- /app/View.php: -------------------------------------------------------------------------------- 1 | buildStyles($style_contents); 67 | 68 | return ""; 69 | }, $processedContent); 70 | 71 | echo $processedContent; 72 | } 73 | 74 | private static $iteration = 0; 75 | 76 | public static function asignElementId(string $htmlContent) { 77 | // matches for html opening or self enclosed tags 78 | $match = "/(\s*<\w+\s*)(((\w*=\".*\"\s*)|(\w*='.*'\s*))*)(\s*)(>|\/>)/"; 79 | 80 | $processedContent = preg_replace_callback($match, function ($matches) { 81 | ++self::$iteration; 82 | $elementId = BaseUtility::generateRandomString(6); 83 | 84 | $newhtmlTag = $matches[1] . ' spkId="' . self::$iteration . '-'.$elementId.'"' . " " .$matches[2] . " " . $matches[7]; 85 | return $newhtmlTag; 86 | }, $htmlContent); 87 | 88 | return $processedContent; 89 | } 90 | 91 | 92 | private static $dom_updates = ""; 93 | public static function compileDomUpdates(string $content) { 94 | error_log("compileDomUpdates running ..."); 95 | 96 | $processedContent = preg_replace_callback("/(<\/\w*>|<\w>)/", function ($matches) { 97 | return $matches[0] . "\n"; 98 | }, $content); 99 | 100 | // error_log($processedContent); 101 | 102 | $match = "/((\s*<\w+\s*)(((\w*=\".*\"\s*)|(\w*='.*'\s*))*)(\s*)(>|\/>))(.*)(\{\s*(\w+)\s*\}(.*))(<\/\w*>|<\w>)/"; 103 | 104 | $processedContent = preg_replace_callback($match, function ($matches) { 105 | 106 | $attribute_match = "/(spkId=\")([\w-]*)(\")/"; 107 | 108 | $has_match = preg_match($attribute_match, $matches[3], $attr_matches); 109 | 110 | if ($has_match) { 111 | $state_key = $matches[11]; 112 | $state_list = array_filter(self::$reactive_state_list, function($value) use ($state_key) { 113 | return $value['value'] == $state_key; 114 | }); 115 | 116 | if (count($state_list) > 0) { 117 | $update_data = "document.querySelector('[spkId=\"".$attr_matches[2]."\"]').textContent = `".$matches[9]." \${".$state_key."} ".$matches[12]."`;"; 118 | 119 | $update_list = self::$reactive_state_list[$state_key]["updates"]; 120 | array_push($update_list, $update_data); 121 | 122 | self::$reactive_state_list[$state_key]["updates"] = $update_list; 123 | self::$dom_updates = self::$dom_updates . $update_data; 124 | } 125 | } 126 | 127 | return str_replace($matches[10], '$'.$matches[10], $matches[0]); 128 | }, $processedContent); 129 | 130 | 131 | $processedContent = preg_replace_callback("/( 289 | EOD; 290 | 291 | }, $raw_script_content); 292 | 293 | $render = self::$component_logic; 294 | 295 | echo << 297 | 298 | 299 | 300 | title 301 | 302 | 303 | $render 304 | 305 | 306 | 307 | EOD; 308 | } 309 | 310 | public function buildStyles(string $content) { 311 | $css_generator = new CssGenerator($content); 312 | return $css_generator->compiled_css; 313 | } 314 | 315 | public static function useLayout(string $viewName, array $data = []) { 316 | $viewName = str_replace('../', '', $viewName); 317 | $layoutContent = self::layoutContent(); 318 | $viewContent = self::renderView($viewName); 319 | 320 | $content = str_replace('{{content}}', $viewContent, $layoutContent); 321 | $match = "/(\{\{)(|\s+)([\w]+)(|\s+)(\}\})/"; 322 | // $replace_text = '$\3'; 323 | 324 | 325 | $processedContent = preg_replace_callback($match, function ($matches) use ($data) { 326 | $text = $matches[3]; 327 | return $data[$text] ?? ""; 328 | }, $content); 329 | 330 | 331 | echo $processedContent; 332 | } 333 | 334 | protected static function layoutContent() { 335 | ob_start(); 336 | 337 | include_once __DIR__ . "/../views/layouts/main.php"; 338 | 339 | return ob_get_clean(); 340 | } 341 | } 342 | ?> -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparkle/sparkle", 3 | "description": "Sparkle is a free to use open source php MVC framework for building web applications. It's very simple to work with and has a very low learning curve.", 4 | "type": "project", 5 | "license": "MIT", 6 | "autoload": { 7 | "psr-4": { 8 | "App\\": "app/" 9 | } 10 | }, 11 | "authors": [ 12 | { 13 | "name": "loftytech", 14 | "email": "58659264+loftytech@users.noreply.github.com" 15 | } 16 | ], 17 | "minimum-stability": "stable", 18 | "require": {} 19 | } 20 | -------------------------------------------------------------------------------- /core/Route.php: -------------------------------------------------------------------------------- 1 | substr($set_url_arr[$i], 1), 54 | 'index' => $i 55 | )); 56 | 57 | if ($req_url_arr[$i] != '') { 58 | array_push($url_params, array( 59 | 'data'=> $req_url_arr[$i], 60 | 'index' => $i 61 | )); 62 | } 63 | 64 | } else { 65 | $set_url_str = $set_url_str . $set_url_arr[$i]; 66 | 67 | if ($req_url_len == $set_url_len) { 68 | $req_url_str = $req_url_str . $req_url_arr[$i]; 69 | } 70 | } 71 | } 72 | 73 | if (count($url_params) == count($set_params)) { 74 | for ($i=0; $i < count($set_params); $i++) { 75 | $att_params = array_merge($att_params, array( 76 | $set_params[$i]['data'] => $req_url_arr[$set_params[$i]['index']] 77 | )); 78 | } 79 | } 80 | } 81 | 82 | if ($req_url_len == $set_url_len && strtolower($req_url_str) == strtolower($set_url_str) && count($url_params) == count($set_params) && strtolower($_SERVER['REQUEST_METHOD']) == strtolower($type)) { 83 | $request = new Request(method: $_SERVER['REQUEST_METHOD'], path: $raw_req_url, params: $att_params, query: $_GET, body: array_merge(json_decode(file_get_contents('php://input'), true) ?? [], $_POST), headers: apache_request_headers() ?? []); 84 | 85 | foreach($functions as $key => $function) { 86 | 87 | if (is_callable($function)) { 88 | $returnedData = $function($request); 89 | if ($returnedData != null) { 90 | $request = $returnedData; 91 | } else { 92 | exit; 93 | } 94 | } else if (is_array($function)) { 95 | [$class, $method] = $function; 96 | 97 | if (class_exists($class)) { 98 | $class = new $class(); 99 | 100 | if (method_exists($class, $method)) { 101 | $returnedData = call_user_func_array([$class, $method], [$request]); 102 | if ($returnedData != null) { 103 | $request = $returnedData; 104 | } else { 105 | exit; 106 | } 107 | } 108 | } 109 | } 110 | } 111 | exit; 112 | 113 | } else if ($route == "/404") { 114 | $lastMethod = end($functions); 115 | $lastMethod(); 116 | exit; 117 | } else { 118 | // echo "Invalid route"; 119 | } 120 | } 121 | 122 | public static function set($route, $type, array | callable ...$functions ) { 123 | self::validateRoute($route, $type, ...$functions); 124 | } 125 | 126 | public static function get($route, array | callable ...$functions ) { 127 | self::validateRoute($route, "GET", ...$functions); 128 | } 129 | 130 | public static function post($route, array | callable ...$functions ) { 131 | self::validateRoute($route, "POST", ...$functions); 132 | } 133 | public static function patch($route, array | callable ...$functions ) { 134 | self::validateRoute($route, "PATCH", ...$functions); 135 | } 136 | public static function put($route, array | callable ...$functions ) { 137 | self::validateRoute($route, "PUT", ...$functions); 138 | } 139 | public static function delete($route, array | callable ...$functions ) { 140 | self::validateRoute($route, "DELETE", ...$functions); 141 | } 142 | } 143 | 144 | ?> -------------------------------------------------------------------------------- /core/env_loader.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | Options -Indexes 2 | 3 | RewriteEngine On 4 | RewriteCond %{REQUEST_URI} !/css 5 | RewriteCond %{REQUEST_URI} !/uploads 6 | RewriteCond %{REQUEST_URI} !/img 7 | RewriteCond %{REQUEST_URI} !/svg 8 | RewriteCond %{REQUEST_URI} !/lofty-admin 9 | RewriteCond %{REQUEST_URI} !/ads.txt 10 | RewriteCond %{REQUEST_URI} !/favicon.ico 11 | RewriteCond %{REQUEST_URI} !/icon.png 12 | RewriteRule ^([^/]+)/? index.php?url=$1 [L,QSA] 13 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); 2 | 3 | *{ 4 | margin: 0px; 5 | padding: 0px; 6 | list-style:none; 7 | text-decoration:none; 8 | font-family: 'Montserrat', sans-serif; 9 | font-weight: 400; 10 | } 11 | html { 12 | box-sizing: border-box; 13 | } 14 | *, *::before, *::after { 15 | box-sizing: inherit; 16 | } 17 | :root { 18 | --primary-color: #cddd1f; 19 | } 20 | h1, h2, h3 { 21 | font-size: 1.2em; 22 | } 23 | 24 | a { 25 | color: #dd1f6f; 26 | } 27 | body { 28 | background: #eaeaea; 29 | max-width: 100vw; 30 | overflow: hidden; 31 | } 32 | img { 33 | display: block; 34 | max-width: 100%; 35 | } 36 | /* 1.0 Header */ 37 | 38 | .wrapper { 39 | display: flex; 40 | justify-content: center; 41 | align-items: center; 42 | flex-direction: column; 43 | background-color: #222; 44 | width: 100vw; 45 | height: 100vh; 46 | padding: 20px; 47 | } 48 | .wrapper h2 { 49 | color: var(--primary-color); 50 | font-weight: 800; 51 | font-size: 3rem; 52 | text-align: center; 53 | } 54 | .wrapper p { 55 | color: #f7f7f7; 56 | margin-top: 40px; 57 | font-weight: 300; 58 | font-size: 14px; 59 | text-align: center; 60 | line-height: 24px; 61 | } 62 | 63 | .meta-links ul { 64 | display: flex; 65 | gap: 20px; 66 | flex-wrap: wrap; 67 | margin-top: 20px; 68 | } 69 | .meta-links ul a { 70 | display: flex; 71 | align-items: center; 72 | gap: 10px; 73 | color: #ffffff; 74 | font-weight: 600; 75 | } 76 | .meta-links ul a span { 77 | text-decoration: underline; 78 | } 79 | 80 | @media screen and (min-width: 720px) { 81 | .wrapper h2 { 82 | font-size: 5rem; 83 | } 84 | } -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | 0, 24 | 'message'=>'not found', 25 | ], 404); 26 | }); 27 | 28 | 29 | ?> -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /star: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/private/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /storage/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /views/Home.php: -------------------------------------------------------------------------------- 1 |
2 |

Welcome to

3 |

This is a demo page for illustration purposes. You can edit or remove the file in the views directory.

4 | 5 | 6 | 13 |
-------------------------------------------------------------------------------- /views/Test.php: -------------------------------------------------------------------------------- 1 |
2 |

Welcome to

3 |

This is a demo page for illustration purposes. You can edit or remove the file in the views directory.

4 | 5 |
6 | counter: {counter} lol 7 | 8 |
9 | 10 | 11 |

{userName}

12 | 13 | 14 | 21 |
22 | 23 | 54 | 55 | 75 | 76 | -------------------------------------------------------------------------------- /views/layouts/main.php: -------------------------------------------------------------------------------- 1 | 2 | {{content}} 3 | -------------------------------------------------------------------------------- /views/widgets/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /views/widgets/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{site_title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 |
17 | 18 |
19 | --------------------------------------------------------------------------------