init
This commit is contained in:
commit
4691f92900
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# Composer vendor directory
|
||||
/vendor/
|
||||
|
||||
# ОС/IDE/Editor
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# PHPStorm / JetBrains
|
||||
.idea/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# Cache Composer
|
||||
composer.phar
|
||||
|
||||
# Cache and Logs
|
||||
*.log
|
||||
*.cache
|
||||
|
||||
# PHPUnit
|
||||
/phpunit.xml
|
||||
/phpunit.xml.dist
|
||||
/test-results/
|
||||
|
||||
# Coverage reports
|
||||
coverage/
|
||||
|
||||
# Build artifacts
|
||||
/build/
|
||||
|
||||
# Production config
|
||||
config.php
|
||||
269
README.md
Normal file
269
README.md
Normal file
@ -0,0 +1,269 @@
|
||||
# FridgeBites Project
|
||||
|
||||
The FridgeBites school project is a web-based platform designed to ....
|
||||
|
||||
The FridgeBites was developed almost entirely from scratch using PHP, without relying on any frameworks.
|
||||
|
||||
> The FridgeBites project was developed as part of a coursework assignment for the [Subotica Tech - College of Applied Sciences](https://www.vts.su.ac.rs/) program, and it is not intended for commercial use.
|
||||
|
||||
* [Libraries](#libraries)
|
||||
* [Installing](#installing)
|
||||
* [Configuration](#configuration)
|
||||
* Server
|
||||
* [Project structure](#project-structure)
|
||||
* [MVC (Model-View-Controller)](#mvc-model-view-controller)
|
||||
* [BaseModel](#basemodel)
|
||||
* [BaseController and Page Rendering](#basecontroller-and-page-rendering)
|
||||
* [Router](#router-and-url-handling)
|
||||
* [Ajax](#ajax)
|
||||
|
||||
## Libraries
|
||||
|
||||
The website is built entirely from scratch using pure PHP, without relying on any frameworks. This was done as an experimental project to enhance personal skills. Despite this, some libraries were used to add specific functionality:
|
||||
|
||||
- [\[PHP\] PHPMailer](https://github.com/PHPMailer/PHPMailer): A library for sending emails, used for password recovery functionality.
|
||||
- [\[JS\] Swiper.js](https://github.com/nolimits4web/swiper): A highly customizable library for creating sliders and carousels.
|
||||
- [\[JS\] Toastify.js](https://github.com/apvarun/toastify-js): A lightweight library for creating beautiful and customizable toast notifications.
|
||||
|
||||
## Installing
|
||||
|
||||
1. Install PHP dependencies using Composer:
|
||||
```
|
||||
composer install
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The configuration template file is located in the root directory of the project as `config-default.php`.
|
||||
|
||||
You need to make a copy of this file named `config.php` and update it with the appropriate settings for your project.
|
||||
|
||||
## Project Structure
|
||||
|
||||
The project is organized as follows:
|
||||
|
||||
- `apps/` - Contains all mini-applications.
|
||||
- `{app_name}/` - A single mini-application.
|
||||
- `Templates/` - Page views.
|
||||
- `components/` - Views for individual components.
|
||||
- `Models/` - Models for creating and managing objects.
|
||||
- `Controllers/` - Controllers manage the logic and coordinate between Models/ and Templates/.
|
||||
- `components.php` *(optional)* - Functions for handling the logic and rendering of specific components.
|
||||
- `functions.php` *(optional)* - Utility functions specific to the application.
|
||||
- `urls.php` - Defines an array of routes that map to controllers for rendering pages. These routes are later linked to the root `urls.php`.
|
||||
- `assets/` - Static files such as JavaScript, CSS, and images.
|
||||
- `includes/` - Core PHP scripts and libraries.
|
||||
- `media/` - Uploaded files stored on the server.
|
||||
- `.htaccess` - Configuration file for the web server (e.g., URL rewriting).
|
||||
- `config-default.php` - A template configuration file. Can be used as a base to create `config.php`.
|
||||
- `config.php` - The main configuration file, containing constants and key settings.
|
||||
- `db.php` - Functions for interacting with the database.
|
||||
- `functions.php` - Global utility functions used across the project.
|
||||
- `index.php` - The entry point for the site. Loads the router, configuration files, and more.
|
||||
- `urls.php` - Combines all `urls.php` files from mini-applications into a unified router.
|
||||
|
||||
|
||||
## MVC (Model-View-Controller)
|
||||
|
||||
To interact with database objects, we use classes inherited from `BaseModel`. For rendering pages, all logic is implemented in controllers inherited from `BaseController`, with a specified path to the corresponding view.
|
||||
|
||||
All views are connected via the router.
|
||||
|
||||
### BaseModel
|
||||
|
||||
`use Lycoreco\Includes\Model\BaseModel;`
|
||||
|
||||
The `BaseModel` class is responsible for managing data stored and processed on the server. All other models inherit from this base class.
|
||||
|
||||
#### To create a model, the following fields need to be defined:
|
||||
|
||||
- `public $field_{column_name}`: Public fields corresponding to the current table's columns, which will later be accessible.
|
||||
- `static protected $table_name`: The name of the database table.
|
||||
- `static protected $table_fields`: Specifies the fields and their data types (`int`, `string`, `bool`, or `DateTime`):
|
||||
```php
|
||||
static protected $table_fields = [
|
||||
'id' => 'int',
|
||||
'user_id' => 'int',
|
||||
'recovery_slug' => 'string',
|
||||
'created_at' => 'DateTime',
|
||||
'is_used' => 'bool'
|
||||
];
|
||||
```
|
||||
- `static protected $additional_fields`: Additional fields to be joined with the current model. The field names will later be the same as the variables. (Field names from the main table are prefixed with `obj.{column_name}`):
|
||||
```php
|
||||
static protected $additional_fields = [
|
||||
[
|
||||
"field" => [
|
||||
"tb2.name AS platform_title",
|
||||
"tb2.icon_html AS platform_icon",
|
||||
"tb2.background_color AS platform_background"
|
||||
],
|
||||
"join_table" => "taxonomies tb2 ON tb2.id = obj.platform_id"
|
||||
]
|
||||
];
|
||||
```
|
||||
- `static protected $search_fields`: A list of fields to use for search queries. (Field names from the main table are prefixed with `obj.{column_name}`):
|
||||
```php
|
||||
static protected $search_fields = ['obj.title'];
|
||||
```
|
||||
|
||||
#### Methods in BaseModel:
|
||||
|
||||
* `static init_table()`: Creates a new table for the model (used only in `setup.php`). Can use `db_query()` internally.
|
||||
* `static filter(fields, sort_by, count, field_relation, offset, search, additional_fields)`: Retrieves an array of objects based on the specified filter.
|
||||
```php
|
||||
$objects = CustomModel::filter(
|
||||
[
|
||||
// Field filters
|
||||
[
|
||||
'name' => 'obj.sales', // Field name
|
||||
'type' => '>', // Comparison type (=, >, <, >=, <=, LIKE, IN). Default is "="
|
||||
'value' => 20, // Value to compare
|
||||
'is_having' => false // Search in WHERE or HAVING (not tied to the model)
|
||||
]
|
||||
],
|
||||
['-obj.sales', 'obj.title'], // Sorting (prefix with "-" for descending order)
|
||||
10, // Number of objects to retrieve (COUNT)
|
||||
'AND', // Combine WHERE/HAVING with AND or OR
|
||||
0, // Offset
|
||||
'detroit', // Search value
|
||||
[] // Additional fields (same as `$additional_fields`)
|
||||
);
|
||||
```
|
||||
|
||||
- `static get(fields, sort_by)`: Similar to `filter()`, but retrieves only the first matching model. Returns false if none is found.
|
||||
- `static count(fields, search)`: Gets the count of objects matching the filter.
|
||||
- `is_saved()`: Checks if the current object is saved in the database.
|
||||
- `delete()`: Deletes the object from the database.
|
||||
- `save()`: Saves the object to the database (either updates or creates a new record).
|
||||
---
|
||||
- `after_save()`: Additional functionality to execute after saving (optional).
|
||||
- `valid()`: Validates the data. If there are issues, returns an array of string messages.
|
||||
|
||||
### BaseController and Page Rendering
|
||||
|
||||
`use Lycoreco\Include\BaseController;`
|
||||
|
||||
For managing a specific page, we use a class that extends the `BaseController`, located at `/includes/BaseController.php`.
|
||||
|
||||
Within this class, we can implement and modify the following methods and fields:
|
||||
|
||||
* `$template_name` - The path to the view. Typically located in the `Templates/` folder of the current application.
|
||||
* `$allow_role` - Specifies which user role is permitted to access the view (`null`, `"user"`, or `"admin"`).
|
||||
* `get_model()` - Retrieves a specific model associated with the page (e.g., `SinglePostController`).
|
||||
* `get_context_data()` - Passes an array of variables in `$context` to the view.
|
||||
* `distinct()` - Provides additional functionality before rendering the view.
|
||||
* `post()` - Executes only if the page is accessed via the POST method.
|
||||
|
||||
### Router and URL Handling
|
||||
|
||||
The main `Router` class is located at `includes/Routing/Router.php`. This class serves as the core for routing and displaying the appropriate controller based on the path.
|
||||
|
||||
#### Classes Overview
|
||||
|
||||
`namespace Lycoreco\Includes\Routing;`
|
||||
|
||||
- **`Router`**: The primary class used for determining which controller to render based on the requested path.
|
||||
- **`Path`**: A helper class where the path to a controller and the link name are specified.
|
||||
- **Exception Classes**: Used for handling errors, including:
|
||||
- `PageError` (general, 500)
|
||||
- `PermissionDenied403`
|
||||
- `Unauthorized401`
|
||||
- `BadRequest400`
|
||||
- `NotFound404`
|
||||
|
||||
#### Defining Routes
|
||||
|
||||
In mini-applications, routes are defined in a `urls.php` file, where paths and controllers are specified. Variables like `slug` or `id` can be passed using placeholders such as `[:string]` or `[:int]`.
|
||||
|
||||
```php
|
||||
use Lycoreco\Apps\MyApp\Controllers;
|
||||
|
||||
$my_app_urls = [
|
||||
// path, controller, name
|
||||
new Path('/item/[:string]', new Controllers\SingleController(), 'single'),
|
||||
new Path('/home', new Controllers\HomeController(), 'home'),
|
||||
];
|
||||
```
|
||||
|
||||
#### Integrating Routes into the Main Application
|
||||
|
||||
The array of mini-application routes is then added to the **root directory**'s `urls.php` file, specifying the group name. Additionally, error controllers (e.g., "default", 404, 500) can also be defined:
|
||||
|
||||
```php
|
||||
use Lycoreco\Includes\Routing\Router;
|
||||
|
||||
require APPS_PATH . '/my_app/urls.php';
|
||||
|
||||
Router::includes($my_app_urls, "myapp");
|
||||
Router::set_error_controller('default', new ErrorController());
|
||||
```
|
||||
|
||||
|
||||
## Ajax
|
||||
The website supports AJAX functionality through a dedicated mini-application called `ajax` and an `AjaxController` (accessible via `/ajax`). This setup enables features such as:
|
||||
* Adding items to the wishlist
|
||||
* Real-time search
|
||||
* Adding items to the cart
|
||||
* Viewing the cart
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Data Processing:** In the `AjaxController`, data is sent via the request body in JSON format. The data is then processed by a function specified in the action attribute of the JSON object.
|
||||
|
||||
2. **Creating a Custom Action:** To create a custom action, you need to add a new function in `apps/Ajax/ajax-actions.php`.
|
||||
* The function must be named `ajax_{your_action_name}`.
|
||||
* It takes an associative array from the args attribute of the JSON object as its argument.
|
||||
* The function should return an array (either associative or indexed), which will be encoded into JSON for the client.
|
||||
* Error Handling: To trigger an error, return `get_ajax_error($message, $error_code)`
|
||||
|
||||
3. **Frontend Interaction:** On the frontend, you can use the `fetch()` API or an existing `sendAjax` function. The sendAjax function accepts the following parameters:
|
||||
* `action` — The name of the action to be executed.
|
||||
* `args` — An object containing arguments to be passed to the action.
|
||||
* `onLoad` — A callback function executed when the request starts.
|
||||
* `onSuccess` — A callback function executed upon a successful response.
|
||||
* `onError` — A callback function executed if an error occurs.
|
||||
|
||||
|
||||
### Example: Custom Action in PHP (backend) and Javascript (frontend)
|
||||
|
||||
**PHP (backend)**
|
||||
```php
|
||||
// Example action: Return a number with a message
|
||||
function ajax_my_number($args) {
|
||||
$result = array();
|
||||
|
||||
// Validate the input
|
||||
$num = $args['number'];
|
||||
if (!isset($num) || !is_numeric($num)) {
|
||||
return get_ajax_error("Invalid number", 400); // Return error for invalid input
|
||||
}
|
||||
|
||||
// Prepare the response
|
||||
$result['message'] = "My number is " . $num;
|
||||
|
||||
return json_encode($result, JSON_PRETTY_PRINT); // Return JSON-encoded result
|
||||
}
|
||||
```
|
||||
**Javascript (frontend)**
|
||||
```js
|
||||
sendAjax(
|
||||
// action
|
||||
"my_number",
|
||||
// args
|
||||
{
|
||||
number: 5
|
||||
},
|
||||
// onLoad
|
||||
() => {
|
||||
console.log('loading...');
|
||||
},
|
||||
// onSuccess
|
||||
(result) => {
|
||||
console.log(result.message);
|
||||
},
|
||||
// onError
|
||||
(error) => {
|
||||
console.log(error.message);
|
||||
});
|
||||
```
|
||||
20
apps/Admin/Controllers/Abstract/AdminBaseController.php
Normal file
20
apps/Admin/Controllers/Abstract/AdminBaseController.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Admin\Controllers\Abstract;
|
||||
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
abstract class AdminBaseController extends BaseController
|
||||
{
|
||||
protected $allow_role = 'admin';
|
||||
|
||||
public function get_context_data()
|
||||
{
|
||||
$context = array();
|
||||
|
||||
// Add admin components (the_admin_header, ...)
|
||||
require_once APPS_PATH . '/Admin/components.php';
|
||||
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
88
apps/Admin/Controllers/Abstract/AdminListController.php
Normal file
88
apps/Admin/Controllers/Abstract/AdminListController.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Admin\Controllers\Abstract;
|
||||
|
||||
define('ADMIN_MAX_ELEMENTS', value: 20);
|
||||
|
||||
abstract class AdminListController extends AdminBaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Admin/Templates/list-view.php';
|
||||
|
||||
// Model related with BaseModel
|
||||
protected $model_сlass_name;
|
||||
/**
|
||||
* Fields to show in the table
|
||||
* array(
|
||||
* "title_1" => "field_name_1",
|
||||
* "title_2" => "field_name_2",
|
||||
* )
|
||||
* @var array
|
||||
*/
|
||||
protected $table_fields;
|
||||
protected $single_router_name;
|
||||
protected $create_router_name;
|
||||
protected $verbose_name = "object";
|
||||
protected $verbose_name_multiply = "objects";
|
||||
protected $sort_by = array();
|
||||
|
||||
public function custom_filter_fields()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
public function get_context_data()
|
||||
{
|
||||
$context = parent::get_context_data();
|
||||
|
||||
$search = $_GET['s'] ?? '';
|
||||
|
||||
$page = 1;
|
||||
if (isset($_GET['page'])) {
|
||||
$page = (int)$_GET['page'];
|
||||
}
|
||||
|
||||
// Get objects by sort and search
|
||||
$context['objects'] = $this->model_сlass_name::filter(
|
||||
$this->custom_filter_fields(),
|
||||
$this->sort_by,
|
||||
ADMIN_MAX_ELEMENTS,
|
||||
'OR',
|
||||
calc_page_offset(ADMIN_MAX_ELEMENTS, $page),
|
||||
$search
|
||||
);
|
||||
$context['page'] = $page;
|
||||
$context['elem_per_page'] = ADMIN_MAX_ELEMENTS;
|
||||
$context['count'] = $this->model_сlass_name::count(
|
||||
$this->custom_filter_fields(),
|
||||
$search
|
||||
);
|
||||
|
||||
// Get fields to display in the table
|
||||
$table_fields = array();
|
||||
foreach ($this->table_fields as $title => $field) {
|
||||
// Check field is function or not
|
||||
$is_func = false;
|
||||
if (str_ends_with($field, "()")) {
|
||||
$is_func = true;
|
||||
$field = substr($field, 0, -strlen('()'));
|
||||
}
|
||||
|
||||
|
||||
$table_fields[] = array(
|
||||
'field_title' => $title,
|
||||
'is_func' => $is_func,
|
||||
'field_name' => $field
|
||||
);
|
||||
}
|
||||
$context['table_fields'] = $table_fields;
|
||||
|
||||
// Verbose names
|
||||
$context['verbose_name'] = $this->verbose_name;
|
||||
$context['verbose_name_multiply'] = $this->verbose_name_multiply;
|
||||
|
||||
// Router name to link every object and link to create object
|
||||
$context['single_router_name'] = $this->single_router_name;
|
||||
$context['create_router_name'] = $this->create_router_name;
|
||||
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
186
apps/Admin/Controllers/Abstract/AdminSingleController.php
Normal file
186
apps/Admin/Controllers/Abstract/AdminSingleController.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Admin\Controllers\Abstract;
|
||||
|
||||
use Lycoreco\Includes\Model\{
|
||||
ValidationError,
|
||||
CustomDateTime
|
||||
};
|
||||
use Lycoreco\Includes\Routing\HttpExceptions;
|
||||
|
||||
abstract class AdminSingleController extends AdminBaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Admin/Templates/single-view.php';
|
||||
|
||||
protected $model_сlass_name;
|
||||
|
||||
/**
|
||||
* Router name to redirect new object
|
||||
* @var string
|
||||
*/
|
||||
protected $object_router_name;
|
||||
|
||||
/**
|
||||
* Relation form fields and model fields
|
||||
* array(
|
||||
* [
|
||||
* 'model_field' => 'username',
|
||||
* 'input_type' => 'text',
|
||||
* 'dynamic_save' => false, // Default is true. If false, field won't save automatically. You can use before_save()
|
||||
* 'input_label' => 'My Username', // If not set, default is 'model_field' with Capitalize
|
||||
* 'input_attrs' => ['disabled', 'required', 'maxlength="50"'], // Default empty
|
||||
* 'input_values' => [ ['value_1', 'My Value 1'], ... ] // For select field.
|
||||
* ]
|
||||
* )
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = array();
|
||||
protected $field_title = 'field_id';
|
||||
protected $edit_title_template = 'Edit [:verbose] "[:field]"';
|
||||
protected $can_save = true;
|
||||
|
||||
/**
|
||||
* Function names with $object attribute.
|
||||
* ['the_last_feedbacks']
|
||||
* @var array
|
||||
*/
|
||||
protected $component_widgets = array();
|
||||
protected $verbose_name = 'object';
|
||||
|
||||
/**
|
||||
* Show model in admin panel
|
||||
* @param bool $is_new is new model (Create)?
|
||||
*/
|
||||
public function __construct($is_new = false)
|
||||
{
|
||||
$this->context['is_new'] = $is_new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom model object
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_model()
|
||||
{
|
||||
if (isset($this->__model))
|
||||
return $this->__model;
|
||||
|
||||
if ($this->context['is_new']) {
|
||||
$this->__model = new $this->model_сlass_name();
|
||||
} else {
|
||||
$this->__model = $this->model_сlass_name::get(
|
||||
array(
|
||||
[
|
||||
'name' => 'obj.id',
|
||||
'type' => '=',
|
||||
'value' => $this->url_context['url_1']
|
||||
]
|
||||
)
|
||||
);
|
||||
if (empty($this->__model))
|
||||
throw new HttpExceptions\NotFound404();
|
||||
}
|
||||
return $this->__model;
|
||||
}
|
||||
|
||||
public function get_context_data()
|
||||
{
|
||||
$context = parent::get_context_data();
|
||||
|
||||
$object = $this->get_model();
|
||||
|
||||
$context['object'] = $object;
|
||||
$context['verbose_name'] = $this->verbose_name;
|
||||
|
||||
$context['edit_title'] = str_replace('[:verbose]', $this->verbose_name, $this->edit_title_template);
|
||||
if ($object->is_saved())
|
||||
$context['edit_title'] = str_replace('[:field]', $object->{$this->field_title}, $context['edit_title']);
|
||||
|
||||
$context['object_table_name'] = $object->get_table_name();
|
||||
|
||||
$context['fields'] = $this->fields;
|
||||
$context['can_save'] = $this->can_save;
|
||||
$context['component_widgets'] = $this->component_widgets;
|
||||
|
||||
return $context;
|
||||
}
|
||||
/**
|
||||
* Some code for updating object before save
|
||||
* @param mixed $object
|
||||
* @return void
|
||||
*/
|
||||
protected function before_save(&$object) {}
|
||||
protected function save(&$object)
|
||||
{
|
||||
$object->save();
|
||||
}
|
||||
|
||||
protected function post()
|
||||
{
|
||||
$object = $this->get_model();
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
// Exceptions
|
||||
if (gettype($field) == 'string')
|
||||
continue;
|
||||
|
||||
if (isset($field['dynamic_save']))
|
||||
if ($field['dynamic_save'] === false)
|
||||
continue;
|
||||
|
||||
// Get value from fields
|
||||
$field_value = null;
|
||||
switch ($field['input_type']) {
|
||||
case 'checkbox':
|
||||
$field_value = $_POST[$field['model_field']] ? true : false;
|
||||
break;
|
||||
case 'image':
|
||||
$file = $_FILES[$field['model_field']];
|
||||
if (isset($file)) {
|
||||
$path = upload_file($file, $this->model_сlass_name . '/', 'image');
|
||||
|
||||
if (!empty($path)) {
|
||||
$field_value = $path;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'datetime-local':
|
||||
$datetime_text = $_POST[$field['model_field']] ?? null;
|
||||
$datetime = new CustomDateTime();
|
||||
|
||||
if ($datetime_text)
|
||||
$datetime = new CustomDateTime($datetime_text);
|
||||
|
||||
$field_value = $datetime;
|
||||
break;
|
||||
|
||||
default:
|
||||
$field_value = $_POST[$field['model_field']] ?? null;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set value to object
|
||||
$object->{'field_' . $field['model_field']} = $field_value;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->before_save($object);
|
||||
$this->save($object);
|
||||
|
||||
// If object is new, redirect to his own page
|
||||
if ($this->context['is_new']) {
|
||||
$url = get_permalink($this->object_router_name, [$object->get_id()]);
|
||||
redirect_to($url);
|
||||
}
|
||||
|
||||
$this->context['success_message'] = "The " . $this->verbose_name . " has been saved";
|
||||
} catch (ValidationError $ex) {
|
||||
$this->context['error_form'] = $ex;
|
||||
} catch (\Exception $ex) {
|
||||
if (DEBUG_MODE)
|
||||
$this->context['error_message'] = $ex->getMessage();
|
||||
else
|
||||
$this->context['error_message'] = "Unexpected Error";
|
||||
}
|
||||
}
|
||||
}
|
||||
56
apps/Admin/Controllers/AdminBanController.php
Normal file
56
apps/Admin/Controllers/AdminBanController.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace Lycoreco\Apps\Admin\Controllers;
|
||||
|
||||
class AdminBanController extends Abstract\AdminSingleController
|
||||
{
|
||||
protected $model_сlass_name = "Lycoreco\Apps\Users\Models\BanlistModel";
|
||||
protected $field_title = 'field_created_at';
|
||||
protected $object_router_name = 'admin:ban';
|
||||
protected $verbose_name = "ban";
|
||||
protected $fields = array(
|
||||
[
|
||||
'model_field' => 'user_id',
|
||||
'input_type' => 'number',
|
||||
'dynamic_save' => false,
|
||||
'input_label' => 'User id',
|
||||
'input_attrs' => ['required', 'disabled']
|
||||
],
|
||||
[
|
||||
'model_field' => 'reason',
|
||||
'input_type' => 'text',
|
||||
'input_label' => 'Reason',
|
||||
],
|
||||
[
|
||||
'model_field' => 'end_at',
|
||||
'input_type' => 'datetime-local',
|
||||
'input_label' => 'End at',
|
||||
'input_attrs' => ['required']
|
||||
],
|
||||
[
|
||||
'model_field' => 'created_at',
|
||||
'input_type' => 'text',
|
||||
'dynamic_save' => false,
|
||||
'input_label' => 'Created at',
|
||||
'input_attrs' => ['disabled']
|
||||
],
|
||||
);
|
||||
protected function save(&$object)
|
||||
{
|
||||
$is_new = !$object->is_saved();
|
||||
|
||||
$object->save();
|
||||
|
||||
if($is_new)
|
||||
$object->send_email_notification();
|
||||
}
|
||||
|
||||
protected function get_model()
|
||||
{
|
||||
$model = parent::get_model();
|
||||
|
||||
if($this->context['is_new'])
|
||||
$model->field_user_id = (int)$this->url_context['url_1'];
|
||||
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
31
apps/Admin/Controllers/AdminBanListController.php
Normal file
31
apps/Admin/Controllers/AdminBanListController.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Admin\Controllers;
|
||||
|
||||
|
||||
class AdminBanListController extends Abstract\AdminListController
|
||||
{
|
||||
protected $model_сlass_name = "Lycoreco\Apps\Users\Models\BanlistModel";
|
||||
protected $table_fields = array(
|
||||
'Reason' => 'field_reason',
|
||||
'Created at' => 'field_created_at',
|
||||
'End at' => 'field_end_at',
|
||||
'Active?' => 'is_active()',
|
||||
);
|
||||
protected $single_router_name = 'admin:ban';
|
||||
protected $verbose_name = "ban";
|
||||
protected $verbose_name_multiply = "banlist";
|
||||
protected $sort_by = ['-obj.end_at'];
|
||||
|
||||
public function custom_filter_fields()
|
||||
{
|
||||
$user_id = $this->url_context['url_1'];
|
||||
return array(
|
||||
[
|
||||
'name' => 'obj.user_id',
|
||||
'type' => '=',
|
||||
'value' => $user_id
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
94
apps/Admin/Controllers/AdminDeleteController.php
Normal file
94
apps/Admin/Controllers/AdminDeleteController.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace Lycoreco\Apps\Admin\Controllers;
|
||||
|
||||
use Lycoreco\Includes\Routing\HttpExceptions;
|
||||
use Lycoreco\Apps\Users\Models\UserModel;
|
||||
|
||||
/**
|
||||
* Controller to delete every object model using className and id
|
||||
*/
|
||||
class AdminDeleteController extends Abstract\AdminBaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Admin/Templates/delete.php';
|
||||
|
||||
/**
|
||||
* @return null|UserModel
|
||||
*/
|
||||
protected function get_model()
|
||||
{
|
||||
if (isset($this->__model))
|
||||
return $this->__model;
|
||||
|
||||
$id = $this->url_context['url_2'];
|
||||
$model_class = '';
|
||||
|
||||
switch ($this->url_context['url_1']) {
|
||||
case 'users':
|
||||
$model_class = "Lycoreco\Apps\Users\Models\UserModel";
|
||||
break;
|
||||
case 'user-banlist':
|
||||
$model_class = "Lycoreco\Apps\Users\Models\BanlistModel";
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return $model_class::get(array(
|
||||
[
|
||||
'name' => 'obj.id',
|
||||
'type' => '=',
|
||||
'value' => $id
|
||||
]
|
||||
));
|
||||
}
|
||||
public function get_context_data()
|
||||
{
|
||||
$context = parent::get_context_data();
|
||||
|
||||
$model = $this->get_model();
|
||||
if (empty($model))
|
||||
throw new HttpExceptions\NotFound404();
|
||||
|
||||
// Display field to show what's model
|
||||
$field = '';
|
||||
$back_url = '';
|
||||
switch ($this->url_context['url_1']) {
|
||||
case 'users':
|
||||
$back_url = get_permalink('admin:user', [$model->get_id()]);
|
||||
$field = 'field_username';
|
||||
break;
|
||||
|
||||
case 'user-banlist':
|
||||
$back_url = get_permalink('admin:ban', [$model->get_id()]);
|
||||
$field = 'field_reason';
|
||||
break;
|
||||
}
|
||||
|
||||
$context['back_url'] = $back_url;
|
||||
$context['model'] = $model;
|
||||
$context['field'] = $field;
|
||||
|
||||
return $context;
|
||||
}
|
||||
protected function post()
|
||||
{
|
||||
$model = $this->get_model();
|
||||
|
||||
if (empty($model))
|
||||
throw new HttpExceptions\NotFound404();
|
||||
|
||||
$model->delete();
|
||||
|
||||
// Redirect after delete
|
||||
$type_model = $this->url_context['url_1'];
|
||||
switch ($type_model) {
|
||||
case 'users':
|
||||
$link = get_permalink('admin:user-list');
|
||||
break;
|
||||
default:
|
||||
$link = get_permalink('admin:home');
|
||||
break;
|
||||
}
|
||||
redirect_to($link);
|
||||
}
|
||||
}
|
||||
20
apps/Admin/Controllers/AdminHomeController.php
Normal file
20
apps/Admin/Controllers/AdminHomeController.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace Lycoreco\Apps\Admin\Controllers;
|
||||
|
||||
class AdminHomeController extends Abstract\AdminBaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Admin/Templates/home.php';
|
||||
|
||||
public function get_context_data()
|
||||
{
|
||||
$context = parent::get_context_data();
|
||||
|
||||
$context['last_orders'] = [];
|
||||
|
||||
$datetime_month_ago = new \DateTime();
|
||||
$datetime_month_ago->modify("-1 month");
|
||||
|
||||
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
88
apps/Admin/Controllers/AdminUserController.php
Normal file
88
apps/Admin/Controllers/AdminUserController.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
namespace Lycoreco\Apps\Admin\Controllers;
|
||||
|
||||
use Lycoreco\Includes\Model\ValidationError;
|
||||
use Lycoreco\Apps\Users\Models\UserModel;
|
||||
|
||||
class AdminUserController extends Abstract\AdminSingleController
|
||||
{
|
||||
protected $model_сlass_name = "Lycoreco\Apps\Users\Models\UserModel";
|
||||
protected $field_title = 'field_username';
|
||||
protected $verbose_name = 'user';
|
||||
protected $object_router_name = 'admin:user';
|
||||
protected $component_widgets = ['the_user_banlist'];
|
||||
protected $fields = array(
|
||||
[
|
||||
'model_field' => 'username',
|
||||
'input_type' => 'text',
|
||||
'input_attrs' => ['required']
|
||||
],
|
||||
[
|
||||
'model_field' => 'email',
|
||||
'input_type' => 'email',
|
||||
'input_label' => 'E-mail',
|
||||
'input_attrs' => ['required']
|
||||
],
|
||||
[
|
||||
'model_field' => 'password',
|
||||
'input_type' => 'text',
|
||||
'dynamic_save' => false,
|
||||
'input_attrs' => ['required']
|
||||
],
|
||||
[
|
||||
'model_field' => 'is_admin',
|
||||
'input_type' => 'checkbox',
|
||||
'input_label' => 'Is admin'
|
||||
],
|
||||
[
|
||||
'model_field' => 'is_active',
|
||||
'input_type' => 'checkbox',
|
||||
'input_label' => 'Is active'
|
||||
],
|
||||
[
|
||||
'model_field' => 'register_at',
|
||||
'input_type' => 'text',
|
||||
'dynamic_save' => false,
|
||||
'input_label' => 'Register at',
|
||||
'input_attrs' => ['disabled']
|
||||
],
|
||||
'<hr>',
|
||||
[
|
||||
'model_field' => 'fname',
|
||||
'input_type' => 'text',
|
||||
'input_label' => 'First name',
|
||||
'input_attrs' => ['required']
|
||||
],
|
||||
[
|
||||
'model_field' => 'lname',
|
||||
'input_type' => 'text',
|
||||
'input_label' => 'Last name',
|
||||
'input_attrs' => ['required']
|
||||
]
|
||||
);
|
||||
/**
|
||||
* Some code for updating object before save
|
||||
* @param UserModel $object
|
||||
* @return void
|
||||
*/
|
||||
protected function before_save(&$object)
|
||||
{
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
// If password doesn't changed
|
||||
if ($password === $object->field_password && $this->context['is_new'] == false)
|
||||
return;
|
||||
|
||||
$result_valid = UserModel::valid_password($password);
|
||||
|
||||
// If password is valid - hash password and save to object
|
||||
if ($result_valid === true) {
|
||||
$password_hash = UserModel::password_hash($password);
|
||||
$object->field_password = $password_hash;
|
||||
}
|
||||
// if not, throw ValidationError
|
||||
else {
|
||||
throw new ValidationError($result_valid);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
apps/Admin/Controllers/AdminUserListController.php
Normal file
19
apps/Admin/Controllers/AdminUserListController.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace Lycoreco\Apps\Admin\Controllers;
|
||||
|
||||
|
||||
class AdminUserListController extends Abstract\AdminListController
|
||||
{
|
||||
protected $model_сlass_name = "Lycoreco\Apps\Users\Models\UserModel";
|
||||
protected $table_fields = array(
|
||||
'Username' => 'field_username',
|
||||
'Public Name' => 'get_public_name()',
|
||||
'E-mail ' => 'field_email',
|
||||
'Status ' => 'get_role()',
|
||||
'Banned' => 'is_banned()'
|
||||
);
|
||||
protected $single_router_name = 'admin:user';
|
||||
protected $create_router_name = 'admin:user-new';
|
||||
protected $verbose_name = "user";
|
||||
protected $verbose_name_multiply = "users";
|
||||
}
|
||||
4
apps/Admin/Templates/components/admin-footer.php
Normal file
4
apps/Admin/Templates/components/admin-footer.php
Normal file
@ -0,0 +1,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
67
apps/Admin/Templates/components/admin-header.php
Normal file
67
apps/Admin/Templates/components/admin-header.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
use Lycoreco\Includes\Routing\Router;
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo get_title_website($title); ?></title>
|
||||
<link rel="shortcut icon" type="image/png" href="<?php echo ASSETS_PATH . '/favicon.png' ?>">
|
||||
|
||||
<!-- Google fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Play:wght@400;700&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
|
||||
<!-- Google fonts/ -->
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link href="<?php echo ASSETS_PATH . '/fontawesome-free-6.6.0-web/css/fontawesome.min.css' ?>" rel="stylesheet" />
|
||||
<link href="<?php echo ASSETS_PATH . '/fontawesome-free-6.6.0-web/css/brands.min.css' ?>" rel="stylesheet" />
|
||||
<link href="<?php echo ASSETS_PATH . '/fontawesome-free-6.6.0-web/css/solid.min.css' ?>" rel="stylesheet" />
|
||||
<!-- Font Awesome/ -->
|
||||
|
||||
<link rel="stylesheet" href="<?php echo ASSETS_PATH . '/css/reset.css' ?>">
|
||||
<link rel="stylesheet" href="<?php echo ASSETS_PATH . '/css/style.css' ?>">
|
||||
<link rel="stylesheet" href="<?php echo ASSETS_PATH . '/css/admin.css' ?>">
|
||||
</head>
|
||||
<body>
|
||||
<header class="header-admin">
|
||||
<div class="logo">
|
||||
FridgeBites Admin
|
||||
</div>
|
||||
<div class="header-admin__control">
|
||||
<div class="username">
|
||||
Hello, <span><?php echo CURRENT_USER->field_username ?></span>
|
||||
</div>
|
||||
<div class="links">
|
||||
<a href="<?php the_permalink('index:home') ?>">View site</a>
|
||||
|
|
||||
<a href="<?php the_permalink('users:logout') ?>">Log Out</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="wrapper-admin">
|
||||
<aside class="admin-sidebar">
|
||||
<a href="<?php the_permalink('admin:product-new') ?>" class="btn btn-primary"><i class="fa-solid fa-plus"></i> New Product</a>
|
||||
<hr>
|
||||
<ul class="admin-sidebar__list">
|
||||
<li>
|
||||
<a class="<?php echo Router::$current_router_name == 'admin:home' ? "active" : "" ?>" href="<?php the_permalink('admin:home') ?>">
|
||||
<i class="fa-solid fa-house"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="<?php echo Router::$current_router_name == 'admin:user-list' ? "active" : "" ?>" href="<?php the_permalink('admin:user-list') ?>">
|
||||
<i class="fa-solid fa-users"></i> Users
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="wrapper-admin__content">
|
||||
|
||||
|
||||
|
||||
|
||||
13
apps/Admin/Templates/delete.php
Normal file
13
apps/Admin/Templates/delete.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php the_admin_header('Dashboard') ?>
|
||||
|
||||
<div class="admin-container delete">
|
||||
<h1 class="p-title">Delete Object</h1>
|
||||
<div class="meta-text">Do you really want to delete "<?php echo $context['model']->{$context['field']} ?>"(<?php echo $context['model']->get_id() ?>) from the "<?php echo $context['model']->get_table_name() ?>" table?</div>
|
||||
|
||||
<form method="post" class="btn-control">
|
||||
<a href="<?php echo $context['back_url'] ?>" class="btn btn-primary">Back</a>
|
||||
<button type="submit" class="btn btn-danger">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php the_admin_footer() ?>
|
||||
93
apps/Admin/Templates/home.php
Normal file
93
apps/Admin/Templates/home.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php the_admin_header('Dashboard') ?>
|
||||
|
||||
<div class="admin-container">
|
||||
<section>
|
||||
<h2>Stats per month</h2>
|
||||
|
||||
<div id="dashboard-stats">
|
||||
<div class="dashboard-stats__item top-sales">
|
||||
<div class="icon">
|
||||
<i class="fa-solid fa-bag-shopping"></i>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="label">Total sales</div>
|
||||
<div class="value">0$</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-stats__item profit">
|
||||
<div class="icon">
|
||||
<i class="fa-solid fa-money-bill-trend-up"></i>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="label">Profit</div>
|
||||
<div class="value">0$</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-stats__item orders">
|
||||
<div class="icon">
|
||||
<i class="fa-solid fa-cart-shopping"></i>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="label">Orders</div>
|
||||
<div class="value">0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-stats__item new-users">
|
||||
<div class="icon">
|
||||
<i class="fa-solid fa-user"></i>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="label">New users</div>
|
||||
<div class="value">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Quick tools</h2>
|
||||
|
||||
<div id="quicktools">
|
||||
<a href="<?php the_permalink('admin:product-new') ?>" class="btn">
|
||||
<i class="fa-solid fa-plus"></i> New Product
|
||||
</a>
|
||||
<form action="<?php the_permalink('admin:product-list') ?>" method="get">
|
||||
<div class="input">
|
||||
<input type="text" name="s" placeholder="Search for products">
|
||||
<button type="submit"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2>Latest orders</h2>
|
||||
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order number</th>
|
||||
<th>Method</th>
|
||||
<th>Total price</th>
|
||||
<th>Buyer</th>
|
||||
<th>Created at</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($context['last_orders'] as $order): ?>
|
||||
<tr>
|
||||
<td><a href="<?php the_permalink('admin:order', [$order->get_id()]) ?>"><?php echo $order->field_order_number ?></a></td>
|
||||
<td><?php echo $order->field_method ?></td>
|
||||
<td><?php echo $order->get_total_price() ?></td>
|
||||
<td><?php echo $order->get_buyer_username() ?></td>
|
||||
<td><?php echo $order->field_created_at ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php the_admin_footer() ?>
|
||||
71
apps/Admin/Templates/list-view.php
Normal file
71
apps/Admin/Templates/list-view.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php the_admin_header(ucfirst($context['verbose_name_multiply'])) ?>
|
||||
|
||||
<div class="admin-container">
|
||||
<h1 class="p-title"><?php echo ucfirst($context['verbose_name_multiply']) ?></h1>
|
||||
|
||||
<section>
|
||||
<div id="quicktools">
|
||||
<?php if(isset($context['create_router_name'])): ?>
|
||||
<a href="<?php the_permalink($context['create_router_name']) ?>" class="btn">
|
||||
<i class="fa-solid fa-plus"></i> New <?php echo ucfirst($context['verbose_name']) ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span></span>
|
||||
<?php endif; ?>
|
||||
<form method="get">
|
||||
<div class="input">
|
||||
<input type="text" name="s" placeholder="Search for <?php echo $context['verbose_name_multiply'] ?>" value="<?php echo isset($_GET['s']) ? $_GET['s'] : '' ?>">
|
||||
<button type="submit"><i class="fa-solid fa-magnifying-glass"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="admin-list">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<?php foreach($context['table_fields'] as $field): ?>
|
||||
<th><?php echo $field['field_title'] ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($context['objects'] as $object): ?>
|
||||
<tr>
|
||||
<?php for($i = 0; $i < count($context['table_fields']); $i++) {
|
||||
$field_value = '';
|
||||
if($context['table_fields'][$i]['is_func'])
|
||||
$field_value = $object->{$context['table_fields'][$i]['field_name']}();
|
||||
else
|
||||
$field_value = $object->{$context['table_fields'][$i]['field_name']};
|
||||
?>
|
||||
<td>
|
||||
<?php if($i == 0): ?>
|
||||
<a href="<?php the_permalink($context['single_router_name'], [$object->get_id()]) ?>"><?php the_safe($field_value) ?></a>
|
||||
<?php else: ?>
|
||||
|
||||
<?php
|
||||
if(gettype($field_value) == 'boolean')
|
||||
echo $field_value ? 'Yes' : 'No';
|
||||
else
|
||||
the_safe($field_value);
|
||||
?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php the_pagination($context['count'], $context['elem_per_page'], $context['page']) ?>
|
||||
|
||||
<?php if(empty($context['objects'])): ?>
|
||||
<div class="nothing">
|
||||
Nothing was found
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<?php the_admin_footer() ?>
|
||||
142
apps/Admin/Templates/single-view.php
Normal file
142
apps/Admin/Templates/single-view.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php the_admin_header(
|
||||
$context['is_new'] ? 'New ' . $context['verbose_name'] : get_the_safe($context['edit_title']));
|
||||
|
||||
$disabled_attr = $context['can_save'] ? '' : 'disabled';
|
||||
?>
|
||||
|
||||
<div class="admin-container">
|
||||
<h1 class="p-title">
|
||||
<?php if($context['object']->is_saved()): ?>
|
||||
<?php the_safe($context['edit_title']) ?>
|
||||
<?php else: ?>
|
||||
New <?php echo $context['verbose_name'] ?>
|
||||
<?php endif; ?>
|
||||
</h1>
|
||||
|
||||
<form class="form admin-single" method="post" enctype="multipart/form-data">
|
||||
<div class="admin-single__form">
|
||||
<?php
|
||||
if(isset($context['error_message']))
|
||||
the_alert($context['error_message'], 'warning', 'form-alert');
|
||||
|
||||
if(isset($context['success_message']))
|
||||
the_alert($context['success_message'], 'success', 'form-alert');
|
||||
?>
|
||||
|
||||
<?php foreach($context['fields'] as $field): ?>
|
||||
<?php
|
||||
// If field is html string
|
||||
if(gettype($field) == 'string') {
|
||||
echo $field;
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Label for every field (except checkbox) -->
|
||||
<?php
|
||||
$label = isset($field['input_label']) ? $field['input_label'] : ucfirst($field['model_field']);
|
||||
if($field['input_type'] != 'checkbox'): ?>
|
||||
<label for="<?php echo $field['model_field'] ?>">
|
||||
<?php echo $label ?> <?php echo in_array('required', $field['input_attrs'] ?? array()) ? '<span>*</span>' : '' ?>
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// Input field
|
||||
$attrs_str = isset($field['input_attrs']) ? implode(" ", $field['input_attrs']) : '';
|
||||
$val = $context['object']->{'field_' . $field['model_field']} ?? '';
|
||||
|
||||
switch($field['input_type']) {
|
||||
case 'text':
|
||||
case 'number':
|
||||
case 'email':
|
||||
case 'color':
|
||||
case 'datetime-local':
|
||||
?>
|
||||
<div class="input <?php echo $attrs_str . ' ' . $disabled_attr . ' ' . $field['input_type'] ?>">
|
||||
<input type="<?php echo $field['input_type'] ?>" id="<?php echo $field['model_field'] ?>" name="<?php echo $field['model_field'] ?>" value="<?php the_safe($val) ?>" <?php echo $attrs_str . ' ' . $disabled_attr ?> step="0.01">
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case 'checkbox':
|
||||
?>
|
||||
<div class="input-checkbox <?php echo $attrs_str ?>">
|
||||
<input type="checkbox" id="<?php echo $field['model_field'] ?>" name="<?php echo $field['model_field'] ?>" value="on" <?php echo $attrs_str ?> <?php echo $val ? 'checked' : '' ?>>
|
||||
<label for="<?php echo $field['model_field'] ?>"><?php echo $label ?></label>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case 'textarea':
|
||||
?>
|
||||
<div class="input <?php echo $attrs_str . ' ' . $disabled_attr ?>">
|
||||
<textarea name="<?php echo $field['model_field'] ?>" id="<?php echo $field['model_field'] ?>" <?php echo $attrs_str . ' ' . $disabled_attr ?> rows="7"><?php the_safe($val) ?></textarea>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case 'select':
|
||||
?>
|
||||
<div class="input-select <?php echo $attrs_str . ' ' . $disabled_attr ?>">
|
||||
<select name="<?php echo $field['model_field'] ?>" id="<?php echo $field['model_field'] ?>" <?php echo $attrs_str . ' ' . $disabled_attr ?>>
|
||||
<?php foreach($field['input_values'] as $option): ?>
|
||||
<option value="<?php echo $option[0] ?>" <?php echo $val == $option[0] ? 'selected' : '' ?>>
|
||||
<?php echo $option[1] ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
|
||||
case 'image':
|
||||
?>
|
||||
<div class="input-file">
|
||||
<input type="file" id="<?php echo $field['model_field'] ?>" name="<?php echo $field['model_field'] ?>" accept="image/*" <?php echo $attrs_str . ' ' . $disabled_attr ?>>
|
||||
|
||||
<?php if(!empty($val)): ?>
|
||||
<div class="image">
|
||||
<img src="<?php echo MEDIA_URL . $val ?>" alt="">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
|
||||
}?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="admin-single__info">
|
||||
<div class="admin-block">
|
||||
<div class="admin-block__title">
|
||||
<?php echo $context['object']->is_saved() ? "Edit " . $context['verbose_name'] : "New " . $context['verbose_name'] ?>
|
||||
</div>
|
||||
<div class="admin-block__content">
|
||||
<div class="btn-control">
|
||||
<?php if($context['object']->is_saved()): ?>
|
||||
<a href="<?php the_permalink('admin:delete', [str_replace('_', '-', $context['object']->get_table_name()), $context['object']->get_id()]) ?>" class="btn btn-danger" type="submit">Delete</a>
|
||||
<?php else: ?>
|
||||
<span></span>
|
||||
<?php endif; ?>
|
||||
|
||||
<button class="btn btn-primary" type="submit" <?php echo $disabled_attr ?>>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
if(isset($context['error_form']))
|
||||
$context['error_form']->display_error();
|
||||
?>
|
||||
|
||||
<?php
|
||||
if($context['object']->is_saved()) {
|
||||
foreach ($context['component_widgets'] as $widget) {
|
||||
$widget($context['object']);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php the_admin_footer() ?>
|
||||
26
apps/Admin/Templates/widgets/user_banlist.php
Normal file
26
apps/Admin/Templates/widgets/user_banlist.php
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="admin-block">
|
||||
<div class="admin-block__title">Banlist</div>
|
||||
<div class="admin-block__content">
|
||||
<div class="admin-block__table">
|
||||
<?php if (!empty($banlist)): ?>
|
||||
<?php foreach ($banlist as $ban): ?>
|
||||
<div class="row">
|
||||
<div class="column"><a href="<?php the_permalink('admin:ban', [$ban->get_id()]) ?>"><?php echo $ban->field_reason ?></a></div>
|
||||
|
||||
<?php if($ban->is_active()): ?>
|
||||
Active
|
||||
<?php else: ?>
|
||||
Inactive
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="nothing">Banlist empty</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div class="btn-control">
|
||||
<a href="<?php the_permalink('admin:banlist', [$user->get_id()]) ?>" class="btn">Show all</a>
|
||||
<a href="<?php the_permalink('admin:ban-new', [$user->get_id()]) ?>" class="btn btn-primary">New ban</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
33
apps/Admin/components.php
Normal file
33
apps/Admin/components.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Lycoreco\Apps\Users\Models\{
|
||||
UserModel,
|
||||
BanlistModel
|
||||
};
|
||||
|
||||
function the_admin_header(string $title)
|
||||
{
|
||||
require_once APPS_PATH . '/Admin/Templates/components/admin-header.php';
|
||||
}
|
||||
|
||||
function the_admin_footer()
|
||||
{
|
||||
require_once APPS_PATH . '/Admin/Templates/components/admin-footer.php';
|
||||
}
|
||||
|
||||
/*
|
||||
* Widgets in the single object page
|
||||
*/
|
||||
function the_user_banlist(UserModel $user)
|
||||
{
|
||||
$banlist = BanlistModel::filter(array(
|
||||
[
|
||||
'name' => 'obj.user_id',
|
||||
'type' => '=',
|
||||
'value' => $user->get_id()
|
||||
]
|
||||
),
|
||||
['-obj.end_at']);
|
||||
|
||||
require APPS_PATH . '/Admin/Templates/widgets/user_banlist.php';
|
||||
}
|
||||
25
apps/Admin/urls.php
Normal file
25
apps/Admin/urls.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Lycoreco\Apps\Admin\Controllers;
|
||||
use Lycoreco\Includes\Routing\Path;
|
||||
|
||||
$admin_urls = [
|
||||
// Dashboard
|
||||
new Path('/admin', new Controllers\AdminHomeController(), 'home'),
|
||||
|
||||
// Lists
|
||||
new Path('/admin/users', new Controllers\AdminUserListController(), 'user-list'),
|
||||
|
||||
////// Single object ///////
|
||||
// User
|
||||
new Path('/admin/user/new', new Controllers\AdminUserController(true), 'user-new'),
|
||||
new Path('/admin/user/[:int]', new Controllers\AdminUserController(false), 'user'),
|
||||
|
||||
// Ban
|
||||
new Path('/admin/user/[:int]/banlist', new Controllers\AdminBanListController(), 'banlist'),
|
||||
new Path('/admin/user/[:int]/ban/new', new Controllers\AdminBanController(true), 'ban-new'),
|
||||
new Path('/admin/ban/[:int]', new Controllers\AdminBanController(false), 'ban'),
|
||||
|
||||
// Dynamic delete for every object type
|
||||
new Path('/admin/[:string]/[:int]/delete', new Controllers\AdminDeleteController(), 'delete')
|
||||
];
|
||||
45
apps/Ajax/Controllers/AjaxController.php
Normal file
45
apps/Ajax/Controllers/AjaxController.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Ajax\Controllers;
|
||||
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class AjaxController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Ajax/Templates/ajax-result.php';
|
||||
|
||||
protected function restrict() {}
|
||||
|
||||
public function get_context_data()
|
||||
{
|
||||
require_once APPS_PATH . '/Ajax/ajax-actions.php';
|
||||
$context['result'] = "";
|
||||
|
||||
$json = file_get_contents('php://input');
|
||||
$data = json_decode($json, true);
|
||||
|
||||
$action = $data['action'] ?? false;
|
||||
|
||||
// If request from other site
|
||||
if (!in_array($_SERVER['HTTP_HOST'], ALLOWED_HOSTS)) {
|
||||
$context['result'] = get_ajax_error("403 Forbidden", 403);
|
||||
return $context;
|
||||
}
|
||||
|
||||
// if don't receive action method
|
||||
if (empty($action)) {
|
||||
$context['result'] = get_ajax_error("The action field indicating the function is not specified");
|
||||
return $context;
|
||||
}
|
||||
$action = "ajax_" . $action;
|
||||
|
||||
try {
|
||||
$context['result'] = $action($data['args']);
|
||||
} catch (\Exception $ex) {
|
||||
$context['result'] = get_ajax_error($ex->getMessage());
|
||||
}
|
||||
|
||||
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
2
apps/Ajax/Templates/ajax-result.php
Normal file
2
apps/Ajax/Templates/ajax-result.php
Normal file
@ -0,0 +1,2 @@
|
||||
<?php
|
||||
echo $context['result'];
|
||||
47
apps/Ajax/ajax-actions.php
Normal file
47
apps/Ajax/ajax-actions.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
function get_ajax_error($message, $error_code = 500)
|
||||
{
|
||||
http_response_code($error_code);
|
||||
|
||||
$error = array();
|
||||
$error['error'] = $message;
|
||||
|
||||
return json_encode($error, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax actions
|
||||
*/
|
||||
function ajax_search($args) {
|
||||
$search_query = $args['query'];
|
||||
|
||||
$result = [];
|
||||
$data = [
|
||||
[
|
||||
'id' => 2,
|
||||
'name' => 'Genshin Impact'
|
||||
],
|
||||
[
|
||||
'id' => 3,
|
||||
'name' => 'Zenless zone zero'
|
||||
],
|
||||
[
|
||||
'id' => 4,
|
||||
'name' => 'Honkai Star Rail'
|
||||
],
|
||||
[
|
||||
'id' => 5,
|
||||
'name' => 'Honkai Impact'
|
||||
],
|
||||
];
|
||||
$result['results'] = [];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if(str_contains($value['name'], $search_query))
|
||||
$result['results'][] = $value;
|
||||
}
|
||||
sleep(3);
|
||||
|
||||
return json_encode($result, JSON_PRETTY_PRINT);
|
||||
}
|
||||
8
apps/Ajax/urls.php
Normal file
8
apps/Ajax/urls.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Lycoreco\Includes\Routing\Path;
|
||||
use Lycoreco\Apps\Ajax\Controllers\AjaxController;
|
||||
|
||||
$ajax_urls = array(
|
||||
new Path('/ajax', new AjaxController(), 'main'),
|
||||
);
|
||||
10
apps/Index/Controllers/ErrorController.php
Normal file
10
apps/Index/Controllers/ErrorController.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Index\Controllers;
|
||||
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class ErrorController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Index/Templates/error.php';
|
||||
}
|
||||
10
apps/Index/Controllers/HomepageController.php
Normal file
10
apps/Index/Controllers/HomepageController.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Index\Controllers;
|
||||
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class HomepageController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Index/Templates/index.php';
|
||||
}
|
||||
23
apps/Index/Templates/error.php
Normal file
23
apps/Index/Templates/error.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
$error = $context['error_model'];
|
||||
|
||||
the_header(
|
||||
$error->get_http_error(),
|
||||
'',
|
||||
'error',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
]);
|
||||
|
||||
/**
|
||||
* @var PageError
|
||||
*/
|
||||
|
||||
?>
|
||||
|
||||
<div class="error-page">
|
||||
<div class="error-code"><?php echo $error->get_http_error() ?></div>
|
||||
<div class="error-message"><?php echo $error->getMessage() ?></div>
|
||||
</div>
|
||||
|
||||
<?php the_footer() ?>
|
||||
18
apps/Index/Templates/index.php
Normal file
18
apps/Index/Templates/index.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php the_header(
|
||||
'Welcome',
|
||||
'Discover and purchase top-rated games and software to elevate your entertainment and productivity. Explore our curated selection for the best deals and exclusive offers!',
|
||||
'frontpage',
|
||||
[
|
||||
['robots', 'nofollow, noindex'],
|
||||
['keywords', 'keys, programms, games, xbox, pc, playstation']
|
||||
]);
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
Index page
|
||||
</div>
|
||||
|
||||
<?php the_footer(array(
|
||||
ASSETS_PATH . '/swiper/swiper-bundle.min.js',
|
||||
ASSETS_PATH . '/js/index.js',
|
||||
)); ?>
|
||||
8
apps/Index/urls.php
Normal file
8
apps/Index/urls.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Lycoreco\Apps\Index\Controllers;
|
||||
use Lycoreco\Includes\Routing\Path;
|
||||
|
||||
$index_urls = [
|
||||
new Path('', new Controllers\HomepageController(), 'home'),
|
||||
];
|
||||
53
apps/Users/Controllers/ActivationController.php
Normal file
53
apps/Users/Controllers/ActivationController.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Apps\Users\Models\{
|
||||
ActivateCodeModel,
|
||||
UserModel
|
||||
};
|
||||
use Lycoreco\Includes\Routing\HttpExceptions;
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class ActivationController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/activation.php';
|
||||
|
||||
protected function get_model(): ActivateCodeModel
|
||||
{
|
||||
if (isset($this->__model))
|
||||
return $this->__model;
|
||||
|
||||
$this->__model = ActivateCodeModel::get(array(
|
||||
[
|
||||
'name' => 'obj.activate_slug',
|
||||
'type' => '=',
|
||||
'value' => $this->url_context['url_1'] // slug
|
||||
]
|
||||
));
|
||||
|
||||
if(empty($this->__model))
|
||||
throw new HttpExceptions\NotFound404('The account activation link is invalid');
|
||||
|
||||
return $this->__model;
|
||||
}
|
||||
public function get_context_data() {
|
||||
$context = parent::get_context_data();
|
||||
|
||||
$activation_model = $this->get_model();
|
||||
$user = UserModel::get(array(
|
||||
[
|
||||
'name' => 'obj.id',
|
||||
'type' => '=',
|
||||
'value' => $activation_model->field_user_id
|
||||
]
|
||||
));
|
||||
if($user->field_is_active)
|
||||
throw new HttpExceptions\BadRequest400("The account already activated");
|
||||
|
||||
$user->field_is_active = true;
|
||||
$user->save();
|
||||
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
37
apps/Users/Controllers/EditContactInfoController.php
Normal file
37
apps/Users/Controllers/EditContactInfoController.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Includes\Model\ValidationError;
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
|
||||
class EditContactInfoController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/sect_information/edit_information.php';
|
||||
protected $allow_role = 'user';
|
||||
|
||||
protected function post()
|
||||
{
|
||||
$username = $_POST['username'] ?? '';
|
||||
$fname = $_POST['fname'] ?? '';
|
||||
$lname = $_POST['lname'] ?? '';
|
||||
|
||||
$curr_user = CURRENT_USER;
|
||||
|
||||
$curr_user->field_username = $username;
|
||||
$curr_user->field_fname = $fname;
|
||||
$curr_user->field_lname = $lname;
|
||||
|
||||
try {
|
||||
$curr_user->save();
|
||||
redirect_to(get_permalink('users:profile'));
|
||||
} catch (ValidationError $ex) {
|
||||
$this->context['error_form'] = $ex;
|
||||
return;
|
||||
} catch (\Exception $ex) {
|
||||
$this->context['error_message'] = 'Unexpected error';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
apps/Users/Controllers/EditEmailController.php
Normal file
37
apps/Users/Controllers/EditEmailController.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Includes\Model\ValidationError;
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class EditEmailController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/sect_information/edit_email.php';
|
||||
protected $allow_role = 'user';
|
||||
|
||||
protected function post()
|
||||
{
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
$curr_user = CURRENT_USER;
|
||||
|
||||
$curr_user->field_email = $email;
|
||||
|
||||
try {
|
||||
if ($curr_user->password_verify($password)) {
|
||||
$curr_user->save();
|
||||
redirect_to(get_permalink('users:profile'));
|
||||
} else {
|
||||
$this->context['error_message'] = 'Invalid password';
|
||||
}
|
||||
} catch (ValidationError $ex) {
|
||||
$this->context['error_form'] = $ex;
|
||||
return;
|
||||
} catch (\Exception $ex) {
|
||||
$this->context['error_message'] = 'Unexpected error';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
apps/Users/Controllers/EditPasswordController.php
Normal file
48
apps/Users/Controllers/EditPasswordController.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Apps\Users\Models\UserModel;
|
||||
use Lycoreco\Includes\Model\ValidationError;
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class EditPasswordController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/sect_information/edit_password.php';
|
||||
protected $allow_role = 'user';
|
||||
|
||||
protected function post()
|
||||
{
|
||||
|
||||
$old_password = $_POST['old_password'] ?? '';
|
||||
$new_password = $_POST['new_password'] ?? '';
|
||||
$repeat_password = $_POST['repeat_password'] ?? '';
|
||||
|
||||
// Check password
|
||||
if (!CURRENT_USER->password_verify($old_password)) {
|
||||
$this->context['error_form'] = new ValidationError(['You entered your old password incorrectly.']);
|
||||
return;
|
||||
}
|
||||
if ($new_password != $repeat_password) {
|
||||
$this->context['error_form'] = new ValidationError(['Passwords don\'t match']);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$curr_user = CURRENT_USER;
|
||||
if (UserModel::valid_password($new_password) !== true) {
|
||||
$errors = UserModel::valid_password($new_password);
|
||||
throw new ValidationError($errors);
|
||||
}
|
||||
// After validations, save new password
|
||||
$curr_user->field_password = UserModel::password_hash($new_password);
|
||||
$curr_user->save();
|
||||
redirect_to(get_permalink('users:profile'));
|
||||
} catch (ValidationError $ex) {
|
||||
$this->context['error_form'] = $ex;
|
||||
return;
|
||||
} catch (\Exception $ex) {
|
||||
$this->context['error_message'] = 'Unexpected error';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
apps/Users/Controllers/ForgotPasswordController.php
Normal file
82
apps/Users/Controllers/ForgotPasswordController.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Apps\Users\Models\UserModel;
|
||||
use Lycoreco\Apps\Users\Models\RecoveryPassModel;
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class ForgotPasswordController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/forgot_password.php';
|
||||
|
||||
protected function distinct()
|
||||
{
|
||||
// If current user is authorized, redirect to homepage
|
||||
if (!empty(CURRENT_USER)) {
|
||||
$home = get_permalink('index:home');
|
||||
redirect_to($home);
|
||||
}
|
||||
}
|
||||
protected function post()
|
||||
{
|
||||
require_once APPS_PATH . '/Users/functions.php';
|
||||
|
||||
$email = $_POST['email'];
|
||||
if (!isset($email)) {
|
||||
$this->context['error_message'] = 'Enter your e-mail address';
|
||||
return;
|
||||
}
|
||||
$user = UserModel::get(array(
|
||||
[
|
||||
'name' => 'obj.email',
|
||||
'type' => '=',
|
||||
'value' => $email
|
||||
]
|
||||
));
|
||||
// If user is exists
|
||||
if ($user) {
|
||||
try {
|
||||
// Check if there was a previous request.
|
||||
if (!RecoveryPassModel::is_cooldown_available($user->get_id())) {
|
||||
$this->context['error_message'] = 'You have already sent a request recently. Check your email or wait ' . RecoveryPassModel::get_cooldown_modifier() . '.';
|
||||
return;
|
||||
}
|
||||
|
||||
// Create recovery code
|
||||
$recovery_model = new RecoveryPassModel(array(
|
||||
'user_id' => $user->get_id(),
|
||||
'recovery_slug' => generate_uuid()
|
||||
));
|
||||
$recovery_model->save();
|
||||
|
||||
$link_to_recovery = get_permalink('users:reset', [$recovery_model->field_recovery_slug]);
|
||||
|
||||
|
||||
// E-mail template for recovery
|
||||
$body = get_recovery_email_template(
|
||||
$user->get_public_name(),
|
||||
$link_to_recovery
|
||||
);
|
||||
|
||||
$altBody = get_recovery_email_alt_template(
|
||||
$user->get_public_name(),
|
||||
$link_to_recovery
|
||||
);;
|
||||
|
||||
send_email(
|
||||
'Reset Password',
|
||||
$body,
|
||||
$altBody,
|
||||
$user->field_email,
|
||||
$user->get_public_name()
|
||||
);
|
||||
$this->context['success_message'] = 'The email was sent successfully';
|
||||
} catch (\Exception $ex) {
|
||||
$this->context['error_message'] = 'Unknown error. Please try again.';
|
||||
}
|
||||
} else {
|
||||
$this->context['error_message'] = 'The user with this e-mail was not found.';
|
||||
}
|
||||
}
|
||||
}
|
||||
87
apps/Users/Controllers/LoginController.php
Normal file
87
apps/Users/Controllers/LoginController.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Apps\Users\Models\{
|
||||
UserModel,
|
||||
ActivateCodeModel,
|
||||
BanlistModel
|
||||
};
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class LoginController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/login.php';
|
||||
|
||||
protected function post()
|
||||
{
|
||||
$username = $_POST['username'] ?? null;
|
||||
$password = $_POST['password'] ?? null;
|
||||
|
||||
if (!empty($username) && !empty($password)) {
|
||||
// Try to find user with $username
|
||||
$user = UserModel::get(
|
||||
array(
|
||||
[
|
||||
'name' => 'obj.username',
|
||||
'type' => '=',
|
||||
'value' => $username
|
||||
],
|
||||
[
|
||||
'name' => 'obj.email',
|
||||
'type' => '=',
|
||||
'value' => $username
|
||||
]
|
||||
),
|
||||
array(),
|
||||
'OR'
|
||||
);
|
||||
|
||||
// If user not found
|
||||
if (empty($user)) {
|
||||
$this->context['error_message'] = 'User not found';
|
||||
return;
|
||||
}
|
||||
// If user is not activate
|
||||
if (!$user->field_is_active) {
|
||||
ActivateCodeModel::send_activation($user);
|
||||
|
||||
$this->context['error_message'] = 'Your account has not been activated. An activation email has been sent to your email address.';
|
||||
return;
|
||||
}
|
||||
if ($user->is_banned()) {
|
||||
$this->context['error_message'] = 'Your account has been banned. ';
|
||||
$ban = BanlistModel::get(array(
|
||||
[
|
||||
'name' => 'obj.user_id',
|
||||
'type' => '=',
|
||||
'value' => $user->get_id()
|
||||
]
|
||||
), ['-obj.end_at']);
|
||||
$ban_comment = "Reason: $ban->field_reason. Until to $ban->field_end_at";
|
||||
$this->context['error_message'] .= $ban_comment;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check password is correct or not
|
||||
$is_correct_pass = $user->password_verify($password);
|
||||
if ($is_correct_pass === true) {
|
||||
set_auth_user($user->get_id());
|
||||
redirect_to(get_permalink('index:home'));
|
||||
} else {
|
||||
$this->context['error_message'] = 'You entered an incorrect password.';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function distinct()
|
||||
{
|
||||
// If current user is authorized, redirect to homepage
|
||||
if (!empty(CURRENT_USER)) {
|
||||
$home = get_permalink('index:home');
|
||||
redirect_to($home);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
apps/Users/Controllers/LogoutController.php
Normal file
16
apps/Users/Controllers/LogoutController.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class LogoutController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/logout.php';
|
||||
|
||||
protected function distinct()
|
||||
{
|
||||
logout();
|
||||
redirect_to(get_permalink('index:home'));
|
||||
}
|
||||
}
|
||||
11
apps/Users/Controllers/ProfileController.php
Normal file
11
apps/Users/Controllers/ProfileController.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class ProfileController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/profile.php';
|
||||
protected $allow_role = 'user';
|
||||
}
|
||||
62
apps/Users/Controllers/RegisterController.php
Normal file
62
apps/Users/Controllers/RegisterController.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Apps\Users\Models\UserModel;
|
||||
use Lycoreco\Includes\BaseController;
|
||||
use Lycoreco\Includes\Model\ValidationError;
|
||||
|
||||
class RegisterController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/register.php';
|
||||
|
||||
protected function distinct()
|
||||
{
|
||||
// If current user is authorized, redirect to homepage
|
||||
if (!empty(CURRENT_USER)) {
|
||||
$home = get_permalink('index:home');
|
||||
redirect_to($home);
|
||||
}
|
||||
}
|
||||
|
||||
protected function post()
|
||||
{
|
||||
$username = $_POST['username'] ?? null;
|
||||
$email = $_POST['email'] ?? null;
|
||||
$pass = $_POST['password'] ?? null;
|
||||
$repeat = $_POST['repeat'] ?? null;
|
||||
|
||||
$fname = $_POST['fname'] ?? '';
|
||||
$lname = $_POST['lname'] ?? '';
|
||||
|
||||
// If password is not valid
|
||||
if (UserModel::valid_password($pass) !== true) {
|
||||
$context['error_form'] = new ValidationError(UserModel::valid_password($pass));
|
||||
return;
|
||||
}
|
||||
if ($pass != $repeat) {
|
||||
$context['error_form'] = new ValidationError(["Passwords don't match"]);
|
||||
return;
|
||||
}
|
||||
$pass_hash = UserModel::password_hash($pass);
|
||||
|
||||
$new_user = new UserModel(array(
|
||||
'username' => $username,
|
||||
'email' => $email,
|
||||
'password' => $pass_hash,
|
||||
'fname' => $fname,
|
||||
'lname' => $lname
|
||||
));
|
||||
try {
|
||||
$new_user->save();
|
||||
set_auth_user($new_user->get_id());
|
||||
redirect_to(get_permalink('index:home'));
|
||||
} catch (ValidationError $ex) {
|
||||
$this->context['error_form'] = $ex;
|
||||
return;
|
||||
} catch (\Exception $ex) {
|
||||
$this->context['error_message'] = 'Unexpected error';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
apps/Users/Controllers/ResetPasswordController.php
Normal file
99
apps/Users/Controllers/ResetPasswordController.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Controllers;
|
||||
|
||||
use Lycoreco\Apps\Users\Models\UserModel;
|
||||
use Lycoreco\Apps\Users\Models\RecoveryPassModel;
|
||||
use Lycoreco\Includes\Model\ValidationError;
|
||||
use Lycoreco\Includes\BaseController;
|
||||
|
||||
class ResetPasswordController extends BaseController
|
||||
{
|
||||
protected $template_name = APPS_PATH . '/Users/Templates/reset_password.php';
|
||||
|
||||
protected function get_model()
|
||||
{
|
||||
if (isset($this->__model))
|
||||
return $this->__model;
|
||||
|
||||
$this->__model = RecoveryPassModel::get(array(
|
||||
[
|
||||
'name' => 'obj.recovery_slug',
|
||||
'type' => '=',
|
||||
'value' => $this->url_context['url_1']
|
||||
]
|
||||
));
|
||||
return $this->__model;
|
||||
}
|
||||
|
||||
protected function distinct()
|
||||
{
|
||||
$recovery_model = $this->get_model();
|
||||
|
||||
// Check that recovery_model is available or not
|
||||
if ($recovery_model) {
|
||||
if (!$recovery_model->is_available())
|
||||
$this->context['not_available'] = 'The link is no longer valid. Please try again';
|
||||
} else {
|
||||
$this->context['not_available'] = 'The link does not exist';
|
||||
}
|
||||
}
|
||||
protected function post()
|
||||
{
|
||||
require_once APPS_PATH . '/Users/functions.php';
|
||||
|
||||
$pass = $_POST['password'] ?? null;
|
||||
$repeat = $_POST['repeat'] ?? null;
|
||||
|
||||
$recovery_model = $this->get_model();
|
||||
|
||||
// Validate password
|
||||
if (empty($pass) || empty($repeat)) {
|
||||
$this->context['error_form'] = new ValidationError(["You did not provide a password in the fields."]);
|
||||
return;
|
||||
}
|
||||
if ($pass != $repeat) {
|
||||
$this->context['error_form'] = new ValidationError(["Passwords don't match"]);
|
||||
return;
|
||||
}
|
||||
if (UserModel::valid_password($pass) !== true) {
|
||||
$this->context['error_form'] = new ValidationError(UserModel::valid_password($pass));
|
||||
return;
|
||||
}
|
||||
if (!$recovery_model->is_available())
|
||||
return;
|
||||
|
||||
// Set new password
|
||||
$pass_hash = UserModel::password_hash($pass);
|
||||
|
||||
if ($recovery_model) {
|
||||
$user = UserModel::get(array(
|
||||
[
|
||||
'name' => 'obj.id',
|
||||
'type' => '=',
|
||||
'value' => $recovery_model->field_user_id
|
||||
]
|
||||
));
|
||||
|
||||
// Save new password
|
||||
$user->field_password = $pass_hash;
|
||||
$user->save();
|
||||
|
||||
// Set RecoveryModel as used
|
||||
$recovery_model->field_is_used = true;
|
||||
$recovery_model->save();
|
||||
|
||||
// Send message to email
|
||||
send_email(
|
||||
'Password Updated',
|
||||
get_reset_completed_email_template($user->get_public_name()),
|
||||
get_reset_completed_email_alt_template($user->get_public_name()),
|
||||
$user->field_email,
|
||||
$user->get_public_name()
|
||||
);
|
||||
|
||||
// Show message and do form as unavailable
|
||||
$this->context['not_available'] = 'Your password has been successfully changed';
|
||||
}
|
||||
}
|
||||
}
|
||||
72
apps/Users/Models/ActivateCodeModel.php
Normal file
72
apps/Users/Models/ActivateCodeModel.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace Lycoreco\Apps\Users\Models;
|
||||
|
||||
use Lycoreco\Includes\Model\BaseModel;
|
||||
|
||||
class ActivateCodeModel extends BaseModel
|
||||
{
|
||||
public $field_user_id;
|
||||
public $field_activate_slug;
|
||||
public $field_created_at = null;
|
||||
static protected $table_name = 'user_activate_codes';
|
||||
static protected $table_fields = [
|
||||
'id' => 'int',
|
||||
'user_id' => 'int',
|
||||
'activate_slug' => 'string',
|
||||
'created_at' => 'DateTime'
|
||||
];
|
||||
public static function init_table()
|
||||
{
|
||||
$result = db_query('CREATE TABLE ' . static::$table_name . ' (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
user_id INT NOT NULL,
|
||||
activate_slug VARCHAR(255) UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);');
|
||||
return $result;
|
||||
}
|
||||
public static function send_activation(UserModel $user)
|
||||
{
|
||||
require_once APPS_PATH . '/Users/functions.php';
|
||||
|
||||
if ($user->field_is_active)
|
||||
return;
|
||||
|
||||
$activation_code = ActivateCodeModel::get([
|
||||
[
|
||||
'name' => 'obj.user_id',
|
||||
'type' => '=',
|
||||
'value' => $user->get_id()
|
||||
]
|
||||
]);
|
||||
|
||||
// If activation link does not exist - create new
|
||||
if(!$activation_code) {
|
||||
$activation_code = new ActivateCodeModel(array(
|
||||
'user_id' => $user->get_id(),
|
||||
'activate_slug' => generate_uuid()
|
||||
));
|
||||
$activation_code->save();
|
||||
}
|
||||
$link_to_activate = get_permalink('users:activate', [
|
||||
$activation_code->field_activate_slug
|
||||
]);
|
||||
|
||||
send_email(
|
||||
"Activation account",
|
||||
get_activation_email_template(
|
||||
$user->field_username,
|
||||
$link_to_activate),
|
||||
|
||||
get_activation_email_alt_template(
|
||||
$user->field_username,
|
||||
$link_to_activate),
|
||||
|
||||
$user->field_email,
|
||||
$user->get_public_name()
|
||||
);
|
||||
}
|
||||
}
|
||||
68
apps/Users/Models/BanlistModel.php
Normal file
68
apps/Users/Models/BanlistModel.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace Lycoreco\Apps\Users\Models;
|
||||
|
||||
use Lycoreco\Includes\Model\{
|
||||
BaseModel,
|
||||
CustomDateTime
|
||||
};
|
||||
|
||||
use function PHPSTORM_META\map;
|
||||
|
||||
class BanlistModel extends BaseModel
|
||||
{
|
||||
public $field_user_id;
|
||||
public $field_reason;
|
||||
public $field_created_at = null;
|
||||
public $field_end_at = null;
|
||||
static protected $table_name = 'user_banlist';
|
||||
static protected $table_fields = [
|
||||
'id' => 'int',
|
||||
'user_id' => 'int',
|
||||
'reason' => 'string',
|
||||
'created_at' => 'DateTime',
|
||||
'end_at' => 'DateTime'
|
||||
];
|
||||
static protected $search_fields = ['obj.reason'];
|
||||
|
||||
public static function init_table()
|
||||
{
|
||||
$result = db_query('CREATE TABLE ' . static::$table_name . ' (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
user_id INT NOT NULL,
|
||||
reason VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
end_at TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);');
|
||||
return $result;
|
||||
}
|
||||
public function is_active()
|
||||
{
|
||||
return new CustomDateTime() <= $this->field_end_at;
|
||||
}
|
||||
|
||||
public function send_email_notification()
|
||||
{
|
||||
require_once APPS_PATH . '/Users/functions.php';
|
||||
|
||||
$user = UserModel::get(array(
|
||||
[
|
||||
'name' => 'obj.id',
|
||||
'type' => '=',
|
||||
'value' => $this->field_user_id
|
||||
]
|
||||
));
|
||||
|
||||
send_email(
|
||||
"Account banned",
|
||||
get_ban_template($user->field_username, $this),
|
||||
|
||||
get_ban_alt_template($user->field_username, $this),
|
||||
|
||||
$user->field_email,
|
||||
$user->get_public_name()
|
||||
);
|
||||
}
|
||||
}
|
||||
89
apps/Users/Models/RecoveryPassModel.php
Normal file
89
apps/Users/Models/RecoveryPassModel.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Models;
|
||||
|
||||
use Lycoreco\Includes\Model\BaseModel;
|
||||
|
||||
class RecoveryPassModel extends BaseModel
|
||||
{
|
||||
public $field_user_id;
|
||||
public $field_recovery_slug;
|
||||
public $field_created_at = null;
|
||||
public $field_is_used = false;
|
||||
|
||||
static protected $cooldownModifier = '10 minutes';
|
||||
static protected $table_name = 'recovery_password';
|
||||
static protected $table_fields = [
|
||||
'id' => 'int',
|
||||
'user_id' => 'int',
|
||||
'recovery_slug' => 'string',
|
||||
'created_at' => 'DateTime',
|
||||
'is_used' => 'bool'
|
||||
];
|
||||
|
||||
public function is_available()
|
||||
{
|
||||
// Recovery is already used
|
||||
if ($this->field_is_used)
|
||||
return false;
|
||||
|
||||
// Invalid after 10 minutes
|
||||
$datetime_recovery = new \DateTime($this->field_created_at);
|
||||
$datetime_recovery->modify('+' . static::$cooldownModifier);
|
||||
|
||||
$current_datetime = new \DateTime();
|
||||
if ($datetime_recovery < $current_datetime) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public static function get_cooldown_modifier()
|
||||
{
|
||||
return static::$cooldownModifier;
|
||||
}
|
||||
/**
|
||||
* Check if the email was sent 10 minutes earlier. If so, the user needs to wait for the cooldown.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_cooldown_available($user_id)
|
||||
{
|
||||
$cooldownTime = new \DateTime();
|
||||
$cooldownTime->modify('-' . static::$cooldownModifier);
|
||||
|
||||
$lastRecoveryModel = RecoveryPassModel::get(
|
||||
array(
|
||||
[
|
||||
'name' => 'obj.created_at',
|
||||
'type' => '>=',
|
||||
'value' => $cooldownTime->format('Y-m-d H:i:s')
|
||||
],
|
||||
[
|
||||
'name' => 'obj.user_id',
|
||||
'type' => '=',
|
||||
'value' => $user_id
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
if ($lastRecoveryModel)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function init_table()
|
||||
{
|
||||
$result = db_query('CREATE TABLE ' . static::$table_name . ' (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
user_id INT NOT NULL,
|
||||
recovery_slug VARCHAR(255) UNIQUE NOT NULL,
|
||||
is_used TINYINT(1) DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);');
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
169
apps/Users/Models/UserModel.php
Normal file
169
apps/Users/Models/UserModel.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace Lycoreco\Apps\Users\Models;
|
||||
|
||||
use Lycoreco\Includes\Model\BaseModel;
|
||||
|
||||
class UserModel extends BaseModel
|
||||
{
|
||||
public $field_username;
|
||||
public $field_email;
|
||||
public $field_fname;
|
||||
public $field_lname;
|
||||
public $field_password;
|
||||
public $field_is_admin = false;
|
||||
public $field_is_active = false;
|
||||
public $field_register_at = null;
|
||||
|
||||
protected $is_banned = false;
|
||||
|
||||
static protected $additional_fields = array(
|
||||
[
|
||||
'field' => [
|
||||
'(EXISTS (
|
||||
SELECT 1 FROM user_banlist b
|
||||
WHERE b.user_id = obj.id AND b.end_at > NOW()
|
||||
)) AS is_banned'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
static protected $table_name = 'users';
|
||||
static protected $table_fields = [
|
||||
'id' => 'int',
|
||||
'username' => 'string',
|
||||
'email' => 'string',
|
||||
'fname' => 'string',
|
||||
'lname' => 'string',
|
||||
'password' => 'string',
|
||||
'is_admin' => 'bool',
|
||||
'is_active' => 'bool',
|
||||
'register_at' => 'DateTime'
|
||||
];
|
||||
static protected $search_fields = ['obj.username', 'obj.email', 'obj.fname', 'obj.lname'];
|
||||
|
||||
public static function init_table()
|
||||
{
|
||||
$result = db_query('CREATE TABLE ' . static::$table_name . ' (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(20) UNIQUE NOT NULL,
|
||||
email VARCHAR(40) UNIQUE NOT NULL,
|
||||
fname VARCHAR(20) NOT NULL,
|
||||
lname VARCHAR(20) NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
is_admin TINYINT(1) DEFAULT 0,
|
||||
is_active TINYINT(1) DEFAULT 0,
|
||||
|
||||
register_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);');
|
||||
return $result;
|
||||
}
|
||||
public function get_role()
|
||||
{
|
||||
if ($this->field_is_admin)
|
||||
return 'Admin';
|
||||
else
|
||||
return 'User';
|
||||
}
|
||||
public function is_banned()
|
||||
{
|
||||
return (bool)$this->is_banned;
|
||||
}
|
||||
/**
|
||||
* Get public user name
|
||||
* if user has first name, we get first name
|
||||
* if user has first and last name, we get first and last name
|
||||
* if user hasn't first name, we get username
|
||||
* @return string
|
||||
*/
|
||||
public function get_public_name()
|
||||
{
|
||||
if (empty($this->field_fname))
|
||||
return $this->field_username;
|
||||
|
||||
$full_name = $this->field_fname;
|
||||
|
||||
if ($this->field_lname)
|
||||
$full_name .= " " . $this->field_lname;
|
||||
|
||||
return $full_name;
|
||||
}
|
||||
|
||||
public static function password_hash($password)
|
||||
{
|
||||
return password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
public function password_verify($password)
|
||||
{
|
||||
return password_verify($password, $this->field_password);
|
||||
}
|
||||
public static function valid_password($password)
|
||||
{
|
||||
$errors = [];
|
||||
$password_len = strlen($password);
|
||||
if ($password_len < 4 || $password_len > 10)
|
||||
$errors[] = 'The password is between 4 and 10 characters long.';
|
||||
|
||||
if (strpos($password, ' '))
|
||||
$errors[] = 'Spaces are not allowed.';
|
||||
|
||||
if (empty($errors))
|
||||
return true;
|
||||
|
||||
return $errors;
|
||||
}
|
||||
public function valid()
|
||||
{
|
||||
$errors = array();
|
||||
|
||||
// Check if exists another user with same username/email
|
||||
$exists_user = UserModel::get(
|
||||
array(
|
||||
[
|
||||
'name' => 'obj.username',
|
||||
'type' => '=',
|
||||
'value' => $this->field_username,
|
||||
],
|
||||
[
|
||||
'name' => 'obj.email',
|
||||
'type' => '=',
|
||||
'value' => $this->field_email
|
||||
]
|
||||
),
|
||||
array(),
|
||||
'OR'
|
||||
);
|
||||
if (!empty($exists_user)) {
|
||||
if ($exists_user->get_id() !== $this->get_id()) {
|
||||
if ($exists_user->field_email == $this->field_email)
|
||||
$errors[] = 'The user with this email already exists';
|
||||
|
||||
if ($exists_user->field_username == $this->field_username)
|
||||
$errors[] = 'The user with this username already exists';
|
||||
}
|
||||
}
|
||||
|
||||
// Check length
|
||||
$username_len = strlen($this->field_username);
|
||||
$email_len = strlen($this->field_username);
|
||||
$fname_len = strlen($this->field_username);
|
||||
$lname_len = strlen($this->field_username);
|
||||
|
||||
if ($username_len > 20 || $username_len < 4)
|
||||
$errors[] = 'The username must be between 4 and 20 characters long';
|
||||
|
||||
if ($email_len > 40 || $email_len < 4)
|
||||
$errors[] = 'The email must be between 4 and 40 characters long';
|
||||
|
||||
if ($fname_len > 20 || $fname_len < 3)
|
||||
$errors[] = 'The first name must be between 4 and 20 characters long';
|
||||
|
||||
if ($lname_len > 20 || $lname_len < 3)
|
||||
$errors[] = 'The last name must be between 4 and 40 characters long';
|
||||
|
||||
if (empty($errors))
|
||||
return true;
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
16
apps/Users/Templates/activation.php
Normal file
16
apps/Users/Templates/activation.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php the_header(
|
||||
'Account activation',
|
||||
'',
|
||||
'activation',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
])
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
Your account has been successfully activated. You can now log in.
|
||||
</div>
|
||||
|
||||
<?php
|
||||
the_footer();
|
||||
?>
|
||||
23
apps/Users/Templates/components/profile-nav.php
Normal file
23
apps/Users/Templates/components/profile-nav.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
use Lycoreco\Includes\Routing\Router;
|
||||
|
||||
$user_nav = array(
|
||||
'users:profile' => 'Information',
|
||||
'users:wishlist' => 'My wishlist',
|
||||
'users:orders' => 'My orders'
|
||||
);
|
||||
?>
|
||||
|
||||
<div class="profile-tabs">
|
||||
<?php foreach ($user_nav as $router_name => $title): ?>
|
||||
<?php if($router_name == Router::$current_router_name): ?>
|
||||
|
||||
<div class="btn btn-tab active"><?php echo $title ?></div>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<a href="<?php the_permalink($router_name) ?>" class="btn btn-tab"><?php echo $title ?></a>
|
||||
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
17
apps/Users/Templates/email_templates/activate_account.php
Normal file
17
apps/Users/Templates/email_templates/activate_account.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php the_email_header($preheader) ?>
|
||||
|
||||
<p>Hi <?php echo $username ?>,</p>
|
||||
|
||||
<p>Thanks for signing up for FridgeBites! To complete your registration, please confirm your email address by clicking the link below:</p>
|
||||
|
||||
<p>
|
||||
<a href="<?php echo $url_to_activate ?>"><?php echo $url_to_activate ?></a>
|
||||
</p>
|
||||
|
||||
<p>Once confirmed, you’ll get full access to your account and all the tasty features waiting for you.</p>
|
||||
|
||||
<p>If you didn’t create an account, just ignore this email.</p>
|
||||
|
||||
<p>Welcome aboard!<br>The LycoReco Team</p>
|
||||
|
||||
<?php the_email_footer() ?>
|
||||
@ -0,0 +1,12 @@
|
||||
Hi <?php echo $username ?>,
|
||||
|
||||
Thanks for signing up for FridgeBites! To complete your registration, please confirm your email address by clicking the link below:
|
||||
|
||||
<?php echo $url_to_activate ?>
|
||||
|
||||
Once confirmed, you’ll get full access to your account and all the tasty features waiting for you.
|
||||
|
||||
If you didn’t create an account, just ignore this email.
|
||||
|
||||
Welcome aboard!
|
||||
The LycoReco Team
|
||||
17
apps/Users/Templates/email_templates/ban.php
Normal file
17
apps/Users/Templates/email_templates/ban.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php the_email_header($preheader) ?>
|
||||
|
||||
<p>Hello <?php echo $username ?>,</p>
|
||||
|
||||
<p>We’re reaching out to let you know that your account on FridgeBites has been suspended due to a violation of our community guidelines.</p>
|
||||
|
||||
<p>The ban is effective immediately and prevents access to your account and all associated services.</p>
|
||||
|
||||
<p><b>Reason:</b> <?php echo $model->field_reason ?></p>
|
||||
|
||||
<p><b>End date:</b> <?php echo $model->field_end_at ?></p>
|
||||
|
||||
<p>We take community safety seriously and appreciate your understanding.</p>
|
||||
|
||||
<p>Best regards,<br>The LycoReco Team</p>
|
||||
|
||||
<?php the_email_footer() ?>
|
||||
14
apps/Users/Templates/email_templates/ban_alt.php
Normal file
14
apps/Users/Templates/email_templates/ban_alt.php
Normal file
@ -0,0 +1,14 @@
|
||||
Hello <?php echo $username ?>,
|
||||
|
||||
We’re reaching out to let you know that your account on FridgeBites has been suspended due to a violation of our community guidelines.
|
||||
|
||||
The ban is effective immediately and prevents access to your account and all associated services.
|
||||
|
||||
Reason: <?php echo $model->field_reason ?>
|
||||
|
||||
End date: <?php echo $model->field_end_at ?>
|
||||
|
||||
We take community safety seriously and appreciate your understanding.
|
||||
|
||||
Best regards,
|
||||
The LycoReco Team
|
||||
11
apps/Users/Templates/email_templates/recovery_pass.php
Normal file
11
apps/Users/Templates/email_templates/recovery_pass.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php the_email_header($preheader) ?>
|
||||
|
||||
<p>Hi <?php echo $username ?>,</p>
|
||||
<p>We received a request to reset your password. Click the button below to proceed:</p>
|
||||
<p>
|
||||
<a href="<?php echo $url_to_recovery ?>"><?php echo $url_to_recovery ?></a>
|
||||
</p>
|
||||
<p>If you did not request a password reset, you can safely ignore this email. Your password will remain unchanged.</p>
|
||||
<p>Thanks,<br>The LycoReco Team</p>
|
||||
|
||||
<?php the_email_footer() ?>
|
||||
10
apps/Users/Templates/email_templates/recovery_pass_alt.php
Normal file
10
apps/Users/Templates/email_templates/recovery_pass_alt.php
Normal file
@ -0,0 +1,10 @@
|
||||
Hi <?php echo $username ?>,
|
||||
|
||||
We received a request to reset your password. Click the button below to proceed:
|
||||
|
||||
<?php echo $url_to_recovery ?>
|
||||
|
||||
If you did not request a password reset, you can safely ignore this email. Your password will remain unchanged.
|
||||
|
||||
Thanks,
|
||||
The LycoReco Team
|
||||
@ -0,0 +1,14 @@
|
||||
<?php the_email_header($preheader) ?>
|
||||
|
||||
<p>Hi <?php echo $username ?>,</p>
|
||||
<p>We wanted to let you know that your password has been successfully updated. If you made this change, no further action is required.</p>
|
||||
|
||||
<p>If you did not request a password change, please contact our support team immediately to secure your account</p>
|
||||
|
||||
<p>For any questions or assistance, feel free to reach out to us at <a href="<?php the_permalink('contacts:form') ?>">contact form</a>.</p>
|
||||
|
||||
<p>Thank you for taking steps to keep your account secure.</p>
|
||||
|
||||
<p>Thanks,<br>The LycoReco Team</p>
|
||||
|
||||
<?php the_email_footer() ?>
|
||||
@ -0,0 +1,14 @@
|
||||
Hi <?php echo $username ?>,
|
||||
|
||||
We wanted to let you know that your password has been successfully updated. If you made this change, no further action is required.
|
||||
|
||||
If you did not request a password change, please contact our support team immediately to secure your account.
|
||||
|
||||
For any questions or assistance, feel free to reach out to us at contact form:
|
||||
|
||||
<?php the_permalink('contacts:form') ?>
|
||||
|
||||
Thank you for taking steps to keep your account secure.
|
||||
|
||||
Thanks,
|
||||
The LycoReco Team
|
||||
35
apps/Users/Templates/forgot_password.php
Normal file
35
apps/Users/Templates/forgot_password.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php the_header(
|
||||
'Password Recovery',
|
||||
'',
|
||||
'login-register',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
]) ?>
|
||||
|
||||
<div class="container">
|
||||
<div class="login__inner">
|
||||
<h1 class="p-title">Password Recovery</h1>
|
||||
<p>Enter your email to receive a secure link to reset your password. The link will be valid for a limited time.</p>
|
||||
<form class="form" method="post">
|
||||
<?php
|
||||
if(isset($context['error_message']))
|
||||
the_alert($context['error_message']);
|
||||
|
||||
if(isset($context['success_message']))
|
||||
the_alert($context['success_message'], 'success');
|
||||
?>
|
||||
<label for="field_username">E-mail <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="email" name="email" id="field_username" required>
|
||||
</div>
|
||||
|
||||
<div class="login-choice">
|
||||
<a href="<?php the_permalink('users:login') ?>">Login</a>
|
||||
<button class="btn btn-primary" type="submit">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php the_footer() ?>
|
||||
48
apps/Users/Templates/login.php
Normal file
48
apps/Users/Templates/login.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php the_header(
|
||||
'Login',
|
||||
'',
|
||||
'login-register',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
]) ?>
|
||||
|
||||
<div class="container">
|
||||
<div class="login__inner">
|
||||
<h1 class="p-title">Welcome to KeysShop</h1>
|
||||
<form class="form" method="post">
|
||||
<?php
|
||||
if(isset($context['error_message']))
|
||||
the_alert($context['error_message']);
|
||||
?>
|
||||
|
||||
<label for="field_username">Username</label>
|
||||
<div class="input">
|
||||
<input type="text" name="username" id="field_username" required>
|
||||
</div>
|
||||
|
||||
<label for="field_password">Password</label>
|
||||
<div class="input">
|
||||
<input type="password" name="password" id="field_password" required>
|
||||
</div>
|
||||
|
||||
<div class="login-choice">
|
||||
<a href="<?php the_permalink('users:forgot') ?>">Forgot password?</a>
|
||||
<button class="btn btn-primary" type="submit">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="login-text__notice">
|
||||
<h2>You don't have account?</h2>
|
||||
<p>Register now to buy game or program keys at low prices! </p>
|
||||
|
||||
<a href="<?php the_permalink('users:register') ?>" class="btn btn-primary">
|
||||
Register
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php the_footer() ?>
|
||||
1
apps/Users/Templates/logout.php
Normal file
1
apps/Users/Templates/logout.php
Normal file
@ -0,0 +1 @@
|
||||
Logout....
|
||||
70
apps/Users/Templates/profile.php
Normal file
70
apps/Users/Templates/profile.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
require_once APPS_PATH . '/Users/components.php';
|
||||
|
||||
the_header(
|
||||
get_the_safe(CURRENT_USER->field_username),
|
||||
'View and manage your personal information, update account settings, and track your activity on the Profile page. Customize your preferences and keep your details up-to-date easily.',
|
||||
'profile',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
]);
|
||||
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<?php the_user_nav() ?>
|
||||
|
||||
<h1 class="p-title">Account information</h1>
|
||||
|
||||
<div class="account-block">
|
||||
<h2>Contact information</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Username:</td>
|
||||
<td><?php the_safe(CURRENT_USER->field_username) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>First Name:</td>
|
||||
<td><?php the_safe(CURRENT_USER->field_fname ?? 'None') ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Name:</td>
|
||||
<td><?php the_safe(CURRENT_USER->field_lname ?? 'None') ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status:</td>
|
||||
<td><?php echo CURRENT_USER->get_role() ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="change-user-btns">
|
||||
<a href="<?php the_permalink('users:edit-info') ?>" class="btn">Edit info</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="account-block">
|
||||
<h2>Security</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td>E-mail:</td>
|
||||
<td><?php the_safe(CURRENT_USER->field_email) ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Password:</td>
|
||||
<td>**********</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="change-user-btns">
|
||||
<a href="<?php the_permalink('users:edit-password') ?>" class="btn">Change password</a>
|
||||
<a href="<?php the_permalink('users:edit-email') ?>" class="btn">Change e-mail</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-btn-control">
|
||||
<a href="<?php the_permalink('users:logout') ?>" class="btn btn-logout">Logout</a>
|
||||
|
||||
<?php if(CURRENT_USER->field_is_admin): ?>
|
||||
<a href="<?php the_permalink('admin:home') ?>" class="btn btn-primary">Admin panel</a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php the_footer() ?>
|
||||
81
apps/Users/Templates/register.php
Normal file
81
apps/Users/Templates/register.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php the_header(
|
||||
'Register',
|
||||
'',
|
||||
'login-register',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
]) ?>
|
||||
|
||||
<div class="container">
|
||||
<div class="login__inner">
|
||||
<h1 class="p-title">Create New Account</h1>
|
||||
<form class="form" method="post">
|
||||
<?php
|
||||
if(isset($context['error_message']))
|
||||
the_alert($context['error_message']);
|
||||
?>
|
||||
<label for="field_username">Username <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="text" name="username" id="field_username" required>
|
||||
</div>
|
||||
|
||||
<label for="field_email">E-mail <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="email" name="email" id="field_email" required>
|
||||
</div>
|
||||
|
||||
<label for="field_password">Password <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="password" name="password" id="field_password" required>
|
||||
</div>
|
||||
|
||||
<label for="field_repeat_password">Repeat password <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="password" name="repeat" id="field_repeat_password" required>
|
||||
</div>
|
||||
|
||||
<label for="field_fname">First Name</label>
|
||||
<div class="input">
|
||||
<input type="text" name="fname" id="field_fname">
|
||||
</div>
|
||||
|
||||
<label for="field_lname">Last Name</label>
|
||||
<div class="input">
|
||||
<input type="text" name="lname" id="field_lname">
|
||||
</div>
|
||||
|
||||
<div class="input-checkbox">
|
||||
<input id="terms_agree" type="checkbox" required>
|
||||
<label for="terms_agree">I Agree to the <a href="<?php the_permalink('index:terms') ?>">Terms & Conditions</a></label>
|
||||
</div>
|
||||
<div class="input-checkbox">
|
||||
<input id="privacy_agree" type="checkbox" required>
|
||||
<label for="privacy_agree">I Agree to the <a href="<?php the_permalink('index:privacy') ?>">Privacy Policy</a></label>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if(isset($context['error_form']))
|
||||
$context['error_form']->display_error();
|
||||
?>
|
||||
|
||||
<div class="login-choice">
|
||||
<span></span>
|
||||
<button class="btn btn-primary" type="submit">Create an account</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="login-text__notice">
|
||||
<h2>Do you already have an account?</h2>
|
||||
<p>Log in to your account to continue the purchase. </p>
|
||||
|
||||
<a href="<?php the_permalink('users:login') ?>" class="btn btn-primary">
|
||||
Login
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php the_footer() ?>
|
||||
50
apps/Users/Templates/reset_password.php
Normal file
50
apps/Users/Templates/reset_password.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php the_header(
|
||||
'Password Recovery',
|
||||
'',
|
||||
'login-register',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
])
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="login__inner" <?php if(isset($context['not_available'])) echo 'style="text-align: center;"'; ?>>
|
||||
<h1 class="p-title">Reset Password</h1>
|
||||
<?php if(!isset($context['not_available'])): ?>
|
||||
<p>Enter a new password for your account.</p>
|
||||
<?php else: ?>
|
||||
<p><?php echo $context['not_available'] ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if(!isset($context['not_available'])): ?>
|
||||
<form class="form" method="post">
|
||||
<?php
|
||||
if(isset($context['error_message']))
|
||||
the_alert($context['error_message']);
|
||||
?>
|
||||
<label for="field_pass">New password <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="password" name="password" id="field_pass" required>
|
||||
</div>
|
||||
|
||||
<label for="field_repeat_pass">Repeat new password <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="password" name="repeat" id="field_repeat_pass" required>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if(isset($context['error_form']))
|
||||
$context['error_form']->display_error();
|
||||
?>
|
||||
|
||||
<div class="login-choice">
|
||||
<span></span>
|
||||
<button class="btn btn-primary" type="submit">Reset password</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<?php the_footer() ?>
|
||||
41
apps/Users/Templates/sect_information/edit_email.php
Normal file
41
apps/Users/Templates/sect_information/edit_email.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php the_header(
|
||||
'Change email ' . CURRENT_USER->field_username,
|
||||
'View and manage your personal information, update account settings, and track your activity on the Profile page. Customize your preferences and keep your details up-to-date easily.',
|
||||
'profile-edit',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
]) ?>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="p-title">Edit E-mail</h1>
|
||||
|
||||
<form class="form form-edit" method="post">
|
||||
<?php
|
||||
if(isset($context['error_message']))
|
||||
the_alert($context['error_message']);
|
||||
?>
|
||||
|
||||
<label for="field_email">E-mail <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="email" name="email" id="field_email" value="<?php the_safe(CURRENT_USER->field_email) ?>" required>
|
||||
</div>
|
||||
|
||||
<label for="field_fname">Your password <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="password" name="password" id="field_password" required>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if(isset($context['error_form']))
|
||||
$context['error_form']->display_error();
|
||||
?>
|
||||
|
||||
<div class="btn-control">
|
||||
<a class="a-back" href="<?php the_permalink('users:profile') ?>">< Back</a>
|
||||
<button class="btn btn-primary" type="submit">Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php the_footer() ?>
|
||||
46
apps/Users/Templates/sect_information/edit_information.php
Normal file
46
apps/Users/Templates/sect_information/edit_information.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php the_header(
|
||||
'Edit ' . CURRENT_USER->field_username,
|
||||
'View and manage your personal information, update account settings, and track your activity on the Profile page. Customize your preferences and keep your details up-to-date easily.',
|
||||
'profile-edit',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
]) ?>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="p-title">Edit contact information</h1>
|
||||
|
||||
<form class="form form-edit" method="post">
|
||||
<?php
|
||||
if(isset($context['error_message']))
|
||||
the_alert($context['error_message']);
|
||||
?>
|
||||
|
||||
<label for="field_username">Username <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="text" name="username" id="field_username" value="<?php the_safe(CURRENT_USER->field_username) ?>" required>
|
||||
</div>
|
||||
|
||||
<label for="field_fname">First Name</label>
|
||||
<div class="input">
|
||||
<input type="text" name="fname" id="field_fname" value="<?php the_safe(CURRENT_USER->field_fname ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<label for="field_lname">Last Name</label>
|
||||
<div class="input">
|
||||
<input type="text" name="lname" id="field_lname" value="<?php the_safe(CURRENT_USER->field_lname ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if(isset($context['error_form']))
|
||||
$context['error_form']->display_error();
|
||||
?>
|
||||
|
||||
<div class="btn-control">
|
||||
<a class="a-back" href="<?php the_permalink('users:profile') ?>">< Back</a>
|
||||
<button class="btn btn-primary" type="submit">Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php the_footer() ?>
|
||||
46
apps/Users/Templates/sect_information/edit_password.php
Normal file
46
apps/Users/Templates/sect_information/edit_password.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php the_header(
|
||||
'Change password ' . CURRENT_USER->field_username,
|
||||
'View and manage your personal information, update account settings, and track your activity on the Profile page. Customize your preferences and keep your details up-to-date easily.',
|
||||
'profile-edit',
|
||||
[
|
||||
['robots', 'nofollow, noindex']
|
||||
]) ?>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="p-title">Change password</h1>
|
||||
|
||||
<form class="form form-edit" method="post">
|
||||
<?php
|
||||
if(isset($context['error_message']))
|
||||
the_alert($context['error_message']);
|
||||
?>
|
||||
|
||||
<label for="field_old_password">Old Password <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="password" name="old_password" id="field_old_password" required>
|
||||
</div>
|
||||
|
||||
<label for="field_new_password">New password <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="password" name="new_password" id="field_new_password" required>
|
||||
</div>
|
||||
|
||||
<label for="field_repeat_password">Repeat new password <span>*</span></label>
|
||||
<div class="input">
|
||||
<input type="password" name="repeat_password" id="field_repeat_password" required>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if(isset($context['error_form']))
|
||||
$context['error_form']->display_error();
|
||||
?>
|
||||
|
||||
<div class="btn-control">
|
||||
<a class="a-back" href="<?php the_permalink('users:profile') ?>">< Back</a>
|
||||
<button class="btn btn-primary" type="submit">Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php the_footer() ?>
|
||||
6
apps/Users/components.php
Normal file
6
apps/Users/components.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
function the_user_nav()
|
||||
{
|
||||
include APPS_PATH . '/Users/Templates/components/profile-nav.php';
|
||||
}
|
||||
67
apps/Users/functions.php
Normal file
67
apps/Users/functions.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
// Recovery password
|
||||
function get_recovery_email_template(string $username, string $url_to_recovery)
|
||||
{
|
||||
$preheader = 'Reset your password quickly and securely. Click the link to regain access to your account.';
|
||||
|
||||
ob_start();
|
||||
require APPS_PATH . '/Users/Templates/email_templates/recovery_pass.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function get_recovery_email_alt_template(string $username, string $url_to_recovery)
|
||||
{
|
||||
ob_start();
|
||||
require APPS_PATH . '/Users/Templates/email_templates/recovery_pass_alt.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
// Reset password completed
|
||||
function get_reset_completed_email_template(string $username)
|
||||
{
|
||||
$preheader = 'Your Password Has Been Successfully Updated.';
|
||||
|
||||
ob_start();
|
||||
require APPS_PATH . '/Users/Templates/email_templates/reset_password_compl.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
function get_reset_completed_email_alt_template(string $username)
|
||||
{
|
||||
ob_start();
|
||||
require APPS_PATH . '/Users/Templates/email_templates/reset_password_compl_alt.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
// Activation account
|
||||
function get_activation_email_template(string $username, string $url_to_activate)
|
||||
{
|
||||
$preheader = 'Click the link to activate your account.';
|
||||
|
||||
ob_start();
|
||||
require APPS_PATH . '/Users/Templates/email_templates/activate_account.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
function get_activation_email_alt_template(string $username, string $url_to_activate)
|
||||
{
|
||||
ob_start();
|
||||
require APPS_PATH . '/Users/Templates/email_templates/activate_account_alt.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
// Ban
|
||||
use Lycoreco\Apps\Users\Models\BanlistModel;
|
||||
function get_ban_template(string $username, BanlistModel $model)
|
||||
{
|
||||
$preheader = 'Your account has been suspended due to a policy violation.';
|
||||
|
||||
ob_start();
|
||||
require APPS_PATH . '/Users/Templates/email_templates/ban.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
function get_ban_alt_template(string $username, BanlistModel $model)
|
||||
{
|
||||
ob_start();
|
||||
require APPS_PATH . '/Users/Templates/email_templates/ban_alt.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
26
apps/Users/urls.php
Normal file
26
apps/Users/urls.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Lycoreco\Apps\Users\Controllers;
|
||||
use Lycoreco\Includes\Routing\Path;
|
||||
|
||||
$users_urls = [
|
||||
// Login and register
|
||||
new Path('/login', new Controllers\LoginController(), 'login'),
|
||||
new Path('/logout', new Controllers\LogoutController(), 'logout'),
|
||||
new Path('/register', new Controllers\RegisterController(), 'register'),
|
||||
|
||||
// Profile
|
||||
new Path('/user', new Controllers\ProfileController(), 'profile'),
|
||||
|
||||
// Edit information
|
||||
new Path('/user/edit/information', new Controllers\EditContactInfoController(), 'edit-info'),
|
||||
new Path('/user/edit/email', new Controllers\EditEmailController(), 'edit-email'),
|
||||
new Path('/user/edit/password', new Controllers\EditPasswordController(), 'edit-password'),
|
||||
|
||||
// Reset password
|
||||
new Path('/forgot-password', new Controllers\ForgotPasswordController(), 'forgot'),
|
||||
new Path('/reset-password/[:string]', new Controllers\ResetPasswordController(), 'reset'),
|
||||
|
||||
// Activation
|
||||
new Path('/activate-account/[:string]', new Controllers\ActivationController(), 'activate')
|
||||
];
|
||||
219
assets/css/admin.css
Normal file
219
assets/css/admin.css
Normal file
@ -0,0 +1,219 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.wrapper-admin {
|
||||
display: flex;
|
||||
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
.wrapper-admin__content {
|
||||
width: 100%;
|
||||
}
|
||||
.header-admin {
|
||||
background: var(--dark-block-background);
|
||||
height: 64px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 15px 10px;
|
||||
}
|
||||
.header-admin__control {
|
||||
display: flex;
|
||||
}
|
||||
.header-admin__control .username {
|
||||
color: var(--h-color);
|
||||
font-family: var(--font-family-header);
|
||||
margin-right: 10px;
|
||||
}
|
||||
.header-admin__control .username span {
|
||||
color: var(--link-color);
|
||||
}
|
||||
.header-admin__control .links a {
|
||||
color: var(--h-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
.admin-sidebar {
|
||||
width: 100%;
|
||||
max-width: 314px;
|
||||
flex-shrink: 0;
|
||||
background: var(--block-background);
|
||||
}
|
||||
.admin-sidebar .btn {
|
||||
display: block;
|
||||
margin: 20px 20px 20px 20px;
|
||||
}
|
||||
.admin-sidebar__list {
|
||||
list-style: none;
|
||||
}
|
||||
.admin-sidebar__list a {
|
||||
display: block;
|
||||
padding: 10px 20px;
|
||||
color: var(--h-color);
|
||||
font-size: 20px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.admin-sidebar__list a:hover,
|
||||
.admin-sidebar__list a.active {
|
||||
background: #ffffff2e;
|
||||
}
|
||||
.admin-sidebar__list a i {
|
||||
width: 34px;
|
||||
}
|
||||
.admin-sidebar__list a span {
|
||||
background: #f00;
|
||||
padding: 2px 7px;
|
||||
font-size: 15px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
.admin-container {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
padding: 22px 30px;
|
||||
}
|
||||
.admin-container h2 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.admin-container section {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
#dashboard-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
.dashboard-stats__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
color: var(--h-color);
|
||||
background: var(--block-background);
|
||||
border-radius: 5px;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
.dashboard-stats__item .icon {
|
||||
font-size: 36px;
|
||||
margin-right: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dashboard-stats__item .value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.dashboard-stats__item.top-sales .icon {
|
||||
color: #FF7B00;
|
||||
}
|
||||
.dashboard-stats__item.new-users .icon {
|
||||
color: #00A3E8;
|
||||
}
|
||||
.dashboard-stats__item.orders .icon {
|
||||
color: #BA00E8;
|
||||
}
|
||||
.dashboard-stats__item.profit .icon {
|
||||
color: #00E842;
|
||||
}
|
||||
|
||||
#quicktools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 14px 22px;
|
||||
background: var(--block-background);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.admin-table {
|
||||
color: var(--h-color);
|
||||
|
||||
margin-bottom: 15px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.admin-table thead {
|
||||
background: var(--block-background);
|
||||
}
|
||||
.admin-table td,
|
||||
.admin-table th {
|
||||
padding: 15px 10px;
|
||||
}
|
||||
.admin-block__content .admin-block__table {
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.admin-table tbody tr:nth-child(odd),
|
||||
.admin-block__table .row:nth-child(odd) {
|
||||
background: #9f9f9f;
|
||||
}
|
||||
.admin-table tbody tr:nth-child(even),
|
||||
.admin-block__table .row:nth-child(even) {
|
||||
background: #767676;
|
||||
}
|
||||
.admin-block__table {
|
||||
color: var(--h-color);
|
||||
}
|
||||
.admin-block__table .row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.admin-single {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 320px;
|
||||
gap: 20px;
|
||||
}
|
||||
.admin-block {
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--block-background);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.admin-block__content {
|
||||
padding: 10px;
|
||||
}
|
||||
.admin-block__title {
|
||||
font-size: 18px;
|
||||
font-family: var(--font-family-header);
|
||||
background: var(--block-background);
|
||||
padding: 10px 10px;
|
||||
color: var(--h-color);
|
||||
}
|
||||
.admin-block__table .row {
|
||||
padding: 10px;
|
||||
}
|
||||
.admin-container.delete {
|
||||
text-align: center;
|
||||
}
|
||||
.admin-container.delete .meta-text {
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.admin-container.delete .btn-control {
|
||||
justify-content: space-around;
|
||||
}
|
||||
.order-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px !important;
|
||||
font-size: 18px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.order-content .admin-block__table {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.order-stat span:first-child {
|
||||
color: var(--text-color);
|
||||
font-weight: 400;
|
||||
font-family: var(--font-family-header);
|
||||
}
|
||||
.order-stat span:last-child {
|
||||
color: var(--h-color);
|
||||
}
|
||||
94
assets/css/reset.css
Normal file
94
assets/css/reset.css
Normal file
@ -0,0 +1,94 @@
|
||||
html, body, div, span, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
abbr, address, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, samp,
|
||||
small, strong, sub, sup, var,
|
||||
b, i,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin:0;
|
||||
padding:0;
|
||||
border:0;
|
||||
outline:0;
|
||||
font-size:100%;
|
||||
vertical-align:baseline;
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height:1;
|
||||
}
|
||||
|
||||
article,aside,details,figcaption,figure,
|
||||
footer,header,hgroup,menu,nav,section {
|
||||
display:block;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style:none;
|
||||
}
|
||||
|
||||
blockquote, q {
|
||||
quotes:none;
|
||||
}
|
||||
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content:'';
|
||||
content:none;
|
||||
}
|
||||
|
||||
a {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-size:100%;
|
||||
vertical-align:baseline;
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
/* change colours to suit your needs */
|
||||
ins {
|
||||
background-color:#ff9;
|
||||
color:#000;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
/* change colours to suit your needs */
|
||||
mark {
|
||||
background-color:#ff9;
|
||||
color:#000;
|
||||
font-style:italic;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
abbr[title], dfn[title] {
|
||||
border-bottom:1px dotted;
|
||||
cursor:help;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse:collapse;
|
||||
border-spacing:0;
|
||||
}
|
||||
|
||||
/* change border colour to suit your needs */
|
||||
hr {
|
||||
display:block;
|
||||
height:1px;
|
||||
border:0;
|
||||
border-top:1px solid #cccccc;
|
||||
margin:1em 0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
input, select {
|
||||
vertical-align:middle;
|
||||
}
|
||||
0
assets/css/style.css
Normal file
0
assets/css/style.css
Normal file
165
assets/fontawesome-free-6.6.0-web/LICENSE.txt
Normal file
165
assets/fontawesome-free-6.6.0-web/LICENSE.txt
Normal file
@ -0,0 +1,165 @@
|
||||
Fonticons, Inc. (https://fontawesome.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Font Awesome Free License
|
||||
|
||||
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||
commercial projects, open source projects, or really almost whatever you want.
|
||||
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
The Font Awesome Free download is licensed under a Creative Commons
|
||||
Attribution 4.0 International License and applies to all icons packaged
|
||||
as SVG and JS file types.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Fonts: SIL OFL 1.1 License
|
||||
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
Copyright (c) 2024 Fonticons, Inc. (https://fontawesome.com)
|
||||
with Reserved Font Name: "Font Awesome".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
SIL OPEN FONT LICENSE
|
||||
Version 1.1 - 26 February 2007
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting — in part or in whole — any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
Copyright 2024 Fonticons, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Attribution
|
||||
|
||||
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||
Awesome Free files already contain embedded comments with sufficient
|
||||
attribution, so you shouldn't need to do anything additional when using these
|
||||
files normally.
|
||||
|
||||
We've kept attribution comments terse, so we ask that you do not actively work
|
||||
to remove them from files, especially code. They're a great way for folks to
|
||||
learn about Font Awesome.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Brand Icons
|
||||
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
||||
7876
assets/fontawesome-free-6.6.0-web/css/all.css
Normal file
7876
assets/fontawesome-free-6.6.0-web/css/all.css
Normal file
File diff suppressed because it is too large
Load Diff
9
assets/fontawesome-free-6.6.0-web/css/all.min.css
vendored
Normal file
9
assets/fontawesome-free-6.6.0-web/css/all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1600
assets/fontawesome-free-6.6.0-web/css/brands.css
Normal file
1600
assets/fontawesome-free-6.6.0-web/css/brands.css
Normal file
File diff suppressed because it is too large
Load Diff
6
assets/fontawesome-free-6.6.0-web/css/brands.min.css
vendored
Normal file
6
assets/fontawesome-free-6.6.0-web/css/brands.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6215
assets/fontawesome-free-6.6.0-web/css/fontawesome.css
vendored
Normal file
6215
assets/fontawesome-free-6.6.0-web/css/fontawesome.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
assets/fontawesome-free-6.6.0-web/css/fontawesome.min.css
vendored
Normal file
9
assets/fontawesome-free-6.6.0-web/css/fontawesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
19
assets/fontawesome-free-6.6.0-web/css/regular.css
Normal file
19
assets/fontawesome-free-6.6.0-web/css/regular.css
Normal file
@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Free';
|
||||
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }
|
||||
|
||||
.far,
|
||||
.fa-regular {
|
||||
font-weight: 400; }
|
||||
6
assets/fontawesome-free-6.6.0-web/css/regular.min.css
vendored
Normal file
6
assets/fontawesome-free-6.6.0-web/css/regular.min.css
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}
|
||||
19
assets/fontawesome-free-6.6.0-web/css/solid.css
Normal file
19
assets/fontawesome-free-6.6.0-web/css/solid.css
Normal file
@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-style-family-classic: 'Font Awesome 6 Free';
|
||||
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
||||
|
||||
.fas,
|
||||
.fa-solid {
|
||||
font-weight: 900; }
|
||||
6
assets/fontawesome-free-6.6.0-web/css/solid.min.css
vendored
Normal file
6
assets/fontawesome-free-6.6.0-web/css/solid.min.css
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}
|
||||
459
assets/fontawesome-free-6.6.0-web/css/svg-with-js.css
Normal file
459
assets/fontawesome-free-6.6.0-web/css/svg-with-js.css
Normal file
@ -0,0 +1,459 @@
|
||||
/*!
|
||||
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
:root, :host {
|
||||
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free';
|
||||
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free';
|
||||
--fa-font-light: normal 300 1em/1 'Font Awesome 6 Pro';
|
||||
--fa-font-thin: normal 100 1em/1 'Font Awesome 6 Pro';
|
||||
--fa-font-duotone: normal 900 1em/1 'Font Awesome 6 Duotone';
|
||||
--fa-font-brands: normal 400 1em/1 'Font Awesome 6 Brands';
|
||||
--fa-font-sharp-solid: normal 900 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-regular: normal 400 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-light: normal 300 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-thin: normal 100 1em/1 'Font Awesome 6 Sharp';
|
||||
--fa-font-sharp-duotone-solid: normal 900 1em/1 'Font Awesome 6 Sharp Duotone'; }
|
||||
|
||||
svg:not(:root).svg-inline--fa, svg:not(:host).svg-inline--fa {
|
||||
overflow: visible;
|
||||
box-sizing: content-box; }
|
||||
|
||||
.svg-inline--fa {
|
||||
display: var(--fa-display, inline-block);
|
||||
height: 1em;
|
||||
overflow: visible;
|
||||
vertical-align: -.125em; }
|
||||
.svg-inline--fa.fa-2xs {
|
||||
vertical-align: 0.1em; }
|
||||
.svg-inline--fa.fa-xs {
|
||||
vertical-align: 0em; }
|
||||
.svg-inline--fa.fa-sm {
|
||||
vertical-align: -0.07143em; }
|
||||
.svg-inline--fa.fa-lg {
|
||||
vertical-align: -0.2em; }
|
||||
.svg-inline--fa.fa-xl {
|
||||
vertical-align: -0.25em; }
|
||||
.svg-inline--fa.fa-2xl {
|
||||
vertical-align: -0.3125em; }
|
||||
.svg-inline--fa.fa-pull-left {
|
||||
margin-right: var(--fa-pull-margin, 0.3em);
|
||||
width: auto; }
|
||||
.svg-inline--fa.fa-pull-right {
|
||||
margin-left: var(--fa-pull-margin, 0.3em);
|
||||
width: auto; }
|
||||
.svg-inline--fa.fa-li {
|
||||
width: var(--fa-li-width, 2em);
|
||||
top: 0.25em; }
|
||||
.svg-inline--fa.fa-fw {
|
||||
width: var(--fa-fw-width, 1.25em); }
|
||||
|
||||
.fa-layers svg.svg-inline--fa {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0; }
|
||||
|
||||
.fa-layers-text, .fa-layers-counter {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
text-align: center; }
|
||||
|
||||
.fa-layers {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
vertical-align: -.125em;
|
||||
width: 1em; }
|
||||
.fa-layers svg.svg-inline--fa {
|
||||
transform-origin: center center; }
|
||||
|
||||
.fa-layers-text {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transform-origin: center center; }
|
||||
|
||||
.fa-layers-counter {
|
||||
background-color: var(--fa-counter-background-color, #ff253a);
|
||||
border-radius: var(--fa-counter-border-radius, 1em);
|
||||
box-sizing: border-box;
|
||||
color: var(--fa-inverse, #fff);
|
||||
line-height: var(--fa-counter-line-height, 1);
|
||||
max-width: var(--fa-counter-max-width, 5em);
|
||||
min-width: var(--fa-counter-min-width, 1.5em);
|
||||
overflow: hidden;
|
||||
padding: var(--fa-counter-padding, 0.25em 0.5em);
|
||||
right: var(--fa-right, 0);
|
||||
text-overflow: ellipsis;
|
||||
top: var(--fa-top, 0);
|
||||
transform: scale(var(--fa-counter-scale, 0.25));
|
||||
transform-origin: top right; }
|
||||
|
||||
.fa-layers-bottom-right {
|
||||
bottom: var(--fa-bottom, 0);
|
||||
right: var(--fa-right, 0);
|
||||
top: auto;
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform-origin: bottom right; }
|
||||
|
||||
.fa-layers-bottom-left {
|
||||
bottom: var(--fa-bottom, 0);
|
||||
left: var(--fa-left, 0);
|
||||
right: auto;
|
||||
top: auto;
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform-origin: bottom left; }
|
||||
|
||||
.fa-layers-top-right {
|
||||
top: var(--fa-top, 0);
|
||||
right: var(--fa-right, 0);
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform-origin: top right; }
|
||||
|
||||
.fa-layers-top-left {
|
||||
left: var(--fa-left, 0);
|
||||
right: auto;
|
||||
top: var(--fa-top, 0);
|
||||
transform: scale(var(--fa-layers-scale, 0.25));
|
||||
transform-origin: top left; }
|
||||
|
||||
.fa-1x {
|
||||
font-size: 1em; }
|
||||
|
||||
.fa-2x {
|
||||
font-size: 2em; }
|
||||
|
||||
.fa-3x {
|
||||
font-size: 3em; }
|
||||
|
||||
.fa-4x {
|
||||
font-size: 4em; }
|
||||
|
||||
.fa-5x {
|
||||
font-size: 5em; }
|
||||
|
||||
.fa-6x {
|
||||
font-size: 6em; }
|
||||
|
||||
.fa-7x {
|
||||
font-size: 7em; }
|
||||
|
||||
.fa-8x {
|
||||
font-size: 8em; }
|
||||
|
||||
.fa-9x {
|
||||
font-size: 9em; }
|
||||
|
||||
.fa-10x {
|
||||
font-size: 10em; }
|
||||
|
||||
.fa-2xs {
|
||||
font-size: 0.625em;
|
||||
line-height: 0.1em;
|
||||
vertical-align: 0.225em; }
|
||||
|
||||
.fa-xs {
|
||||
font-size: 0.75em;
|
||||
line-height: 0.08333em;
|
||||
vertical-align: 0.125em; }
|
||||
|
||||
.fa-sm {
|
||||
font-size: 0.875em;
|
||||
line-height: 0.07143em;
|
||||
vertical-align: 0.05357em; }
|
||||
|
||||
.fa-lg {
|
||||
font-size: 1.25em;
|
||||
line-height: 0.05em;
|
||||
vertical-align: -0.075em; }
|
||||
|
||||
.fa-xl {
|
||||
font-size: 1.5em;
|
||||
line-height: 0.04167em;
|
||||
vertical-align: -0.125em; }
|
||||
|
||||
.fa-2xl {
|
||||
font-size: 2em;
|
||||
line-height: 0.03125em;
|
||||
vertical-align: -0.1875em; }
|
||||
|
||||
.fa-fw {
|
||||
text-align: center;
|
||||
width: 1.25em; }
|
||||
|
||||
.fa-ul {
|
||||
list-style-type: none;
|
||||
margin-left: var(--fa-li-margin, 2.5em);
|
||||
padding-left: 0; }
|
||||
.fa-ul > li {
|
||||
position: relative; }
|
||||
|
||||
.fa-li {
|
||||
left: calc(-1 * var(--fa-li-width, 2em));
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: var(--fa-li-width, 2em);
|
||||
line-height: inherit; }
|
||||
|
||||
.fa-border {
|
||||
border-color: var(--fa-border-color, #eee);
|
||||
border-radius: var(--fa-border-radius, 0.1em);
|
||||
border-style: var(--fa-border-style, solid);
|
||||
border-width: var(--fa-border-width, 0.08em);
|
||||
padding: var(--fa-border-padding, 0.2em 0.25em 0.15em); }
|
||||
|
||||
.fa-pull-left {
|
||||
float: left;
|
||||
margin-right: var(--fa-pull-margin, 0.3em); }
|
||||
|
||||
.fa-pull-right {
|
||||
float: right;
|
||||
margin-left: var(--fa-pull-margin, 0.3em); }
|
||||
|
||||
.fa-beat {
|
||||
animation-name: fa-beat;
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
|
||||
|
||||
.fa-bounce {
|
||||
animation-name: fa-bounce;
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); }
|
||||
|
||||
.fa-fade {
|
||||
animation-name: fa-fade;
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
|
||||
|
||||
.fa-beat-fade {
|
||||
animation-name: fa-beat-fade;
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
|
||||
|
||||
.fa-flip {
|
||||
animation-name: fa-flip;
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
|
||||
|
||||
.fa-shake {
|
||||
animation-name: fa-shake;
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--fa-animation-timing, linear); }
|
||||
|
||||
.fa-spin {
|
||||
animation-name: fa-spin;
|
||||
animation-delay: var(--fa-animation-delay, 0s);
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-duration: var(--fa-animation-duration, 2s);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--fa-animation-timing, linear); }
|
||||
|
||||
.fa-spin-reverse {
|
||||
--fa-animation-direction: reverse; }
|
||||
|
||||
.fa-pulse,
|
||||
.fa-spin-pulse {
|
||||
animation-name: fa-spin;
|
||||
animation-direction: var(--fa-animation-direction, normal);
|
||||
animation-duration: var(--fa-animation-duration, 1s);
|
||||
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--fa-animation-timing, steps(8)); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fa-beat,
|
||||
.fa-bounce,
|
||||
.fa-fade,
|
||||
.fa-beat-fade,
|
||||
.fa-flip,
|
||||
.fa-pulse,
|
||||
.fa-shake,
|
||||
.fa-spin,
|
||||
.fa-spin-pulse {
|
||||
animation-delay: -1ms;
|
||||
animation-duration: 1ms;
|
||||
animation-iteration-count: 1;
|
||||
transition-delay: 0s;
|
||||
transition-duration: 0s; } }
|
||||
|
||||
@keyframes fa-beat {
|
||||
0%, 90% {
|
||||
transform: scale(1); }
|
||||
45% {
|
||||
transform: scale(var(--fa-beat-scale, 1.25)); } }
|
||||
|
||||
@keyframes fa-bounce {
|
||||
0% {
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
10% {
|
||||
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
|
||||
30% {
|
||||
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
|
||||
50% {
|
||||
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
|
||||
57% {
|
||||
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
|
||||
64% {
|
||||
transform: scale(1, 1) translateY(0); }
|
||||
100% {
|
||||
transform: scale(1, 1) translateY(0); } }
|
||||
|
||||
@keyframes fa-fade {
|
||||
50% {
|
||||
opacity: var(--fa-fade-opacity, 0.4); } }
|
||||
|
||||
@keyframes fa-beat-fade {
|
||||
0%, 100% {
|
||||
opacity: var(--fa-beat-fade-opacity, 0.4);
|
||||
transform: scale(1); }
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
|
||||
|
||||
@keyframes fa-flip {
|
||||
50% {
|
||||
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
|
||||
|
||||
@keyframes fa-shake {
|
||||
0% {
|
||||
transform: rotate(-15deg); }
|
||||
4% {
|
||||
transform: rotate(15deg); }
|
||||
8%, 24% {
|
||||
transform: rotate(-18deg); }
|
||||
12%, 28% {
|
||||
transform: rotate(18deg); }
|
||||
16% {
|
||||
transform: rotate(-22deg); }
|
||||
20% {
|
||||
transform: rotate(22deg); }
|
||||
32% {
|
||||
transform: rotate(-12deg); }
|
||||
36% {
|
||||
transform: rotate(12deg); }
|
||||
40%, 100% {
|
||||
transform: rotate(0deg); } }
|
||||
|
||||
@keyframes fa-spin {
|
||||
0% {
|
||||
transform: rotate(0deg); }
|
||||
100% {
|
||||
transform: rotate(360deg); } }
|
||||
|
||||
.fa-rotate-90 {
|
||||
transform: rotate(90deg); }
|
||||
|
||||
.fa-rotate-180 {
|
||||
transform: rotate(180deg); }
|
||||
|
||||
.fa-rotate-270 {
|
||||
transform: rotate(270deg); }
|
||||
|
||||
.fa-flip-horizontal {
|
||||
transform: scale(-1, 1); }
|
||||
|
||||
.fa-flip-vertical {
|
||||
transform: scale(1, -1); }
|
||||
|
||||
.fa-flip-both,
|
||||
.fa-flip-horizontal.fa-flip-vertical {
|
||||
transform: scale(-1, -1); }
|
||||
|
||||
.fa-rotate-by {
|
||||
transform: rotate(var(--fa-rotate-angle, 0)); }
|
||||
|
||||
.fa-stack {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 2em;
|
||||
position: relative;
|
||||
width: 2.5em; }
|
||||
|
||||
.fa-stack-1x,
|
||||
.fa-stack-2x {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: var(--fa-stack-z-index, auto); }
|
||||
|
||||
.svg-inline--fa.fa-stack-1x {
|
||||
height: 1em;
|
||||
width: 1.25em; }
|
||||
|
||||
.svg-inline--fa.fa-stack-2x {
|
||||
height: 2em;
|
||||
width: 2.5em; }
|
||||
|
||||
.fa-inverse {
|
||||
color: var(--fa-inverse, #fff); }
|
||||
|
||||
.sr-only,
|
||||
.fa-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0; }
|
||||
|
||||
.sr-only-focusable:not(:focus),
|
||||
.fa-sr-only-focusable:not(:focus) {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0; }
|
||||
|
||||
.svg-inline--fa .fa-primary {
|
||||
fill: var(--fa-primary-color, currentColor);
|
||||
opacity: var(--fa-primary-opacity, 1); }
|
||||
|
||||
.svg-inline--fa .fa-secondary {
|
||||
fill: var(--fa-secondary-color, currentColor);
|
||||
opacity: var(--fa-secondary-opacity, 0.4); }
|
||||
|
||||
.svg-inline--fa.fa-swap-opacity .fa-primary {
|
||||
opacity: var(--fa-secondary-opacity, 0.4); }
|
||||
|
||||
.svg-inline--fa.fa-swap-opacity .fa-secondary {
|
||||
opacity: var(--fa-primary-opacity, 1); }
|
||||
|
||||
.svg-inline--fa mask .fa-primary,
|
||||
.svg-inline--fa mask .fa-secondary {
|
||||
fill: black; }
|
||||
|
||||
.fad.fa-inverse,
|
||||
.fa-duotone.fa-inverse {
|
||||
color: var(--fa-inverse, #fff); }
|
||||
6
assets/fontawesome-free-6.6.0-web/css/svg-with-js.min.css
vendored
Normal file
6
assets/fontawesome-free-6.6.0-web/css/svg-with-js.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
26
assets/fontawesome-free-6.6.0-web/css/v4-font-face.css
Normal file
26
assets/fontawesome-free-6.6.0-web/css/v4-font-face.css
Normal file
@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype");
|
||||
unicode-range: U+F003,U+F006,U+F014,U+F016-F017,U+F01A-F01B,U+F01D,U+F022,U+F03E,U+F044,U+F046,U+F05C-F05D,U+F06E,U+F070,U+F087-F088,U+F08A,U+F094,U+F096-F097,U+F09D,U+F0A0,U+F0A2,U+F0A4-F0A7,U+F0C5,U+F0C7,U+F0E5-F0E6,U+F0EB,U+F0F6-F0F8,U+F10C,U+F114-F115,U+F118-F11A,U+F11C-F11D,U+F133,U+F147,U+F14E,U+F150-F152,U+F185-F186,U+F18E,U+F190-F192,U+F196,U+F1C1-F1C9,U+F1D9,U+F1DB,U+F1E3,U+F1EA,U+F1F7,U+F1F9,U+F20A,U+F247-F248,U+F24A,U+F24D,U+F255-F25B,U+F25D,U+F271-F274,U+F278,U+F27B,U+F28C,U+F28E,U+F29C,U+F2B5,U+F2B7,U+F2BA,U+F2BC,U+F2BE,U+F2C0-F2C1,U+F2C3,U+F2D0,U+F2D2,U+F2D4,U+F2DC; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-v4compatibility.woff2") format("woff2"), url("../webfonts/fa-v4compatibility.ttf") format("truetype");
|
||||
unicode-range: U+F041,U+F047,U+F065-F066,U+F07D-F07E,U+F080,U+F08B,U+F08E,U+F090,U+F09A,U+F0AC,U+F0AE,U+F0B2,U+F0D0,U+F0D6,U+F0E4,U+F0EC,U+F10A-F10B,U+F123,U+F13E,U+F148-F149,U+F14C,U+F156,U+F15E,U+F160-F161,U+F163,U+F175-F178,U+F195,U+F1F8,U+F219,U+F27A; }
|
||||
6
assets/fontawesome-free-6.6.0-web/css/v4-font-face.min.css
vendored
Normal file
6
assets/fontawesome-free-6.6.0-web/css/v4-font-face.min.css
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a}
|
||||
2194
assets/fontawesome-free-6.6.0-web/css/v4-shims.css
Normal file
2194
assets/fontawesome-free-6.6.0-web/css/v4-shims.css
Normal file
File diff suppressed because it is too large
Load Diff
6
assets/fontawesome-free-6.6.0-web/css/v4-shims.min.css
vendored
Normal file
6
assets/fontawesome-free-6.6.0-web/css/v4-shims.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
22
assets/fontawesome-free-6.6.0-web/css/v5-font-face.css
Normal file
22
assets/fontawesome-free-6.6.0-web/css/v5-font-face.css
Normal file
@ -0,0 +1,22 @@
|
||||
/*!
|
||||
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Brands';
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-display: block;
|
||||
font-weight: 900;
|
||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-display: block;
|
||||
font-weight: 400;
|
||||
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }
|
||||
6
assets/fontawesome-free-6.6.0-web/css/v5-font-face.min.css
vendored
Normal file
6
assets/fontawesome-free-6.6.0-web/css/v5-font-face.min.css
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2024 Fonticons, Inc.
|
||||
*/
|
||||
@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}
|
||||
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-brands-400.ttf
Normal file
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-brands-400.ttf
Normal file
Binary file not shown.
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-brands-400.woff2
Normal file
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-regular-400.ttf
Normal file
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-regular-400.ttf
Normal file
Binary file not shown.
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-regular-400.woff2
Normal file
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-solid-900.ttf
Normal file
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-solid-900.woff2
Normal file
BIN
assets/fontawesome-free-6.6.0-web/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/images/about/stripe.png
Normal file
BIN
assets/images/about/stripe.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
assets/images/about/whats.png
Normal file
BIN
assets/images/about/whats.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 336 KiB |
BIN
assets/images/user.png
Normal file
BIN
assets/images/user.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 614 B |
13
assets/js/catalog.js
Normal file
13
assets/js/catalog.js
Normal file
@ -0,0 +1,13 @@
|
||||
let filters = document.querySelector('.filters');
|
||||
let filterOpenBtn = document.getElementById('catalog-filter-btn');
|
||||
|
||||
filters.addEventListener('click', (e) => {
|
||||
if(e.target == filters) {
|
||||
filters.classList.remove('active');
|
||||
blockScroll(false);
|
||||
}
|
||||
});
|
||||
filterOpenBtn.addEventListener('click', (e) => {
|
||||
filters.classList.add('active');
|
||||
blockScroll(true);
|
||||
});
|
||||
18
assets/js/index.js
Normal file
18
assets/js/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
new Swiper(".recent-keys", {
|
||||
slidesPerView: 1,
|
||||
spaceBetween: 20,
|
||||
loop: true,
|
||||
|
||||
autoplay: {
|
||||
delay: 3000,
|
||||
disableOnInteraction: false,
|
||||
},
|
||||
breakpoints: {
|
||||
630: {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
930: {
|
||||
slidesPerView: 3,
|
||||
}
|
||||
}
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user