Add vendor files-1

This commit is contained in:
Steve Oswald 2022-11-24 21:28:26 +01:00 committed by GitHub
parent 55ea24fc42
commit 7b540101d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
89 changed files with 12439 additions and 0 deletions

55
vendor/leafs/anchor/README.md vendored Normal file
View file

@ -0,0 +1,55 @@
<!-- markdownlint-disable no-inline-html -->
<p align="center">
<br><br>
<img src="https://leafphp.dev/logo-circle.png" height="100"/>
<br><br>
</p>
# Leaf Anchor
[![Latest Stable Version](https://poser.pugx.org/leafs/anchor/v/stable)](https://packagist.org/packages/leafs/anchor)
[![Total Downloads](https://poser.pugx.org/leafs/anchor/downloads)](https://packagist.org/packages/leafs/anchor)
[![License](https://poser.pugx.org/leafs/anchor/license)](https://packagist.org/packages/leafs/anchor)
This package contains leaf's utils for deep sanitizing of data and basic security provided for your app data. It also serves as the base for security provided in other modules like CSRF.
## Installation
You can easily install Leaf using [Composer](https://getcomposer.org/).
```bash
composer require leafs/anchor
```
## Basic Usage
After [installing](#installation) anchor, create an _index.php_ file.
### Base XSS protection
```php
<?php
require __DIR__ . "vendor/autoload.php";
$data = $_POST["data"];
$data = Leaf\Anchor::sanitize($data);
echo $data;
```
This also works on arrays
```php
<?php
require __DIR__ . "vendor/autoload.php";
$data = Leaf\Anchor::sanitize($_POST);
echo $data["input"];
```
You may quickly test this using the built-in PHP server:
```bash
php -S localhost:8000
```

36
vendor/leafs/anchor/composer.json vendored Normal file
View file

@ -0,0 +1,36 @@
{
"name": "leafs/anchor",
"description": "Leaf PHP util module",
"keywords": [
"util",
"leaf",
"php",
"framework"
],
"homepage": "https://leafphp.netlify.app/#/",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Michael Darko",
"email": "mickdd22@gmail.com",
"homepage": "https://mychi.netlify.app",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"Leaf\\": "src"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"require-dev": {
"pestphp/pest": "^1.21"
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
}
}

18
vendor/leafs/anchor/phpunit.xml vendored Normal file
View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Test Suite">
<directory suffix=".test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
<directory suffix=".php">./src</directory>
</include>
</coverage>
</phpunit>

127
vendor/leafs/anchor/src/Anchor.php vendored Normal file
View file

@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace Leaf;
/**
* Leaf Security Module
* ---------------------------------
* Simple to use security based utility methods
*
* @author Michael Darko <mickdd22@gmail.com>
* @since v2.2
* @version 1.0
*/
class Anchor
{
protected static $config = [
'SECRET_KEY' => '_token',
'SECRET' => '@nkor_leaf$0Secret!',
'EXCEPT' => [],
'METHODS' => ['POST', 'PUT', 'PATCH', 'DELETE'],
];
protected static $errors = [];
/**
* Manage config for leaf anchor
*
* @param array|null $config The config to set
*/
public static function config($config = null)
{
if ($config === null) {
return static::$config;
}
static::$config = array_merge(static::$config, $config);
}
/**
* Escape malicious characters
*
* @param mixed $data The data to sanitize.
*/
public static function sanitize($data)
{
if (is_array($data)) {
foreach ($data as $key => $value) {
$data[is_string($key) ? self::sanitize($key) : $key] = self::sanitize($value);
}
}
if (is_string($data)) {
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}
return $data;
}
/**
* Get an item or items from an array of data.
*
* @param array $dataSource An array of data to search through
* @param string|array $item The items to return
*/
public static function deepGet($dataSource, $item = null)
{
if (!$item) {
return $dataSource;
}
$output = [];
if (is_array($item)) {
foreach ($item as $dataItem) {
$output[$dataItem] = $dataSource[$dataItem] ?? null;
}
} else {
$output = $dataSource[$item] ?? null;
}
return $output;
}
/**
* Convert string to boolean. Created due to inconsistencies in PHP's boolval and (bool)
*
* @param string $value The value to convert
* @return bool
*/
public static function toBool($value)
{
if (is_bool($value)) {
return $value;
}
if (is_string($value)) {
$value = strtolower($value);
if ($value === 'true' || $value === '1') {
return true;
}
if ($value === 'false' || $value === '0') {
return false;
}
}
return (bool) $value;
}
/**
* Generate a token for identifying your application
*
* @param int $strength Number of random characters to attach to token
*/
public static function generateToken(int $strength = 16): string
{
return bin2hex(static::$config['SECRET'] . '.' . random_bytes($strength));
}
public static function errors(): array
{
return static::$errors;
}
}

View file

@ -0,0 +1,46 @@
<?php
use Leaf\Anchor;
test('set config', function () {
Anchor::config(['SECRET' => 'item']);
$config = Anchor::config();
expect($config['SECRET'])->toBe('item');
});
test('sanitize', function () {
$html = '<b>Hello World</b>';
expect(Anchor::sanitize($html))->toBe(htmlspecialchars($html));
});
test('sanitize array', function () {
$html = ['<b>Hello World</b>', '<b>Hello World</b>'];
expect(Anchor::sanitize($html))->toBe([
htmlspecialchars('<b>Hello World</b>'),
htmlspecialchars('<b>Hello World</b>'),
]);
});
test('sanitize assoc array', function () {
$html = ['key' => '<b>Hello World</b>'];
expect(Anchor::sanitize($html))->toBe(['key' => htmlspecialchars('<b>Hello World</b>')]);
});
test('generate token', function () {
expect(Anchor::generateToken())->toBeString();
});
test('secret in token', function () {
$anchorSecret = 'SOMETHING';
Anchor::config(['SECRET' => $anchorSecret]);
expect(strpos(hex2bin(Anchor::generateToken()), $anchorSecret))->toBe(0);
});
test('errors', function () {
expect(Anchor::errors())->toBeArray();
});

27
vendor/leafs/cookie/README.md vendored Normal file
View file

@ -0,0 +1,27 @@
<!-- markdownlint-disable no-inline-html -->
<p align="center">
<br><br>
<img src="https://leafphp.netlify.app/assets/img/leaf3-logo.png" height="100"/>
<h1 align="center">Leaf Cookie Module</h1>
<br><br>
</p>
# Leaf PHP
[![Latest Stable Version](https://poser.pugx.org/leafs/cookie/v/stable)](https://packagist.org/packages/leafs/cookie)
[![Total Downloads](https://poser.pugx.org/leafs/cookie/downloads)](https://packagist.org/packages/leafs/cookie)
[![License](https://poser.pugx.org/leafs/cookie/license)](https://packagist.org/packages/leafs/cookie)
Leaf's cookie functionality packaged as a serve-yourself module.
## Installation
You can easily install Leaf using [Composer](https://getcomposer.org/).
```bash
composer require leafs/cookie
```
## View Leaf's docs [here](https://leafphp.netlify.app/#/)
Built with ❤ by [**Mychi Darko**](https://mychi.netlify.app)

32
vendor/leafs/cookie/composer.json vendored Normal file
View file

@ -0,0 +1,32 @@
{
"name": "leafs/cookie",
"description": "Leaf PHP cookie module",
"keywords": [
"cookies",
"http",
"leaf",
"php",
"framework"
],
"homepage": "https://leafphp.netlify.app/#/",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Michael Darko",
"email": "mickdd22@gmail.com",
"homepage": "https://mychi.netlify.app",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"Leaf\\Http\\": "src"
},
"files": [
"src/functions.php"
]
},
"minimum-stability": "dev",
"prefer-stable": true
}

149
vendor/leafs/cookie/src/Cookie.php vendored Normal file
View file

@ -0,0 +1,149 @@
<?php
namespace Leaf\Http;
/**
* Leaf Cookie
* ------------------------------------
* Cookie management made simple with Leaf
*
* @author Michael Darko
* @since 2.0.0
*/
class Cookie
{
/**
* Default options for cookie values
*/
protected static $options = [
'expires' => 0,
'path' => '',
'domain' => '',
'secure' => false,
'httponly' => false,
'samesite' => 'Lax',
];
/**
* Set default cookie options
*
* @param array $defaults
* ```
* 'expires' => 0,
* 'path' => ',
* 'domain' => ',
* 'secure' => false,
* 'httponly' => false,
* 'samesite' => 'Lax',
* ```
*/
public static function setDefaults(array $defaults)
{
static::$options = array_merge(
static::$options,
$defaults
);
}
/**
* Set a new cookie
*
* @param string|array $key Cookie name
* @param mixed $value Cookie value
* @param array $options Cookie settings
*/
public static function set($key, $value = '', array $options = [])
{
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
\Leaf\Config::set('response.cookies', array_merge(
\Leaf\Config::get('response.cookies'),
[$key => [$value, $options['expires'] ?? (time() + 604800)]],
));
return;
}
if (is_array($key)) {
foreach ($key as $name => $value) {
self::set($name, $value);
}
} else {
setcookie($key, $value, [
'expires' => ($options['expire'] ?? $options['expires']) ?? self::$options['expire'],
'path' => $options['path'] ?? self::$options['path'],
'domain' => $options['domain'] ?? self::$options['domain'],
'secure' => $options['secure'] ?? self::$options['secure'],
'httponly' => $options['httponly'] ?? self::$options['httponly'],
'samesite' => $options['samesite'] ?? self::$options['samesite'],
]);
}
}
/**
* Shorthand method of setting a cookie + value + expire time
*
* @param string $name The name of the cookie
* @param string $value If string, the value of cookie; if array, properties for cookie including: value, expire, path, domain, secure, httponly
* @param string $expires When the cookie expires. Default: 7 days
*/
public static function simpleCookie(string $name, string $value, string $expires = null)
{
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
\Leaf\Config::set('response.cookies', array_merge(
\Leaf\Config::get('response.cookies'),
[$name => [$value, $expires ?? (time() + 604800)]],
));
return;
}
self::set($name, $value, ['expires' => $expires ?? (time() + 604800)]);
}
/**
* Get all set cookies
*/
public static function all(): array
{
return $_COOKIE ?? [];
}
/**
* Get a particular cookie
*/
public static function get($param)
{
return $_COOKIE[$param] ?? null;
}
/**
* Unset a particular cookie
*/
public static function unset($key)
{
if (is_array($key)) {
foreach ($key as $name) {
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
\Leaf\Config::set('response.cookies', array_merge(
\Leaf\Config::get('response.cookies'),
[$key => ['', time() - 604800]],
));
}
self::unset($name);
}
} else {
setcookie($key, '', time() - 86400);
}
}
/**
* Unset all cookies
*/
public static function unsetAll()
{
foreach ($_COOKIE as $key => &$value) {
self::unset($key);
}
}
}

20
vendor/leafs/cookie/src/functions.php vendored Normal file
View file

@ -0,0 +1,20 @@
<?php
if (!function_exists('cookie')) {
/**
* Return cookie data/object or set cookie data
*
* @return \Leaf\Http\Cookie
*/
function cookie()
{
$cookie = Leaf\Config::get('cookie')['instance'] ?? null;
if (!$cookie) {
$cookie = new \Leaf\Http\Cookie();
Leaf\Config::set('cookie', ['instance' => $cookie]);
}
return $cookie;
}
}

74
vendor/leafs/db/README.md vendored Normal file
View file

@ -0,0 +1,74 @@
<!-- markdownlint-disable no-inline-html -->
<p align="center">
<br><br>
<img src="https://leafphp.netlify.app/assets/img/leaf3-logo.png" height="100"/>
<h1 align="center">Leaf Db v3</h1>
<br><br>
</p>
[![Latest Stable Version](https://poser.pugx.org/leafs/db/v/stable)](https://packagist.org/packages/leafs/db)
[![Total Downloads](https://poser.pugx.org/leafs/db/downloads)](https://packagist.org/packages/leafs/db)
[![License](https://poser.pugx.org/leafs/db/license)](https://packagist.org/packages/leafs/db)
Leaf DB has gone through yet another re-write. This time, Leaf DB focuses on maintaining a cleaner structure with more usable and grounded code. v3 supports more databases like postgres and sqlite, comes with some performance gains and is far more efficient than v1 and v2. It is also independent of the leaf core which makes it suitable for any project you run.
## What's new?
### DB Support
Leaf DB now supports connections with other databases like postgresql, sqlite, oracle and more.
### Deep syncing with leaf 3
Leaf DB is now detached from leaf, however, as a leaf 3 module, there's additional functionality you can get from using leaf db in a leaf 3 app. Deep syncing config, instances and functional mode all become available to you.
### PDO rewrite
Under the hood, Leaf DB has been rewritten to fully support PDO, both internally and user instantiated PDO instances. This makes leaf db more flexible and more compatible with most systems and applications.
### Performance Improvements
After a series of benchmarks with ApacheBench, apps using leaf db v3 were almost twice as fast as apps using the prior version. These small performance wins can go a long way to improve the overall perfomance of your app drastically.
### Methods
- `create`
- `drop`
- `insert` with multiple fields
- Connections with pgsql, oracle, sqlite and many more db types
- Functional mode
## Installation
You can easily install Leaf using [Composer](https://getcomposer.org/).
```bash
composer require leafs/db
```
## Basic usage
After installing leaf db, you need to connect to your database to use any of the db functions.
```php
$db = new Leaf\Db('127.0.0.1', 'dbName', 'user', 'password');
# or
$db = new Leaf\Db();
$db->connect('127.0.0.1', 'dbName', 'user', 'password');
```
If you're using leaf db in a leaf 3 app, you will have access to the `db` global
```php
db()->connect('127.0.0.1', 'dbName', 'user', 'password');
```
From there, you can use any db method.
```php
$users = db()->select('users')->all();
```
You can find leaf db's complete documentation [here](https://leafphp.dev/modules/db/). **The docs are still being updated.**

38
vendor/leafs/db/composer.json vendored Normal file
View file

@ -0,0 +1,38 @@
{
"name": "leafs/db",
"description": "Leaf PHP db module.",
"keywords": [
"database",
"orm",
"leaf",
"php",
"framework"
],
"homepage": "https://leafphp.netlify.app/#/",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Michael Darko",
"email": "mickdd22@gmail.com",
"homepage": "https://mychi.netlify.app",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"Leaf\\": "src"
},
"files": [
"src/functions.php"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"ext-mysqli": "*"
},
"require-dev": {
"pestphp/pest": "^1.21"
}
}

248
vendor/leafs/db/src/Db.php vendored Normal file
View file

@ -0,0 +1,248 @@
<?php
declare(strict_types=1);
namespace Leaf;
use Leaf\Db\Builder;
use Leaf\Db\Utils;
/**
* Leaf Db
* -----
* Simple database interactions
*
* @version 3.0
* @since v2.1.0
*/
class Db extends Db\Core
{
/**
* Create a database if it doesn't exist
*
* @param string $db The name of the database to create
*/
public function create(string $db): self
{
$this->query("CREATE DATABASE $db");
return $this;
}
/**
* Drop a database if it exists
*
* @param string $db The name of the database to drop
*/
public function drop(string $db): self
{
$this->query("DROP DATABASE $db");
return $this;
}
/**
* Add a find by id clause to query
*
* @param string|int $id The id of the row to find
*/
public function find($id)
{
$this->where('id', $id);
return $this->first();
}
/**
* Find the first matching item for current query
*/
public function first()
{
$this->query .= ' ORDER BY id ASC LIMIT 1';
return $this->fetchAssoc();
}
/**
* Find the last matching item for current query
*/
public function last()
{
$this->query .= ' ORDER BY id DESC LIMIT 1';
return $this->fetchAssoc();
}
/**
* Order query items by a specific
*
* @param string $column The column to order results by
* @param string $direction The direction to order [DESC, ASC]
*/
public function orderBy(string $column, string $direction = 'desc')
{
$this->query = Builder::orderBy($this->query, $column, $direction);
return $this;
}
/**
* Limit query items by a specific number
*
* @param string|number $limit The number to limit by
*/
public function limit($limit)
{
$this->query = Builder::limit($this->query, $limit);
return $this;
}
/**
* Retrieve a row from table
*
* @param string $table Db Table
* @param string $items Specific table columns to fetch
*/
public function select(string $table, string $items = "*")
{
$this->query("SELECT $items FROM $table");
$this->table = $table;
return $this;
}
/**
* Add a new row in a db table
*
* @param string $table Db Table
*/
public function insert(string $table): self
{
$this->query("INSERT INTO $table");
$this->table = $table;
return $this;
}
/**
* Update a row in a db table
*
* @param string $table Db Table
*/
public function update(string $table): self
{
$this->query("UPDATE $table");
$this->table = $table;
return $this;
}
/**
* Delete a table's records
*
* @param string $table: Db Table
*/
public function delete(string $table): self
{
$this->query("DELETE FROM $table");
$this->table = $table;
return $this;
}
/**
* Pass in parameters into your query
*
* @param array|string $params Key or params to pass into query
* @param string|null $value Value for key
*/
public function params($params): self
{
$this->query = Builder::params($this->query, $params);
$this->bind(...(Builder::$bindings));
$this->params = $params;
return $this;
}
/**
* Add a where clause to db query
*
* @param string|array $condition The condition to evaluate
* @param mixed $comparator Condition value or comparator
* @param mixed $value The value of condition if comparator is passed
*/
public function where($condition, $comparator = null, $value = null): self
{
$this->query = Builder::where(
$this->query,
$condition,
$value === null ? $comparator : $value,
$value === null ? "=" : $comparator
);
$this->bind(...(Builder::$bindings));
return $this;
}
/**
* Add a where clause with OR comparator to db query
*
* @param string|array $condition The condition to evaluate
* @param mixed $comparator Condition value or comparator
* @param mixed $value The value of condition if comparator is passed
*/
public function orWhere($condition, $comparator = null, $value = null): self
{
$this->query = Builder::where(
$this->query,
$condition,
$value === null ? $comparator : $value,
$value === null ? "=" : $comparator,
"OR"
);
$this->bind(...(Builder::$bindings));
return $this;
}
/**
* Hide particular fields from the final value returned
*
* @param mixed $values The value(s) to hide
*/
public function hidden(...$values): self
{
$this->hidden = Utils::flatten($values);
return $this;
}
/**
* Make sure a value doesn't already exist in a table to avoid duplicates.
*
* @param mixed $uniques Items to check for
*/
public function unique(...$uniques)
{
$this->uniques = Utils::flatten($uniques);
return $this;
}
/**
* Add particular fields to the final value returned
*
* @param string|array $name What to add
* @param string $value The value to add
*/
public function add($name, $value = null): self
{
if (is_array($name)) {
$this->added = $name;
} else {
$this->added[$name] = $value;
}
return $this;
}
/**
* Search a db table for a value
*
* @param string $row The item to search for in table
* @param string $value The keyword to search for
* @param array|null $hidden The items to hide from returned result
*/
public function search(string $row, string $value, ?array $hidden = []): ?array
{
return $this->select($this->table)->where($row, 'LIKE', Utils::includes($value))->hidden($hidden)->all();
}
}

159
vendor/leafs/db/src/Db/Builder.php vendored Normal file
View file

@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace Leaf\Db;
/**
* Leaf Db Query Builder
* -------------------------
* Functionality of leaf query builder.
*
* @author Michael Darko
* @since 3.0
* @version 1.0.0
*/
class Builder
{
/**
* Params bound to query
*/
public static $bindings = [];
/**
* Order query results by a colum
*
* @param string $query The query to modify (if any)
* @param string $column The column to order results by
* @param string $direction The direction to order [DESC, ASC]
*/
public static function orderBy(
string $query,
string $column,
string $direction = 'desc'
): string {
if (strpos($query, 'ORDER BY') === false) {
$query .= " ORDER BY $column " . strtoupper($direction);
} else {
$parts = explode('ORDER BY', $query);
$col = explode(' ', trim($parts[1]));
$parts[1] = str_replace($col[0], '', $parts[1]);
$query = implode("ORDER BY $column " . strtoupper($direction), $parts);
}
return $query;
}
/**
* Limit query to specific number of values to return
*
* @param string $query The query to modify (if any)
* @param string|number $number Limit to query
*/
public static function limit(string $query, $number): string
{
if (strpos($query, ' LIMIT ') === false) {
$query .= " LIMIT $number";
} else {
$parts = explode(' LIMIT ', $query);
$num = explode(' ', trim($parts[1]));
$parts[1] = str_replace($num, '', $parts[1]);
$query = implode(" LIMIT $number ", $parts);
}
return $query;
}
/**
* Controls inner workings of all where blocks
*
* @param string $query The query to modify
* @param string|array $condition The condition to evaluate
* @param mixed $value The value if condition is a string
* @param string $comparator The comparator to bind condition
* @param string $operation The operation to join multiple wheres
*/
public static function where(
string $query,
$condition,
$value = null,
string $comparator = "=",
string $operation = "AND"
): string {
$query .= (strpos($query, ' WHERE ') === false) ? ' WHERE ' : " $operation ";
if (is_string($condition)) {
$query .= $condition;
if ($value !== null) {
$query .= " $comparator ?";
static::$bindings[] = $value;
}
} else {
foreach ($condition as $k => $v) {
$query .= "$k$comparator? $operation ";
}
$values = array_values($condition);
$query = rtrim($query, " $operation ");
static::$bindings = array_merge(static::$bindings, $values);
}
return $query;
}
/**
* Builder for params block
*
* @param string $query The query to modify
* @param array|string $params Key or params to pass into query
*/
public static function params(string $query, $params): string
{
$IS_UPDATE = is_int(strpos($query, 'UPDATE '));
$IS_INSERT = is_int(strpos($query, 'INSERT INTO '));
$query .= $IS_UPDATE ? ' SET ' : ' ';
if ($IS_INSERT) {
if (is_array($params[0] ?? null)) {
$flat = Utils::flatten($params, true);
$flatValues = Utils::flatten($params);
$values = [];
$keys = implode(',', array_keys($flat));
foreach ($params as $v) {
$values[] = '(' . rtrim(str_repeat('?,', count(array_values($v))), ',') . ')';
}
static::$bindings = $flatValues;
$values = implode(',', $values);
$query .= "($keys) VALUES $values";
} else {
$keys = implode(',', array_keys($params));
$values = array_values($params);
static::$bindings = $values;
$values = rtrim(str_repeat('?,', count($values)), ',');
$query .= "($keys) VALUES ($values)";
}
}
if ($IS_UPDATE) {
$rebuild = [];
foreach ($params as $k => $v) {
$rebuild[$k] = '?';
}
$query .= str_replace('%3F', '?', http_build_query($rebuild, '', ', '));
static::$bindings = array_values($params);
}
return $query;
}
}

526
vendor/leafs/db/src/Db/Core.php vendored Normal file
View file

@ -0,0 +1,526 @@
<?php
declare(strict_types=1);
namespace Leaf\Db;
/**
* Leaf Db [Core]
* -------------------------
* Core functionality of leaf db.
*
* @author Michael Darko
* @since 3.0
* @version 1.0.0
*/
class Core
{
/**
* Config for leaf db
*/
protected $config = [
'dbtype' => 'mysql',
'charset' => null,
'port' => '3306',
'unixSocket' => null,
'host' => '127.0.0.1',
'username' => 'root',
'password' => '',
'dbname' => '',
];
/**
* Db table to peform operations on
*/
protected $table = null;
/**
* leaf db connection instance
*/
protected $connection = null;
/**
* Errors caught in leaf db
*/
protected $errors = [];
/**
* Actual query to run
*/
protected $query;
/**
* Full list of params passed into leaf db
*/
protected $params = [];
/**
* Params bound to query
*/
protected $bindings = [];
/**
* Items to hide from query results
*/
protected $hidden = [];
/**
* Items to add to query results
*/
protected $added = [];
/**
* Items which should be unique in db
*/
protected $uniques = [];
/**
* Query result
*
* @var \PDOStatement
*/
protected $queryResult;
/**
* Initialize leaf db with a database connection
*
* @param string|array $host Host Name or full config
* @param string $dbname Database name
* @param string $user Database username
* @param string $password Database password
* @param string $dbtype Type of database: mysql, postgres, sqlite, ...
*/
public function __construct(
$host = '',
string $dbname = '',
string $user = 'root',
string $password = '',
string $dbtype = 'mysql'
) {
if (class_exists('Leaf\App')) app()->config('db', $this->config);
if ($host !== '') {
$this->connect($host, $dbname, $user, $password, $dbtype);
}
}
/**
* Connect to database
*
* @param string|array $host Host Name or full config
* @param string $dbname Database name
* @param string $user Database username
* @param string $password Database password
* @param string $dbtype Type of database: mysql, postgres, sqlite, ...
* @param array $pdoOptions Options for PDO connection
*/
public function connect(
$host = '127.0.0.1',
string $dbname = '',
string $user = 'root',
string $password = '',
string $dbtype = 'mysql',
array $pdoOptions = []
): \PDO {
if (is_array($host)) {
$this->config($host);
} else {
$this->config([
'host' => $host,
'dbname' => $dbname,
'username' => $user,
'password' => $password,
'dbtype' => $dbtype,
]);
}
// response()
try {
$dbtype = $this->config('dbtype');
$connection = new \PDO(
$this->dsn(),
$dbtype === 'sqlite' ? null : $this->config('username'),
$dbtype === 'sqlite' ? null : $this->config('password'),
array_merge(
$this->config('pdoOptions') ?? [],
$pdoOptions
)
);
$connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->connection = $connection;
return $connection;
} catch (\Throwable $th) {
throw $th;
}
}
/**
* Connect to database using environment variables
*
* @param array $pdoOptions Options for PDO connection
*/
public function autoConnect(array $pdoOptions = []): \PDO
{
return $this->connect(
[
'dbtype' => getenv('DB_CONNECTION') ? getenv('DB_CONNECTION') : 'mysql',
'charset' => getenv('DB_CHARSET'),
'port' => getenv('DB_PORT') ? getenv('DB_PORT') : '3306',
'host' => getenv('DB_HOST') ? getenv('DB_HOST') : '127.0.0.1',
'username' => getenv('DB_USERNAME') ? getenv('DB_USERNAME') : 'root',
'password' => getenv('DB_PASSWORD') ? getenv('DB_PASSWORD') : '',
'dbname' => getenv('DB_DATABASE'),
],
'',
'',
'',
'',
$pdoOptions
);
}
protected function dsn(): string
{
$dbtype = $this->config('dbtype');
$dbname = $this->config('dbname');
$host = $this->config('host');
if ($dbtype === 'sqlite') {
$dsn = "sqlite:$dbname";
} else {
$dsn = "$dbtype:host=$host";
if ($dbname !== '') $dsn .= ";dbname=$dbname";
if ($this->config('port')) $dsn .= ';port=' . $this->config('port');
if ($this->config('charset')) $dsn .= ';charset=' . $this->config('charset');
if ($this->config('unixSocket')) $dsn .= ';unix_socket=' . $this->config('unixSocket');
}
return $dsn;
}
/**
* Return the database connection
*
* @param \PDO $connection Manual instance of PDO connection
*/
public function connection(\PDO $connection = null)
{
if (!$connection) return $this->connection;
$this->connection = $connection;
}
/**
* Closes Db connection
*/
public function close(): void
{
$this->connection = null;
}
/**
* Set the current db table for operations
*
* @param string $table Table to perform database operations on
*/
public function table(string $table): self
{
$this->table = $table;
return $this;
}
/**
* Configure leaf db - syncs with leaf config
*/
public function config($name, $value = null)
{
if (class_exists('Leaf\App') && function_exists('app')) {
if (is_array($name)) {
$this->config = array_merge($this->config, $name);
app()->config('db', array_merge(app()->config('db'), $this->config));
} else {
return app()->config("db.$name", $value);
}
} else {
if (is_array($name)) {
$this->config = array_merge($this->config, $name);
} else {
if (!$value) {
return $this->config[$name];
} else {
$this->config[$name] = $value;
}
}
}
}
/**
* Manually create a database query
*
* @param string $sql Full db query
*/
public function query(string $sql): self
{
$this->query = $sql;
return $this;
}
/**
* Bind parameters to a query
*
* @param array|string $data The data to bind to string
*/
public function bind(...$bindings): self
{
$this->bindings = $bindings;
return $this;
}
/**
* Execute a generated query
*/
public function execute()
{
if ($this->connection === null) {
trigger_error('Initialise your database first with connect()');
}
$state = $this->copyState();
$this->clearState();
if (count($state['uniques'])) {
$IS_UPDATE = is_int(strpos($state['query'], 'UPDATE '));
$IS_INSERT = is_int(strpos($state['query'], 'INSERT INTO '));
if ($IS_UPDATE || $IS_INSERT) {
foreach ($state['uniques'] as $unique) {
if (!isset($state['params'][$unique])) {
trigger_error("$unique not found, Add $unique to your insert or update items or check your spelling.");
}
if ($this->connection->query("SELECT * FROM {$state['table']} WHERE $unique='{$state['params'][$unique]}'")->fetch(\PDO::FETCH_ASSOC)) {
$this->errors[$unique] = "$unique already exists";
}
}
if (count($this->errors)) {
Builder::$bindings = [];
return null;
}
}
}
if (count($state['bindings']) === 0) {
$this->queryResult = $this->connection->query($state['query']);
} else {
$stmt = $this->connection->prepare($state['query']);
$stmt->execute($state['bindings']);
$this->queryResult = $stmt;
}
Builder::$bindings = [];
return $this->queryResult;
}
/**
* Get raw result of last query
*
* @return \PDOStatement
*/
public function result()
{
$this->execute();
return $this->queryResult;
}
/**
* Fetch column from results
*/
public function column()
{
$this->execute();
return $this->queryResult->fetch(\PDO::FETCH_COLUMN);
}
/**
* Get the current count of objects in query
*/
public function count(): int
{
$this->execute();
return $this->queryResult->rowCount();
}
/**
* Alias for fetchAssoc
*/
public function assoc()
{
return $this->fetchAssoc();
}
/**
* Fetch the items returned by query
*/
public function fetchAssoc()
{
$added = $this->added;
$hidden = $this->hidden;
$this->execute();
$result = $this->queryResult->fetch(\PDO::FETCH_ASSOC);
if (count($added)) {
$result = array_merge($result, $added);
}
if (count($hidden)) {
foreach ($hidden as $item) {
unset($result[$item]);
}
}
return $result;
}
/**
* Alias for fetchObj
*/
public function obj()
{
return $this->fetchObj();
}
/**
* Fetch the items returned by query
*/
public function fetchObj()
{
$add = $this->added;
$hidden = $this->hidden;
$this->execute();
$result = $this->queryResult->fetch(\PDO::FETCH_ASSOC);
if (count($add)) {
$result = array_merge($result, $add);
}
if (count($hidden)) {
foreach ($hidden as $item) {
unset($result[$item]);
}
}
return (object) $result;
}
/**
* Fetch the items returned by query
*/
public function fetchAll($type = 'assoc')
{
$added = $this->added;
$hidden = $this->hidden;
$this->execute();
$results = array_map(function ($result) use ($hidden, $added) {
if (count($hidden)) {
foreach ($hidden as $item) {
unset($result[$item]);
}
}
if (count($added)) {
$result = array_merge($result, $added);
}
return $result;
}, $this->queryResult->fetchAll(\PDO::FETCH_ASSOC));
if ($type == 'obj' || $type == 'object') {
$results = (object) $results;
}
return $results;
}
/**
* Alias for fetchAll
*/
public function all($type = 'assoc')
{
return $this->fetchAll($type);
}
/**
* Alias for fetchAll
*/
public function get($type = 'assoc')
{
return $this->fetchAll($type);
}
/**
* Copy internal state
*/
protected function copyState()
{
return [
'table' => $this->table,
'query' => $this->query,
'bindings' => $this->bindings,
'uniques' => $this->uniques,
'hidden' => $this->hidden,
'added' => $this->added,
'params' => $this->params,
];
}
/**
* Prepare leaf db to handle next query
*/
protected function clearState()
{
$this->table = "";
$this->query = "";
$this->bindings = [];
$this->uniques = [];
$this->hidden = [];
$this->added = [];
$this->params = [];
}
/**
* Get the current snapshot of leaf db internals
*/
public function debug()
{
return [
'query' => $this->query,
'queryResult' => $this->queryResult,
'config' => $this->config,
'connection' => $this->connection,
'bindings' => $this->bindings,
'hidden' => $this->hidden,
'added' => $this->added,
'uniques' => $this->uniques,
'errors' => $this->errors,
];
}
/**
* Return caught errors if any
*/
public function errors(): array
{
return $this->errors;
}
}

67
vendor/leafs/db/src/Db/Utils.php vendored Normal file
View file

@ -0,0 +1,67 @@
<?php
namespace Leaf\Db;
/**
* Leaf Db Utils
* -------------------------
* Core utilities for leaf db.
*
* @author Michael Darko
* @since 3.0
* @version 1.0.0
*/
class Utils
{
/**
* Flatten multidimensional array into a single array
*/
public static function flatten(array $array, bool $keys = false): array
{
$parsed = [];
if ($keys) {
array_walk_recursive($array, function ($a, $b) use (&$parsed) {
$parsed[$b] = $a;
});
} else {
array_walk_recursive($array, function ($a) use (&$parsed) {
$parsed[] = $a;
});
}
return $parsed;
}
/**
* Construct search that begins with a phrase in db
*/
public static function beginsWith($phrase): string
{
return "$phrase%";
}
/**
* Construct search that ends with a phrase in db
*/
public static function endsWith($phrase): string
{
return "%$phrase";
}
/**
* Construct search that includes a phrase in db
*/
public static function includes($phrase): string
{
return "%$phrase%";
}
/**
* Construct search that begins and ends with a phrase in db
*/
public static function word($beginsWith, $endsWith): string
{
return "$beginsWith%$endsWith";
}
}

17
vendor/leafs/db/src/functions.php vendored Normal file
View file

@ -0,0 +1,17 @@
<?php
if (!function_exists('db') && class_exists('Leaf\App')) {
/**
* Return the database object
*
* @return \Leaf\Db
*/
function db()
{
if (!(\Leaf\Config::get("db.instance"))) {
\Leaf\Config::set("db.instance", new \Leaf\Db());
}
return \Leaf\Config::get("db.instance");
}
}

19
vendor/leafs/exception/LICENSE.md vendored Normal file
View file

@ -0,0 +1,19 @@
# The MIT License
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.

53
vendor/leafs/exception/composer.json vendored Normal file
View file

@ -0,0 +1,53 @@
{
"name": "leafs/exception",
"license": "MIT",
"description": "Error handler for leaf (fork of whoops)",
"keywords": [
"library",
"error",
"handling",
"exception",
"whoops",
"throwable"
],
"homepage": "https://github.com/leafsphp/exception",
"authors": [
{
"name": "Filipe Dobreira",
"homepage": "https://github.com/filp",
"role": "Developer"
},
{
"name": "Michael Darko",
"email": "mickdd22@gmail.com",
"homepage": "https://mychi.netlify.app",
"role": "Developer"
}
],
"scripts": {
"test": "phpunit --testdox tests"
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
"mockery/mockery": "^0.9 || ^1.0",
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
"whoops/soap": "Formats errors as SOAP responses"
},
"autoload": {
"psr-4": {
"Leaf\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Leaf\\": "tests/Whoops/"
}
}
}

View file

@ -0,0 +1,232 @@
<?php
declare(strict_types=1);
namespace Leaf\Exception;
use Leaf\Http\Response;
/**
* Stop Exception
*
* This is a general exception thrown from the leaf app
*
* @author Michael Darko
* @since 2.0.0
*/
class General extends \Exception
{
protected $response;
protected $config = [];
public function __construct($throwable)
{
$this->response = new Response();
$this->handleException($throwable);
}
/**
* Configure exception handler
*/
public function configure($config)
{
$configuration = array_merge($this->config, $config);
$this->config = $configuration;
}
/**
* Handles an exception
*/
protected function handleException($throwable)
{
$this->response->throwErr($throwable);
}
/**
* Convert errors into ErrorException objects
*
* This method catches PHP errors and converts them into \ErrorException objects;
* these \ErrorException objects are then thrown and caught by Leaf's
* built-in or custom error handlers.
*
* @param int $errno The numeric type of the Error
* @param string $errstr The error message
* @param string $errfile The absolute path to the affected file
* @param int $errline The line number of the error in the affected file
* @return bool
* @throws \ErrorException
*/
public static function handleErrors($errno, $errstr = '', $errfile = '', $errline = '')
{
if (!($errno & error_reporting())) {
return;
}
try {
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
} catch (\Throwable $th) {
$app = \Leaf\Config::get("app")["instance"];
if ($app && $app->config("log.enabled")) {
$app->logger()->error($th);
}
exit(static::renderBody($th));
}
}
/**
* Returns ErrorException objects from errors
*
* This method catches PHP errors and converts them into \ErrorException objects;
* these \ErrorException objects are then thrown and caught by Leaf's
* built-in or custom error handlers.
*
* @param int $errno The numeric type of the Error
* @param string $errstr The error message
* @param string $errfile The absolute path to the affected file
* @param int $errline The line number of the error in the affected file
* @return void|\ErrorException
*/
public static function toException($errno, $errstr = '', $errfile = '', $errline = '')
{
if (!($errno & error_reporting())) {
return;
}
try {
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
} catch (\Throwable $th) {
return $th;
}
}
/**
* Render response body
*
* @param array $env
* @param \Exception $exception
*
* @return string
*/
protected static function renderBody($exception)
{
$title = static::$config['ERROR_TITLE'] ?? 'Leaf Application Error';
$code = $exception->getCode();
$message = htmlspecialchars($exception->getMessage());
$file = $exception->getFile();
$line = $exception->getLine();
$trace = str_replace(
['#', "\n"],
['<div>#', '</div>'],
htmlspecialchars($exception->getTraceAsString())
);
$trace = str_replace(['): ', '</div>'], ['): <span style="color:#f4ae5d;">', '</span></div>'], $trace);
$body = "<h1 style=\"color:#34be6d;\">$title</h1>";
$body .= '<p>The application could not run because of the following error:</p>';
$body .= '<h2>Details</h2>';
$body .= sprintf('<div><strong>Type:</strong> %s</div>', get_class($exception));
if ($code) {
$body .= "<div><strong>Code:</strong> $code</div>";
}
if ($message) {
$body .= "<div><strong>Message:</strong> $message</div>";
}
if ($file) {
$body .= "<div><strong>File:</strong> $file</div>";
}
if ($line) {
$body .= "<div><strong>Line:</strong> $line</div>";
}
if ($trace) {
$body .= '<h2>Trace</h2>';
$body .= "<pre style=\"padding:20px 30px 15px 30px;background:#003543;overflow-x:scroll;border-radius:10px;\">$trace</pre>";
}
return static::exceptionMarkup($title, $body);
}
/**
* Generate diagnostic template markup
*
* This method accepts a title and body content to generate an HTML document layout.
*
* @param string $title The title of the HTML template
* @param string $body The body content of the HTML template
* @return string
*/
protected static function errorMarkup($title, $body)
{
return "<html><head><title>$title</title><link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700;display=swap\"><style>body{display:flex;justify-content:center;align-items:center;background-color:rgb(0,30,38);color:white;margin:0;padding:0px 30px;font-family:DM Sans,sans-serif;}h1{margin:0;font-weight:normal;}strong{display:inline-block;width:65px;}</style></head><body><h1 style=\"color: #34be6d;border-right:1px solid #555855;padding-right:20px;\">$title</h1><main style=\"padding-left:20px;\">$body</main></body></html>";
}
/**
* Generate diagnostic template markup
*
* This method accepts a title and body content to generate an HTML document layout.
*
* @param string $title The title of the HTML template
* @param string $body The body content of the HTML template
* @return string
*/
protected static function exceptionMarkup($title, $body)
{
return "<html><head><title>$title</title><link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700;display=swap\"><style>body{background-color:rgb(0,30,38);color:white;margin:0;padding:50px;font:15px/14px DM Sans,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}h2{margin-top:70px;}strong{color:#34be6d;display:inline-block;width:65px;}div{margin:15px 0px;}div strong{margin-right:40px;}</style></head><body>$body</body></html>";
}
/**
* Default Not Found handler
*/
public static function defaultDown()
{
echo static::errorMarkup(
'Oops!',
'<p>App is under maintainance, please check back soon.</p>'
);
}
/**
* Default Not Found handler
*/
public static function default404()
{
echo static::errorMarkup(
'404',
'<p>The page you are looking for could not be found.</p>'
);
}
/**
* CSRF error
*/
public static function csrf($error = null)
{
echo static::errorMarkup(
'Invalid request',
"<p>$error</p>" ?? '<p>The page you are looking for has expired.</p>'
);
}
/**
* Default Error handler
*/
public static function defaultError($e = null)
{
if ($e) {
$app = \Leaf\Config::get("app")["instance"];
if ($app && $app->config("log.enabled")) {
$app->logger()->error($e);
}
}
echo self::errorMarkup('Oops!', '<p>A website error has occurred, our team has been notified.</p>');
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Handler;
use InvalidArgumentException;
/**
* Wrapper for Closures passed as handlers. Can be used
* directly, or will be instantiated automagically by Whoops\Run
* if passed to Run::pushHandler
*/
class CallbackHandler extends Handler
{
/**
* @var callable
*/
protected $callable;
/**
* @throws InvalidArgumentException If argument is not callable
* @param callable $callable
*/
public function __construct($callable)
{
if (!is_callable($callable)) {
throw new InvalidArgumentException(
'Argument to ' . __METHOD__ . ' must be valid callable'
);
}
$this->callable = $callable;
}
/**
* @return int|null
*/
public function handle()
{
$exception = $this->getException();
$inspector = $this->getInspector();
$run = $this->getRun();
$callable = $this->callable;
// invoke the callable directly, to get simpler stacktraces (in comparison to call_user_func).
// this assumes that $callable is a properly typed php-callable, which we check in __construct().
return $callable($exception, $inspector, $run);
}
}

View file

@ -0,0 +1,95 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Handler;
use Leaf\Exceptions\Inspector;
use Leaf\Exception\RunInterface;
/**
* Abstract implementation of a Handler.
*/
abstract class Handler implements HandlerInterface
{
/*
Return constants that can be returned from Handler::handle
to message the handler walker.
*/
const DONE = 0x10; // returning this is optional, only exists for
// semantic purposes
/**
* The Handler has handled the Throwable in some way, and wishes to skip any other Handler.
* Execution will continue.
*/
const LAST_HANDLER = 0x20;
/**
* The Handler has handled the Throwable in some way, and wishes to quit/stop execution
*/
const QUIT = 0x30;
/**
* @var RunInterface
*/
private $run;
/**
* @var Inspector $inspector
*/
private $inspector;
/**
* @var \Throwable $exception
*/
private $exception;
/**
* @param RunInterface $run
*/
public function setRun(RunInterface $run)
{
$this->run = $run;
}
/**
* @return RunInterface
*/
protected function getRun()
{
return $this->run;
}
/**
* @param Inspector $inspector
*/
public function setInspector(Inspector $inspector)
{
$this->inspector = $inspector;
}
/**
* @return Inspector
*/
protected function getInspector()
{
return $this->inspector;
}
/**
* @param \Throwable $exception
*/
public function setException($exception)
{
$this->exception = $exception;
}
/**
* @return \Throwable
*/
protected function getException()
{
return $this->exception;
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Handler;
use Leaf\Exceptions\Inspector;
use Leaf\Exception\RunInterface;
interface HandlerInterface
{
/**
* @return int|null A handler may return nothing, or a Handler::HANDLE_* constant
*/
public function handle();
/**
* @param RunInterface $run
* @return void
*/
public function setRun(RunInterface $run);
/**
* @param \Throwable $exception
* @return void
*/
public function setException($exception);
/**
* @param Inspector $inspector
* @return void
*/
public function setInspector(Inspector $inspector);
}

View file

@ -0,0 +1,88 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Handler;
use Leaf\Exceptions\Formatter;
/**
* Catches an exception and converts it to a JSON
* response. Additionally can also return exception
* frames for consumption by an API.
*/
class JsonResponseHandler extends Handler
{
/**
* @var bool
*/
private $returnFrames = false;
/**
* @var bool
*/
private $jsonApi = false;
/**
* Returns errors[[]] instead of error[] to be in compliance with the json:api spec
* @param bool $jsonApi Default is false
* @return static
*/
public function setJsonApi($jsonApi = false)
{
$this->jsonApi = (bool) $jsonApi;
return $this;
}
/**
* @param bool|null $returnFrames
* @return bool|static
*/
public function addTraceToOutput($returnFrames = null)
{
if (func_num_args() == 0) {
return $this->returnFrames;
}
$this->returnFrames = (bool) $returnFrames;
return $this;
}
/**
* @return int
*/
public function handle()
{
if ($this->jsonApi === true) {
$response = [
'errors' => [
Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput()
),
]
];
} else {
$response = [
'error' => Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput()
),
];
}
echo json_encode($response, defined('JSON_PARTIAL_OUTPUT_ON_ERROR') ? JSON_PARTIAL_OUTPUT_ON_ERROR : 0);
return Handler::QUIT;
}
/**
* @return string
*/
public function contentType()
{
return 'application/json';
}
}

View file

@ -0,0 +1,359 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
* Plaintext handler for command line and logs.
* @author Pierre-Yves Landuré <https://howto.biapy.com/>
*/
namespace Leaf\Exception\Handler;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Leaf\Exceptions\Frame;
/**
* Handler outputing plaintext error messages. Can be used
* directly, or will be instantiated automagically by Whoops\Run
* if passed to Run::pushHandler
*/
class PlainTextHandler extends Handler
{
const VAR_DUMP_PREFIX = ' | ';
/**
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* @var callable
*/
protected $dumper;
/**
* @var bool
*/
private $addTraceToOutput = true;
/**
* @var bool|integer
*/
private $addTraceFunctionArgsToOutput = false;
/**
* @var integer
*/
private $traceFunctionArgsOutputLimit = 1024;
/**
* @var bool
*/
private $addPreviousToOutput = true;
/**
* @var bool
*/
private $loggerOnly = false;
/**
* Constructor.
* @throws InvalidArgumentException If argument is not null or a LoggerInterface
* @param \Psr\Log\LoggerInterface|null $logger
*/
public function __construct($logger = null)
{
$this->setLogger($logger);
}
/**
* Set the output logger interface.
* @throws InvalidArgumentException If argument is not null or a LoggerInterface
* @param \Psr\Log\LoggerInterface|null $logger
*/
public function setLogger($logger = null)
{
if (! (is_null($logger)
|| $logger instanceof LoggerInterface)) {
throw new InvalidArgumentException(
'Argument to ' . __METHOD__ .
" must be a valid Logger Interface (aka. Monolog), " .
get_class($logger) . ' given.'
);
}
$this->logger = $logger;
}
/**
* @return \Psr\Log\LoggerInterface|null
*/
public function getLogger()
{
return $this->logger;
}
/**
* Set var dumper callback function.
*
* @param callable $dumper
* @return static
*/
public function setDumper(callable $dumper)
{
$this->dumper = $dumper;
return $this;
}
/**
* Add error trace to output.
* @param bool|null $addTraceToOutput
* @return bool|static
*/
public function addTraceToOutput($addTraceToOutput = null)
{
if (func_num_args() == 0) {
return $this->addTraceToOutput;
}
$this->addTraceToOutput = (bool) $addTraceToOutput;
return $this;
}
/**
* Add previous exceptions to output.
* @param bool|null $addPreviousToOutput
* @return bool|static
*/
public function addPreviousToOutput($addPreviousToOutput = null)
{
if (func_num_args() == 0) {
return $this->addPreviousToOutput;
}
$this->addPreviousToOutput = (bool) $addPreviousToOutput;
return $this;
}
/**
* Add error trace function arguments to output.
* Set to True for all frame args, or integer for the n first frame args.
* @param bool|integer|null $addTraceFunctionArgsToOutput
* @return static|bool|integer
*/
public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null)
{
if (func_num_args() == 0) {
return $this->addTraceFunctionArgsToOutput;
}
if (! is_integer($addTraceFunctionArgsToOutput)) {
$this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput;
} else {
$this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput;
}
return $this;
}
/**
* Set the size limit in bytes of frame arguments var_dump output.
* If the limit is reached, the var_dump output is discarded.
* Prevent memory limit errors.
* @var integer
* @return static
*/
public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit)
{
$this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit;
return $this;
}
/**
* Create plain text response and return it as a string
* @return string
*/
public function generateResponse()
{
$exception = $this->getException();
$message = $this->getExceptionOutput($exception);
if ($this->addPreviousToOutput) {
$previous = $exception->getPrevious();
while ($previous) {
$message .= "\n\nCaused by\n" . $this->getExceptionOutput($previous);
$previous = $previous->getPrevious();
}
}
return $message . $this->getTraceOutput() . "\n";
}
/**
* Get the size limit in bytes of frame arguments var_dump output.
* If the limit is reached, the var_dump output is discarded.
* Prevent memory limit errors.
* @return integer
*/
public function getTraceFunctionArgsOutputLimit()
{
return $this->traceFunctionArgsOutputLimit;
}
/**
* Only output to logger.
* @param bool|null $loggerOnly
* @return static|bool
*/
public function loggerOnly($loggerOnly = null)
{
if (func_num_args() == 0) {
return $this->loggerOnly;
}
$this->loggerOnly = (bool) $loggerOnly;
return $this;
}
/**
* Test if handler can output to stdout.
* @return bool
*/
private function canOutput()
{
return !$this->loggerOnly();
}
/**
* Get the frame args var_dump.
* @param \Leaf\Exceptions\Frame $frame [description]
* @param integer $line [description]
* @return string
*/
private function getFrameArgsOutput(Frame $frame, $line)
{
if ($this->addTraceFunctionArgsToOutput() === false
|| $this->addTraceFunctionArgsToOutput() < $line) {
return '';
}
// Dump the arguments:
ob_start();
$this->dump($frame->getArgs());
if (ob_get_length() > $this->getTraceFunctionArgsOutputLimit()) {
// The argument var_dump is to big.
// Discarded to limit memory usage.
ob_clean();
return sprintf(
"\n%sArguments dump length greater than %d Bytes. Discarded.",
self::VAR_DUMP_PREFIX,
$this->getTraceFunctionArgsOutputLimit()
);
}
return sprintf(
"\n%s",
preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean())
);
}
/**
* Dump variable.
*
* @param mixed $var
* @return void
*/
protected function dump($var)
{
if ($this->dumper) {
call_user_func($this->dumper, $var);
} else {
var_dump($var);
}
}
/**
* Get the exception trace as plain text.
* @return string
*/
private function getTraceOutput()
{
if (! $this->addTraceToOutput()) {
return '';
}
$inspector = $this->getInspector();
$frames = $inspector->getFrames();
$response = "\nStack trace:";
$line = 1;
foreach ($frames as $frame) {
/** @var Frame $frame */
$class = $frame->getClass();
$template = "\n%3d. %s->%s() %s:%d%s";
if (! $class) {
// Remove method arrow (->) from output.
$template = "\n%3d. %s%s() %s:%d%s";
}
$response .= sprintf(
$template,
$line,
$class,
$frame->getFunction(),
$frame->getFile(),
$frame->getLine(),
$this->getFrameArgsOutput($frame, $line)
);
$line++;
}
return $response;
}
/**
* Get the exception as plain text.
* @param \Throwable $exception
* @return string
*/
private function getExceptionOutput($exception)
{
return sprintf(
"%s: %s in file %s on line %d",
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine()
);
}
/**
* @return int
*/
public function handle()
{
$response = $this->generateResponse();
if ($this->getLogger()) {
$this->getLogger()->error($response);
}
if (! $this->canOutput()) {
return Handler::DONE;
}
echo $response;
return Handler::QUIT;
}
/**
* @return string
*/
public function contentType()
{
return 'text/plain';
}
}

View file

@ -0,0 +1,834 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Handler;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use UnexpectedValueException;
use Leaf\Exceptions\Formatter;
use Leaf\Exception\Util\Misc;
use Leaf\Exception\Util\TemplateHelper;
class PrettyPageHandler extends Handler
{
const EDITOR_SUBLIME = "sublime";
const EDITOR_TEXTMATE = "textmate";
const EDITOR_EMACS = "emacs";
const EDITOR_MACVIM = "macvim";
const EDITOR_PHPSTORM = "phpstorm";
const EDITOR_IDEA = "idea";
const EDITOR_VSCODE = "vscode";
const EDITOR_ATOM = "atom";
const EDITOR_ESPRESSO = "espresso";
const EDITOR_XDEBUG = "xdebug";
const EDITOR_NETBEANS = "netbeans";
/**
* Search paths to be scanned for resources.
*
* Stored in the reverse order they're declared.
*
* @var array
*/
private $searchPaths = [];
/**
* Fast lookup cache for known resource locations.
*
* @var array
*/
private $resourceCache = [];
/**
* The name of the custom css file.
*
* @var string|null
*/
private $customCss = null;
/**
* The name of the custom js file.
*
* @var string|null
*/
private $customJs = null;
/**
* @var array[]
*/
private $extraTables = [];
/**
* @var bool
*/
private $handleUnconditionally = false;
/**
* @var string
*/
private $pageTitle = "Whoops! There was an error.";
/**
* @var array[]
*/
private $applicationPaths;
/**
* @var array[]
*/
private $blacklist = [
'_GET' => [],
'_POST' => [],
'_FILES' => [],
'_COOKIE' => [],
'_SESSION' => [],
'_SERVER' => [],
'_ENV' => [],
];
/**
* An identifier for a known IDE/text editor.
*
* Either a string, or a calalble that resolves a string, that can be used
* to open a given file in an editor. If the string contains the special
* substrings %file or %line, they will be replaced with the correct data.
*
* @example
* "txmt://open?url=%file&line=%line"
*
* @var callable|string $editor
*/
protected $editor;
/**
* A list of known editor strings.
*
* @var array
*/
protected $editors = [
"sublime" => "subl://open?url=file://%file&line=%line",
"textmate" => "txmt://open?url=file://%file&line=%line",
"emacs" => "emacs://open?url=file://%file&line=%line",
"macvim" => "mvim://open/?url=file://%file&line=%line",
"phpstorm" => "phpstorm://open?file=%file&line=%line",
"idea" => "idea://open?file=%file&line=%line",
"vscode" => "vscode://file/%file:%line",
"atom" => "atom://core/open/file?filename=%file&line=%line",
"espresso" => "x-espresso://open?filepath=%file&lines=%line",
"netbeans" => "netbeans://open/?f=%file:%line",
];
/**
* @var TemplateHelper
*/
protected $templateHelper;
/**
* Constructor.
*
* @return void
*/
public function __construct()
{
if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
// Register editor using xdebug's file_link_format option.
$this->editors['xdebug'] = function ($file, $line) {
return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format'));
};
// If xdebug is available, use it as default editor.
$this->setEditor('xdebug');
}
// Add the default, local resource search path:
$this->searchPaths[] = __DIR__ . "/../Resources";
// blacklist php provided auth based values
$this->blacklist('_SERVER', 'PHP_AUTH_PW');
$this->templateHelper = new TemplateHelper();
if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
$cloner = new VarCloner();
// Only dump object internals if a custom caster exists for performance reasons
// https://github.com/filp/whoops/pull/404
$cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
$class = $stub->class;
$classes = [$class => $class] + class_parents($obj) + class_implements($obj);
foreach ($classes as $class) {
if (isset(AbstractCloner::$defaultCasters[$class])) {
return $a;
}
}
// Remove all internals
return [];
}]);
$this->templateHelper->setCloner($cloner);
}
}
/**
* @return int|null
*
* @throws \Exception
*/
public function handle()
{
if (!$this->handleUnconditionally()) {
// Check conditions for outputting HTML:
// @todo: Make this more robust
if (PHP_SAPI === 'cli') {
// Help users who have been relying on an internal test value
// fix their code to the proper method
if (isset($_ENV['whoops-test'])) {
throw new \Exception(
'Use handleUnconditionally instead of whoops-test'
.' environment variable'
);
}
return Handler::DONE;
}
}
$templateFile = $this->getResource("views/layout.html.php");
$cssFile = $this->getResource("css/whoops.base.css");
$zeptoFile = $this->getResource("js/zepto.min.js");
$prismJs = $this->getResource("js/prism.js");
$prismCss = $this->getResource("css/prism.css");
$clipboard = $this->getResource("js/clipboard.min.js");
$jsFile = $this->getResource("js/whoops.base.js");
if ($this->customCss) {
$customCssFile = $this->getResource($this->customCss);
}
if ($this->customJs) {
$customJsFile = $this->getResource($this->customJs);
}
$inspector = $this->getInspector();
$frames = $this->getExceptionFrames();
$code = $this->getExceptionCode();
// List of variables that will be passed to the layout template.
$vars = [
"page_title" => $this->getPageTitle(),
// @todo: Asset compiler
"stylesheet" => file_get_contents($cssFile),
"zepto" => file_get_contents($zeptoFile),
"prismJs" => file_get_contents($prismJs),
"prismCss" => file_get_contents($prismCss),
"clipboard" => file_get_contents($clipboard),
"javascript" => file_get_contents($jsFile),
// Template paths:
"header" => $this->getResource("views/header.html.php"),
"header_outer" => $this->getResource("views/header_outer.html.php"),
"frame_list" => $this->getResource("views/frame_list.html.php"),
"frames_description" => $this->getResource("views/frames_description.html.php"),
"frames_container" => $this->getResource("views/frames_container.html.php"),
"panel_details" => $this->getResource("views/panel_details.html.php"),
"panel_details_outer" => $this->getResource("views/panel_details_outer.html.php"),
"panel_left" => $this->getResource("views/panel_left.html.php"),
"panel_left_outer" => $this->getResource("views/panel_left_outer.html.php"),
"frame_code" => $this->getResource("views/frame_code.html.php"),
"env_details" => $this->getResource("views/env_details.html.php"),
"title" => $this->getPageTitle(),
"name" => explode("\\", $inspector->getExceptionName()),
"message" => $inspector->getExceptionMessage(),
"previousMessages" => $inspector->getPreviousExceptionMessages(),
"docref_url" => $inspector->getExceptionDocrefUrl(),
"code" => $code,
"previousCodes" => $inspector->getPreviousExceptionCodes(),
"plain_exception" => Formatter::formatExceptionPlain($inspector),
"frames" => $frames,
"has_frames" => !!count($frames),
"handler" => $this,
"handlers" => $this->getRun()->getHandlers(),
"active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ? 'application' : 'all',
"has_frames_tabs" => $this->getApplicationPaths(),
"tables" => [
"GET Data" => $this->masked($_GET, '_GET'),
"POST Data" => $this->masked($_POST, '_POST'),
"Files" => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [],
"Cookies" => $this->masked($_COOKIE, '_COOKIE'),
"Session" => isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') : [],
"Server/Request Data" => $this->masked($_SERVER, '_SERVER'),
"Environment Variables" => $this->masked($_ENV, '_ENV'),
],
];
if (isset($customCssFile)) {
$vars["stylesheet"] .= file_get_contents($customCssFile);
}
if (isset($customJsFile)) {
$vars["javascript"] .= file_get_contents($customJsFile);
}
// Add extra entries list of data tables:
// @todo: Consolidate addDataTable and addDataTableCallback
$extraTables = array_map(function ($table) use ($inspector) {
return $table instanceof \Closure ? $table($inspector) : $table;
}, $this->getDataTables());
$vars["tables"] = array_merge($extraTables, $vars["tables"]);
$plainTextHandler = new PlainTextHandler();
$plainTextHandler->setException($this->getException());
$plainTextHandler->setInspector($this->getInspector());
$vars["preface"] = "<!--\n\n\n" . $this->templateHelper->escape($plainTextHandler->generateResponse()) . "\n\n\n\n\n\n\n\n\n\n\n-->";
$this->templateHelper->setVariables($vars);
$this->templateHelper->render($templateFile);
return Handler::QUIT;
}
/**
* Get the stack trace frames of the exception currently being handled.
*
* @return \Leaf\Exceptions\FrameCollection
*/
protected function getExceptionFrames()
{
$frames = $this->getInspector()->getFrames();
if ($this->getApplicationPaths()) {
foreach ($frames as $frame) {
foreach ($this->getApplicationPaths() as $path) {
if (strpos($frame->getFile(), $path) === 0) {
$frame->setApplication(true);
break;
}
}
}
}
return $frames;
}
/**
* Get the code of the exception currently being handled.
*
* @return string
*/
protected function getExceptionCode()
{
$exception = $this->getException();
$code = $exception->getCode();
if ($exception instanceof \ErrorException) {
// ErrorExceptions wrap the php-error types within the 'severity' property
$code = Misc::translateErrorCode($exception->getSeverity());
}
return (string) $code;
}
/**
* @return string
*/
public function contentType()
{
return 'text/html';
}
/**
* Adds an entry to the list of tables displayed in the template.
*
* The expected data is a simple associative array. Any nested arrays
* will be flattened with `print_r`.
*
* @param string $label
* @param array $data
*
* @return static
*/
public function addDataTable($label, array $data)
{
$this->extraTables[$label] = $data;
return $this;
}
/**
* Lazily adds an entry to the list of tables displayed in the table.
*
* The supplied callback argument will be called when the error is
* rendered, it should produce a simple associative array. Any nested
* arrays will be flattened with `print_r`.
*
* @param string $label
* @param callable $callback Callable returning an associative array
*
* @throws InvalidArgumentException If $callback is not callable
*
* @return static
*/
public function addDataTableCallback($label, /* callable */ $callback)
{
if (!is_callable($callback)) {
throw new InvalidArgumentException('Expecting callback argument to be callable');
}
$this->extraTables[$label] = function (\Leaf\Exceptions\Inspector $inspector = null) use ($callback) {
try {
$result = call_user_func($callback, $inspector);
// Only return the result if it can be iterated over by foreach().
return is_array($result) || $result instanceof \Traversable ? $result : [];
} catch (\Exception $e) {
// Don't allow failure to break the rendering of the original exception.
return [];
}
};
return $this;
}
/**
* Returns all the extra data tables registered with this handler.
*
* Optionally accepts a 'label' parameter, to only return the data table
* under that label.
*
* @param string|null $label
*
* @return array[]|callable
*/
public function getDataTables($label = null)
{
if ($label !== null) {
return isset($this->extraTables[$label]) ?
$this->extraTables[$label] : [];
}
return $this->extraTables;
}
/**
* Set whether to handle unconditionally.
*
* Allows to disable all attempts to dynamically decide whether to handle
* or return prematurely. Set this to ensure that the handler will perform,
* no matter what.
*
* @param bool|null $value
*
* @return bool|static
*/
public function handleUnconditionally($value = null)
{
if (func_num_args() == 0) {
return $this->handleUnconditionally;
}
$this->handleUnconditionally = (bool) $value;
return $this;
}
/**
* Adds an editor resolver.
*
* Either a string, or a closure that resolves a string, that can be used
* to open a given file in an editor. If the string contains the special
* substrings %file or %line, they will be replaced with the correct data.
*
* @example
* $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
* @example
* $run->addEditor('remove-it', function($file, $line) {
* unlink($file);
* return "http://stackoverflow.com";
* });
*
* @param string $identifier
* @param string|callable $resolver
*
* @return static
*/
public function addEditor($identifier, $resolver)
{
$this->editors[$identifier] = $resolver;
return $this;
}
/**
* Set the editor to use to open referenced files.
*
* Pass either the name of a configured editor, or a closure that directly
* resolves an editor string.
*
* @example
* $run->setEditor(function($file, $line) { return "file:///{$file}"; });
* @example
* $run->setEditor('sublime');
*
* @param string|callable $editor
*
* @throws InvalidArgumentException If invalid argument identifier provided
*
* @return static
*/
public function setEditor($editor)
{
if (!is_callable($editor) && !isset($this->editors[$editor])) {
throw new InvalidArgumentException(
"Unknown editor identifier: $editor. Known editors:" .
implode(",", array_keys($this->editors))
);
}
$this->editor = $editor;
return $this;
}
/**
* Get the editor href for a given file and line, if available.
*
* @param string $filePath
* @param int $line
*
* @throws InvalidArgumentException If editor resolver does not return a string
*
* @return string|bool
*/
public function getEditorHref($filePath, $line)
{
$editor = $this->getEditor($filePath, $line);
if (empty($editor)) {
return false;
}
// Check that the editor is a string, and replace the
// %line and %file placeholders:
if (!isset($editor['url']) || !is_string($editor['url'])) {
throw new UnexpectedValueException(
__METHOD__ . " should always resolve to a string or a valid editor array; got something else instead."
);
}
$editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']);
$editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']);
return $editor['url'];
}
/**
* Determine if the editor link should act as an Ajax request.
*
* @param string $filePath
* @param int $line
*
* @throws UnexpectedValueException If editor resolver does not return a boolean
*
* @return bool
*/
public function getEditorAjax($filePath, $line)
{
$editor = $this->getEditor($filePath, $line);
// Check that the ajax is a bool
if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) {
throw new UnexpectedValueException(
__METHOD__ . " should always resolve to a bool; got something else instead."
);
}
return $editor['ajax'];
}
/**
* Determines both the editor and if ajax should be used.
*
* @param string $filePath
* @param int $line
*
* @return array
*/
protected function getEditor($filePath, $line)
{
if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) {
return [];
}
if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) {
return [
'ajax' => false,
'url' => $this->editors[$this->editor],
];
}
if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
if (is_callable($this->editor)) {
$callback = call_user_func($this->editor, $filePath, $line);
} else {
$callback = call_user_func($this->editors[$this->editor], $filePath, $line);
}
if (empty($callback)) {
return [];
}
if (is_string($callback)) {
return [
'ajax' => false,
'url' => $callback,
];
}
return [
'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false,
'url' => isset($callback['url']) ? $callback['url'] : $callback,
];
}
return [];
}
/**
* Set the page title.
*
* @param string $title
*
* @return static
*/
public function setPageTitle($title)
{
$this->pageTitle = (string) $title;
return $this;
}
/**
* Get the page title.
*
* @return string
*/
public function getPageTitle()
{
return $this->pageTitle;
}
/**
* Adds a path to the list of paths to be searched for resources.
*
* @param string $path
*
* @throws InvalidArgumentException If $path is not a valid directory
*
* @return static
*/
public function addResourcePath($path)
{
if (!is_dir($path)) {
throw new InvalidArgumentException(
"'$path' is not a valid directory"
);
}
array_unshift($this->searchPaths, $path);
return $this;
}
/**
* Adds a custom css file to be loaded.
*
* @param string|null $name
*
* @return static
*/
public function addCustomCss($name)
{
$this->customCss = $name;
return $this;
}
/**
* Adds a custom js file to be loaded.
*
* @param string|null $name
*
* @return static
*/
public function addCustomJs($name)
{
$this->customJs = $name;
return $this;
}
/**
* @return array
*/
public function getResourcePaths()
{
return $this->searchPaths;
}
/**
* Finds a resource, by its relative path, in all available search paths.
*
* The search is performed starting at the last search path, and all the
* way back to the first, enabling a cascading-type system of overrides for
* all resources.
*
* @param string $resource
*
* @throws RuntimeException If resource cannot be found in any of the available paths
*
* @return string
*/
protected function getResource($resource)
{
// If the resource was found before, we can speed things up
// by caching its absolute, resolved path:
if (isset($this->resourceCache[$resource])) {
return $this->resourceCache[$resource];
}
// Search through available search paths, until we find the
// resource we're after:
foreach ($this->searchPaths as $path) {
$fullPath = $path . "/$resource";
if (is_file($fullPath)) {
// Cache the result:
$this->resourceCache[$resource] = $fullPath;
return $fullPath;
}
}
// If we got this far, nothing was found.
throw new RuntimeException(
"Could not find resource '$resource' in any resource paths."
. "(searched: " . join(", ", $this->searchPaths). ")"
);
}
/**
* @deprecated
*
* @return string
*/
public function getResourcesPath()
{
$allPaths = $this->getResourcePaths();
// Compat: return only the first path added
return end($allPaths) ?: null;
}
/**
* @deprecated
*
* @param string $resourcesPath
*
* @return static
*/
public function setResourcesPath($resourcesPath)
{
$this->addResourcePath($resourcesPath);
return $this;
}
/**
* Return the application paths.
*
* @return array
*/
public function getApplicationPaths()
{
return $this->applicationPaths;
}
/**
* Set the application paths.
*
* @param array $applicationPaths
*
* @return void
*/
public function setApplicationPaths($applicationPaths)
{
$this->applicationPaths = $applicationPaths;
}
/**
* Set the application root path.
*
* @param string $applicationRootPath
*
* @return void
*/
public function setApplicationRootPath($applicationRootPath)
{
$this->templateHelper->setApplicationRootPath($applicationRootPath);
}
/**
* blacklist a sensitive value within one of the superglobal arrays.
* Alias for the hideSuperglobalKey method.
*
* @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
* @param string $key The key within the superglobal
* @see hideSuperglobalKey
*
* @return static
*/
public function blacklist($superGlobalName, $key)
{
$this->blacklist[$superGlobalName][] = $key;
return $this;
}
/**
* Hide a sensitive value within one of the superglobal arrays.
*
* @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
* @param string $key The key within the superglobal
* @return static
*/
public function hideSuperglobalKey($superGlobalName, $key)
{
return $this->blacklist($superGlobalName, $key);
}
/**
* Checks all values within the given superGlobal array.
*
* Blacklisted values will be replaced by a equal length string containing
* only '*' characters for string values.
* Non-string values will be replaced with a fixed asterisk count.
* We intentionally dont rely on $GLOBALS as it depends on the 'auto_globals_jit' php.ini setting.
*
* @param array $superGlobal One of the superglobal arrays
* @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
*
* @return array $values without sensitive data
*/
private function masked(array $superGlobal, $superGlobalName)
{
$blacklisted = $this->blacklist[$superGlobalName];
$values = $superGlobal;
foreach ($blacklisted as $key) {
if (isset($superGlobal[$key])) {
$values[$key] = str_repeat('*', is_string($superGlobal[$key]) ? strlen($superGlobal[$key]) : 3);
}
}
return $values;
}
}

View file

@ -0,0 +1,107 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Handler;
use SimpleXMLElement;
use Leaf\Exceptions\Formatter;
/**
* Catches an exception and converts it to an XML
* response. Additionally can also return exception
* frames for consumption by an API.
*/
class XmlResponseHandler extends Handler
{
/**
* @var bool
*/
private $returnFrames = false;
/**
* @param bool|null $returnFrames
* @return bool|static
*/
public function addTraceToOutput($returnFrames = null)
{
if (func_num_args() == 0) {
return $this->returnFrames;
}
$this->returnFrames = (bool) $returnFrames;
return $this;
}
/**
* @return int
*/
public function handle()
{
$response = [
'error' => Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this->addTraceToOutput()
),
];
echo self::toXml($response);
return Handler::QUIT;
}
/**
* @return string
*/
public function contentType()
{
return 'application/xml';
}
/**
* @param SimpleXMLElement $node Node to append data to, will be modified in place
* @param array|\Traversable $data
* @return SimpleXMLElement The modified node, for chaining
*/
private static function addDataToNode(\SimpleXMLElement $node, $data)
{
assert(is_array($data) || $data instanceof Traversable);
foreach ($data as $key => $value) {
if (is_numeric($key)) {
// Convert the key to a valid string
$key = "unknownNode_". (string) $key;
}
// Delete any char not allowed in XML element names
$key = preg_replace('/[^a-z0-9\-\_\.\:]/i', '', $key);
if (is_array($value)) {
$child = $node->addChild($key);
self::addDataToNode($child, $value);
} else {
$value = str_replace('&', '&amp;', print_r($value, true));
$node->addChild($key, $value);
}
}
return $node;
}
/**
* The main function for converting to an XML document.
*
* @param array|\Traversable $data
* @return string XML
*/
private static function toXml($data)
{
assert(is_array($data) || $data instanceof Traversable);
$node = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><root />");
return self::addDataToNode($node, $data)->asXML();
}
}

View file

@ -0,0 +1,244 @@
/* PrismJS 1.24.1
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+markup-templating+php&plugins=line-highlight+line-numbers */
/**
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
* Based on https://github.com/chriskempson/tomorrow-theme
* @author Rose Pritchard
*/
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap');
code[class*='language-'],
pre[class*='language-'] {
color: #ccc;
background: none;
font-family: DM Mono, monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*='language-'] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
:not(pre) > code[class*='language-'],
pre[class*='language-'] {
background: #2d2d2d;
}
/* Inline code */
:not(pre) > code[class*='language-'] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #999;
}
.token.punctuation {
color: #ccc;
}
.token.tag,
.token.attr-name,
.token.namespace,
.token.deleted {
color: #fff;
}
.token.function-name {
color: #6196cc;
}
.token.boolean,
.token.number,
.token.function {
color: #22D3EE;
}
.token.property,
.token.class-name,
.token.constant,
.token.symbol {
color: #fff;
}
.token.selector,
.token.important,
.token.atrule,
.token.builtin {
color: color #E879F9;
}
.token.keyword {
color: #e879f9;
font-style: italic !important;
}
.token.string,
.token.char,
.token.attr-value,
.token.regex {
color: #BEF264;
}
.token.variable {
color: #fff;
}
.token.punctuation, .token.operator, .token.entity, .token.url {
color: rgb(146, 153, 166);
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.inserted {
color: green;
}
pre[data-line] {
position: relative;
padding: 1em 0 1em 3em;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
padding: inherit 0;
margin-top: 1em; /* Same as .prisms padding-top */
background: hsla(193, 96.3%, 20%, 0.08);
background: linear-gradient(
to right,
hsla(193, 96.3%, 20%, 0.1) 70%,
hsla(193, 96.3%, 20%, 0)
);
pointer-events: none;
line-height: inherit;
white-space: pre;
}
@media print {
.line-highlight {
/*
* This will prevent browsers from replacing the background color with white.
* It's necessary because the element is layered on top of the displayed code.
*/
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
}
.line-highlight:before,
.line-highlight[data-end]:after {
content: attr(data-start);
position: absolute;
top: 0.4em;
left: 0.6em;
min-width: 1em;
padding: 0 0.5em;
background-color: hsla(24, 20%, 50%, 0.4);
color: hsl(24, 20%, 95%);
font: bold 65%/1.5 sans-serif;
text-align: center;
vertical-align: 0.3em;
border-radius: 999px;
text-shadow: none;
box-shadow: 0 1px white;
}
.line-highlight[data-end]:after {
content: attr(data-end);
top: auto;
bottom: 0.4em;
}
.line-numbers .line-highlight:before,
.line-numbers .line-highlight:after {
content: none;
}
pre[id].linkable-line-numbers span.line-numbers-rows {
pointer-events: all;
}
pre[id].linkable-line-numbers span.line-numbers-rows > span:before {
cursor: pointer;
}
pre[id].linkable-line-numbers span.line-numbers-rows > span:hover:before {
background-color: rgba(128, 128, 128, 0.2);
}
pre[class*='language-'].line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre[class*='language-'].line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.7em;
width: 3.1em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: rgba(128, 128, 128, 0.3);
display: block;
padding-right: 0.8em;
text-align: right;
}

View file

@ -0,0 +1,574 @@
@import url("https://fonts.googleapis.com/css?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700;display=swap");
body {
font: 12px "DM Sans", sans-serif;
color: #131313;
background: #eeeeee;
padding: 0;
margin: 0;
max-height: 100%;
text-rendering: optimizeLegibility;
}
a {
text-decoration: none;
}
.Whoops.container {
position: relative;
z-index: 9999999999;
}
.panel {
overflow-y: scroll;
height: 100%;
position: fixed;
margin: 0;
left: 0;
top: 0;
}
.branding {
position: absolute;
top: 10px;
right: 20px;
color: #777777;
font-size: 10px;
z-index: 100;
}
.branding a {
color: #e95353;
}
header {
color: white;
box-sizing: border-box;
background-color: rgb(0, 30, 38);
padding: 35px 40px;
max-height: 180px;
overflow: hidden;
transition: 0.5s;
}
header.header-expand {
max-height: 1000px;
}
.exc-title {
margin: 0;
color: #bebebe;
font-size: 14px;
}
.exc-title-primary,
.exc-title-secondary {
color: #3eaf7c;
}
.exc-message {
font-size: 20px;
word-wrap: break-word;
margin: 4px 0 0 0;
color: white;
}
.exc-message span {
display: block;
}
.exc-message-empty-notice {
color: #a29d9d;
font-weight: 300;
}
.prev-exc-title {
margin: 10px 0;
}
.prev-exc-title + ul {
margin: 0;
padding: 0 0 0 20px;
line-height: 12px;
}
.prev-exc-title + ul li {
font: 12px "Helvetica Neue", helvetica, arial, sans-serif;
}
.prev-exc-title + ul li .prev-exc-code {
display: inline-block;
color: #bebebe;
}
.details-container {
left: 30%;
width: 70%;
background: #012a35;
}
.details {
padding: 5px;
background: #012a35;
color: white;
}
.details-heading {
color: #3eaf7c;
padding-bottom: 10px;
margin-bottom: 30px;
border-bottom: 1px solid #3eaf7c;
}
.details pre.sf-dump {
white-space: pre;
word-wrap: inherit;
}
.details pre.sf-dump,
.details pre.sf-dump .sf-dump-num,
.details pre.sf-dump .sf-dump-const,
.details pre.sf-dump .sf-dump-str,
.details pre.sf-dump .sf-dump-note,
.details pre.sf-dump .sf-dump-ref,
.details pre.sf-dump .sf-dump-public,
.details pre.sf-dump .sf-dump-protected,
.details pre.sf-dump .sf-dump-private,
.details pre.sf-dump .sf-dump-meta,
.details pre.sf-dump .sf-dump-key,
.details pre.sf-dump .sf-dump-index {
color: #463c54;
}
.left-panel {
width: 30%;
background: #001e26;
color: white;
}
.frames-description {
background: #001318;
padding: 8px 15px;
color: #a29d9d;
font-size: 11px;
}
.frames-description.frames-description-application {
text-align: center;
font-size: 12px;
}
.frames-container.frames-container-application .frame:not(.frame-application) {
display: none;
}
.frames-tab {
color: #a29d9d;
display: inline-block;
padding: 4px 8px;
margin: 0 2px;
border-radius: 3px;
}
.frames-tab.frames-tab-active {
background-color: #2a2a2a;
color: #bebebe;
}
.frame {
padding: 14px;
cursor: pointer;
transition: all 0.1s ease;
background: #001e26;
color: white;
}
.frame:not(:last-child) {
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.frame.active {
box-shadow: inset -5px 0 0 0 #3eaf7c;
color: #3eaf7c;
background: #001318;
}
.frame:not(.active):hover {
background: #001318;
}
.frame-method-info {
margin-bottom: 10px;
}
.frame-class,
.frame-function,
.frame-index {
font-size: 14px;
}
.frame-index {
float: left;
}
.frame-method-info {
margin-left: 24px;
}
.frame-index {
font-size: 11px;
color: #a29d9d;
background-color: rgba(0, 0, 0, 0.05);
height: 18px;
width: 18px;
line-height: 18px;
border-radius: 5px;
padding: 0 1px 0 1px;
text-align: center;
display: inline-block;
}
.frame-application .frame-index {
background-color: #2a2a2a;
color: #bebebe;
}
.frame-file {
font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas,
"Lucida Console", monospace;
color: #a29d9d;
}
.frame-file .editor-link {
color: #a29d9d;
}
.frame-line {
font-weight: bold;
}
.frame-line:before {
content: ":";
}
.frame-code {
padding: 5px;
background: #012a35;
display: none;
}
.frame-code.active {
display: block;
}
.frame-code .frame-file {
color: #a29d9d;
padding: 12px 6px;
border-bottom: none;
}
pre[class*="language-"].code-block {
background: #001e26;
}
.code-block {
padding: 10px;
margin: 0;
border-radius: 6px;
box-shadow: 0 3px 0 rgba(0, 0, 0, 0.05), 0 10px 30px rgba(0, 0, 0, 0.05),
inset 0 0 1px 0 rgba(255, 255, 255, 0.07);
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
}
.linenums {
margin: 0;
margin-left: 10px;
}
.frame-comments {
border-top: none;
margin-top: 15px;
font-size: 12px;
}
.frame-comments.empty {
}
.frame-comments.empty:before {
content: "No comments for this stack frame.";
font-weight: 300;
color: #a29d9d;
}
.frame-comment {
padding: 10px;
color: #e3e3e3;
border-radius: 6px;
background-color: rgba(255, 255, 255, 0.05);
}
.frame-comment a {
font-weight: bold;
text-decoration: none;
}
.frame-comment a:hover {
color: #4bb1b1;
}
.frame-comment:not(:last-child) {
border-bottom: 1px dotted rgba(0, 0, 0, 0.3);
}
.frame-comment-context {
font-size: 10px;
color: white;
}
.delimiter {
display: inline-block;
}
.data-table-container label {
font-size: 16px;
color: #3eaf7c;
font-weight: bold;
margin: 10px 0;
display: block;
margin-bottom: 5px;
padding-bottom: 5px;
}
.data-table {
width: 100%;
margin-bottom: 30px;
}
.data-table tbody {
font: 13px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas,
"Lucida Console", monospace;
}
.data-table thead {
display: none;
}
.data-table tr {
padding: 5px 0;
}
.data-table td:first-child {
width: 20%;
min-width: 130px;
overflow: hidden;
font-weight: 700;
color: #b4c5d6;
padding-right: 5px;
}
.data-table td:last-child {
width: 80%;
-ms-word-break: break-all;
word-break: break-all;
word-break: break-word;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
.data-table span.empty {
color: #cacaca;
font-weight: 300;
}
.data-table label.empty {
display: inline;
}
.handler {
padding: 4px 0;
font: 14px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas,
"Lucida Console", monospace;
}
#plain-exception {
display: none;
}
.rightButton {
cursor: pointer;
border: 0;
opacity: 0.8;
background: none;
color: rgba(255, 255, 255, 0.1);
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1);
border-radius: 3px;
outline: none !important;
}
.rightButton:hover {
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.3);
}
/* inspired by githubs kbd styles */
kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #fcfcfc;
border-color: #ccc #ccc #bbb;
border-image: none;
border-style: solid;
border-width: 1px;
color: #555;
display: inline-block;
font-size: 11px;
line-height: 10px;
padding: 3px 5px;
vertical-align: middle;
}
/* == Media queries */
/* Expand the spacing in the details section */
@media (min-width: 1000px) {
.details,
.frame-code {
padding: 20px 40px;
}
.details-container {
left: 32%;
width: 68%;
}
.frames-container {
margin: 5px;
}
.left-panel {
width: 32%;
}
}
/* Stack panels */
@media (max-width: 600px) {
.panel {
position: static;
width: 100%;
}
}
/* Stack details tables */
@media (max-width: 400px) {
.data-table,
.data-table tbody,
.data-table tbody tr,
.data-table tbody td {
display: block;
width: 100%;
}
.data-table tbody tr:first-child {
padding-top: 0;
}
.data-table tbody td:first-child,
.data-table tbody td:last-child {
padding-left: 0;
padding-right: 0;
}
.data-table tbody td:last-child {
padding-top: 3px;
}
}
.tooltipped {
position: relative;
}
.tooltipped:after {
position: absolute;
z-index: 1000000;
display: none;
padding: 5px 8px;
color: #fff;
text-align: center;
text-decoration: none;
text-shadow: none;
text-transform: none;
letter-spacing: normal;
word-wrap: break-word;
white-space: pre;
pointer-events: none;
content: attr(aria-label);
background: rgba(0, 0, 0, 0.8);
border-radius: 3px;
-webkit-font-smoothing: subpixel-antialiased;
}
.tooltipped:before {
position: absolute;
z-index: 1000001;
display: none;
width: 0;
height: 0;
color: rgba(0, 0, 0, 0.8);
pointer-events: none;
content: "";
border: 5px solid transparent;
}
.tooltipped:hover:before,
.tooltipped:hover:after,
.tooltipped:active:before,
.tooltipped:active:after,
.tooltipped:focus:before,
.tooltipped:focus:after {
display: inline-block;
text-decoration: none;
}
.tooltipped-s:after {
top: 100%;
right: 50%;
margin-top: 5px;
}
.tooltipped-s:before {
top: auto;
right: 50%;
bottom: -5px;
margin-right: -5px;
border-bottom-color: rgba(0, 0, 0, 0.8);
}
pre.sf-dump {
padding: 0px !important;
margin: 0px !important;
}
.search-for-help {
width: 85%;
padding: 0;
margin: 10px 0;
list-style-type: none;
display: inline-block;
}
.search-for-help li {
display: inline-block;
margin-right: 5px;
}
.search-for-help li:last-child {
margin-right: 0;
}
.search-for-help li a {
}
.search-for-help li a i {
width: 16px;
height: 16px;
overflow: hidden;
display: block;
}
.search-for-help li a svg {
fill: #fff;
}
.search-for-help li a svg path {
background-size: contain;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,184 @@
Zepto(function($) {
var $leftPanel = $('.left-panel');
var $frameContainer = $('.frames-container');
var $appFramesTab = $('#application-frames-tab');
var $allFramesTab = $('#all-frames-tab');
var $container = $('.details-container');
var $activeLine = $frameContainer.find('.frame.active');
var $activeFrame = $container.find('.frame-code.active');
var $ajaxEditors = $('.editor-link[data-ajax]');
var $header = $('header');
$header.on('mouseenter', function () {
if ($header.find('.exception').height() >= 145) {
$header.addClass('header-expand');
}
});
$header.on('mouseleave', function () {
$header.removeClass('header-expand');
});
/*
* add prettyprint classes to our current active codeblock
* run prettyPrint() to highlight the active code
* scroll to the line when prettyprint is done
* highlight the current line
*/
var renderCurrentCodeblock = function(id) {
Prism.highlightAll();
highlightCurrentLine();
}
/*
* Highlight the active and neighboring lines for the current frame
* Adjust the offset to make sure that line is veritcally centered
*/
var highlightCurrentLine = function() {
// We show more code than needed, purely for proper syntax highlighting
// Lets hide a big chunk of that code and then scroll the remaining block
$activeFrame.find('.code-block').first().css({
maxHeight: 345,
overflow: 'hidden',
}).scrollTop(999);
$container.scrollTop(0);
}
/*
* click handler for loading codeblocks
*/
$frameContainer.on('click', '.frame', function() {
var $this = $(this);
var id = /frame\-line\-([\d]*)/.exec($this.attr('id'))[1];
var $codeFrame = $('#frame-code-' + id);
if ($codeFrame) {
$activeLine.removeClass('active');
$activeFrame.removeClass('active');
$this.addClass('active');
$codeFrame.addClass('active');
$activeLine = $this;
$activeFrame = $codeFrame;
renderCurrentCodeblock(id);
}
});
var clipboard = new Clipboard('.clipboard');
var showTooltip = function(elem, msg) {
elem.classList.add('tooltipped', 'tooltipped-s');
elem.setAttribute('aria-label', msg);
};
clipboard.on('success', function(e) {
e.clearSelection();
showTooltip(e.trigger, 'Copied!');
});
clipboard.on('error', function(e) {
showTooltip(e.trigger, fallbackMessage(e.action));
});
var btn = document.querySelector('.clipboard');
btn.addEventListener('mouseleave', function(e) {
e.currentTarget.classList.remove('tooltipped', 'tooltipped-s');
e.currentTarget.removeAttribute('aria-label');
});
function fallbackMessage(action) {
var actionMsg = '';
var actionKey = (action === 'cut' ? 'X' : 'C');
if (/Mac/i.test(navigator.userAgent)) {
actionMsg = 'Press ⌘-' + actionKey + ' to ' + action;
} else {
actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action;
}
return actionMsg;
}
function scrollIntoView($node, $parent) {
var nodeOffset = $node.offset();
var nodeTop = nodeOffset.top;
var nodeBottom = nodeTop + nodeOffset.height;
var parentScrollTop = $parent.scrollTop();
var parentHeight = $parent.height();
if (nodeTop < 0) {
$parent.scrollTop(parentScrollTop + nodeTop);
} else if (nodeBottom > parentHeight) {
$parent.scrollTop(parentScrollTop + nodeBottom - parentHeight);
}
}
$(document).on('keydown', function(e) {
var applicationFrames = $frameContainer.hasClass('frames-container-application'),
frameClass = applicationFrames ? '.frame.frame-application' : '.frame';
if(e.ctrlKey || e.which === 74 || e.which === 75) {
// CTRL+Arrow-UP/k and Arrow-Down/j support:
// 1) select the next/prev element
// 2) make sure the newly selected element is within the view-scope
// 3) focus the (right) container, so arrow-up/down (without ctrl) scroll the details
if (e.which === 38 /* arrow up */ || e.which === 75 /* k */) {
$activeLine.prev(frameClass).click();
scrollIntoView($activeLine, $leftPanel);
$container.focus();
e.preventDefault();
} else if (e.which === 40 /* arrow down */ || e.which === 74 /* j */) {
$activeLine.next(frameClass).click();
scrollIntoView($activeLine, $leftPanel);
$container.focus();
e.preventDefault();
}
} else if (e.which == 78 /* n */) {
if ($appFramesTab.length) {
setActiveFramesTab($('.frames-tab:not(.frames-tab-active)'));
}
}
});
// Render late enough for highlightCurrentLine to be ready
renderCurrentCodeblock();
// Avoid to quit the page with some protocol (e.g. IntelliJ Platform REST API)
$ajaxEditors.on('click', function(e){
e.preventDefault();
$.get(this.href);
});
// Symfony VarDumper: Close the by default expanded objects
$('.sf-dump-expanded')
.removeClass('sf-dump-expanded')
.addClass('sf-dump-compact');
$('.sf-dump-toggle span').html('&#9654;');
// Make the given frames-tab active
function setActiveFramesTab($tab) {
$tab.addClass('frames-tab-active');
if ($tab.attr('id') == 'application-frames-tab') {
$frameContainer.addClass('frames-container-application');
$allFramesTab.removeClass('frames-tab-active');
} else {
$frameContainer.removeClass('frames-container-application');
$appFramesTab.removeClass('frames-tab-active');
}
}
$('a.frames-tab').on('click', function(e) {
e.preventDefault();
setActiveFramesTab($(this));
});
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,42 @@
<?php /* List data-table values, i.e: $_SERVER, $_GET, .... */ ?>
<div class="details">
<h2 class="details-heading">Environment &amp; details:</h2>
<div class="data-table-container" id="data-tables">
<?php foreach ($tables as $label => $data): ?>
<div class="data-table" id="sg-<?php echo $tpl->escape($tpl->slug($label)) ?>">
<?php if (!empty($data)): ?>
<label><?php echo $tpl->escape($label) ?></label>
<table class="data-table">
<thead>
<tr>
<td class="data-table-k">Key</td>
<td class="data-table-v">Value</td>
</tr>
</thead>
<?php foreach ($data as $k => $value): ?>
<tr>
<td><?php echo $tpl->escape($k) ?></td>
<td><?php echo $tpl->dump($value) ?></td>
</tr>
<?php endforeach ?>
</table>
<?php else: ?>
<label class="empty"><?php echo $tpl->escape($label) ?></label>
<span class="empty">empty</span>
<?php endif ?>
</div>
<?php endforeach ?>
</div>
<?php /* List registered handlers, in order of first to last registered */ ?>
<div class="data-table-container" id="handlers">
<label>Registered Handlers</label>
<?php foreach ($handlers as $i => $h): ?>
<div class="handler <?php echo ($h === $handler) ? 'active' : ''?>">
<?php echo $i ?>. <?php echo $tpl->escape(get_class($h)) ?>
</div>
<?php endforeach ?>
</div>
</div>

View file

@ -0,0 +1,66 @@
<?php /* Display a code block for all frames in the stack.
* @todo: This should PROBABLY be done on-demand, lest
* we get 200 frames to process. */ ?>
<div class="frame-code-container <?php echo (!$has_frames ? 'empty' : '') ?>">
<?php foreach ($frames as $i => $frame): ?>
<?php $line = $frame->getLine(); ?>
<div class="frame-code <?php echo ($i == 0 ) ? 'active' : '' ?>" id="frame-code-<?php echo $i ?>">
<div class="frame-file">
<?php $filePath = $frame->getFile(); ?>
<?php if ($filePath && $editorHref = $handler->getEditorHref($filePath, (int) $line)): ?>
<a href="<?php echo $editorHref ?>" class="editor-link"<?php echo ($handler->getEditorAjax($filePath, (int) $line) ? ' data-ajax' : '') ?>>
Open:
<strong><?php echo $tpl->breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?></strong>
</a>
<?php else: ?>
<strong><?php echo $tpl->breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?></strong>
<?php endif ?>
</div>
<?php
// Do nothing if there's no line to work off
if ($line !== null):
// the $line is 1-indexed, we nab -1 where needed to account for this
$range = $frame->getFileLines($line - 20, 40);
// getFileLines can return null if there is no source code
if ($range):
$range = array_map(function ($line) { return empty($line) ? ' ' : $line;}, $range);
$start = key($range) + 1;
$code = join("\n", $range);
?>
<pre class="code-block line-numbers"
data-line="<?php echo $line ?>"
data-start="<?php echo $start ?>"
><code class="language-php"><?php echo $tpl->escape($code) ?></code></pre>
<?php endif ?>
<?php endif ?>
<?php $frameArgs = $tpl->dumpArgs($frame); ?>
<?php if ($frameArgs): ?>
<div class="frame-file">
Arguments
</div>
<div id="frame-code-args-<?=$i?>" class="code-block frame-args">
<?php echo $frameArgs; ?>
</div>
<?php endif ?>
<?php
// Append comments for this frame
$comments = $frame->getComments();
?>
<div class="frame-comments <?php echo empty($comments) ? 'empty' : '' ?>">
<?php foreach ($comments as $commentNo => $comment): ?>
<?php extract($comment) ?>
<div class="frame-comment" id="comment-<?php echo $i . '-' . $commentNo ?>">
<span class="frame-comment-context"><?php echo $tpl->escape($context) ?></span>
<?php echo $tpl->escapeButPreserveUris($comment) ?>
</div>
<?php endforeach ?>
</div>
</div>
<?php endforeach ?>
</div>

View file

@ -0,0 +1,17 @@
<?php /* List file names & line numbers for all stack frames;
clicking these links/buttons will display the code view
for that particular frame */ ?>
<?php foreach ($frames as $i => $frame): ?>
<div class="frame <?php echo ($i == 0 ? 'active' : '') ?> <?php echo ($frame->isApplication() ? 'frame-application' : '') ?>" id="frame-line-<?php echo $i ?>">
<span class="frame-index"><?php echo (count($frames) - $i - 1) ?></span>
<div class="frame-method-info">
<span class="frame-class"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getClass() ?: '')) ?></span>
<span class="frame-function"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getFunction() ?: '')) ?></span>
</div>
<div class="frame-file">
<?php echo $frame->getFile() ? $tpl->breakOnDelimiter('/', $tpl->shorten($tpl->escape($frame->getFile()))) : '<#unknown>' ?><!--
--><span class="frame-line"><?php echo (int) $frame->getLine() ?></span>
</div>
</div>
<?php endforeach;

View file

@ -0,0 +1,3 @@
<div class="frames-container <?php echo $active_frames_tab == 'application' ? 'frames-container-application' : '' ?>">
<?php $tpl->render($frame_list) ?>
</div>

View file

@ -0,0 +1,14 @@
<div class="frames-description <?php echo $has_frames_tabs ? 'frames-description-application' : '' ?>">
<?php if ($has_frames_tabs): ?>
<a href="#" id="application-frames-tab" class="frames-tab <?php echo $active_frames_tab == 'application' ? 'frames-tab-active' : '' ?>">
Application frames (<?php echo $frames->countIsApplication() ?>)
</a>
<a href="#" id="all-frames-tab" class="frames-tab <?php echo $active_frames_tab == 'all' ? 'frames-tab-active' : '' ?>">
All frames (<?php echo count($frames) ?>)
</a>
<?php else: ?>
<span>
Stack frames (<?php echo count($frames) ?>)
</span>
<?php endif; ?>
</div>

View file

@ -0,0 +1,103 @@
<div class="exception">
<img src="https://leafphp.dev/logo-circle.png" style="width: 50px;" alt="">
<br><br>
<div class="exc-title">
<?php foreach ($name as $i => $nameSection) : ?>
<?php if ($i == count($name) - 1) : ?>
<span class="exc-title-primary"><?php echo $tpl->escape($nameSection) ?></span>
<?php else : ?>
<?php echo $tpl->escape($nameSection) . ' \\' ?>
<?php endif ?>
<?php endforeach ?>
<?php if ($code) : ?>
<span title="Exception Code">(<?php echo $tpl->escape($code) ?>)</span>
<?php endif ?>
<br>
</div>
<div class="exc-message">
<?php if (!empty($message)) : ?>
<span><?php echo $tpl->escape($message) ?></span>
<?php if (count($previousMessages)) : ?>
<div class="exc-title prev-exc-title">
<span class="exc-title-secondary">Previous exceptions</span>
</div>
<ul>
<?php foreach ($previousMessages as $i => $previousMessage) : ?>
<li>
<?php echo $tpl->escape($previousMessage) ?>
<span class="prev-exc-code">(<?php echo $previousCodes[$i] ?>)</span>
</li>
<?php endforeach; ?>
</ul>
<?php endif ?>
<?php else : ?>
<span class="exc-message-empty-notice">No message</span>
<?php endif ?>
<ul class="search-for-help">
<?php if (!empty($docref_url)) : ?>
<li>
<a rel="noopener noreferrer" target="_blank" href="<?php echo $docref_url; ?>" title="Search for help in the PHP manual.">
<!-- PHP icon by Icons Solid -->
<!-- https://www.iconfinder.com/icons/322421/book_icon -->
<!-- Free for commercial use -->
<svg height="16px" id="Layer_1" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" width="16px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g transform="translate(240 0)">
<path d="M-211,4v26h-24c-1.104,0-2-0.895-2-2s0.896-2,2-2h22V0h-22c-2.209,0-4,1.791-4,4v24c0,2.209,1.791,4,4,4h26V4H-211z M-235,8V2h20v22h-20V8z M-219,6h-12V4h12V6z M-223,10h-8V8h8V10z M-227,14h-4v-2h4V14z" />
</g>
</svg>
</a>
</li>
<?php endif ?>
<li>
<a rel="noopener noreferrer" target="_blank" href="https://google.com/search?q=<?php echo urlencode(implode('\\', $name) . ' ' . $message) ?>" title="Search for help on Google.">
<!-- Google icon by Alfredo H, from https://www.iconfinder.com/alfredoh -->
<!-- Creative Commons (Attribution 3.0 Unported) -->
<!-- http://creativecommons.org/licenses/by/3.0/ -->
<svg class="google" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M457.732 216.625c2.628 14.04 4.063 28.743 4.063 44.098C461.795 380.688 381.48 466 260.205 466c-116.024 0-210-93.977-210-210s93.976-210 210-210c56.703 0 104.076 20.867 140.44 54.73l-59.205 59.197v-.135c-22.046-21.002-50-31.762-81.236-31.762-69.297 0-125.604 58.537-125.604 127.84 0 69.29 56.306 127.97 125.604 127.97 62.87 0 105.653-35.966 114.46-85.313h-114.46v-81.902h197.528z" />
</svg>
</a>
</li>
<li>
<a rel="noopener noreferrer" target="_blank" href="https://duckduckgo.com/?q=<?php echo urlencode(implode('\\', $name) . ' ' . $message) ?>" title="Search for help on DuckDuckGo.">
<!-- DuckDuckGo icon by IconBaandar Team, from https://www.iconfinder.com/iconbaandar -->
<!-- Creative Commons (Attribution 3.0 Unported) -->
<!-- http://creativecommons.org/licenses/by/3.0/ -->
<svg class="duckduckgo" height="16" viewBox="150 150 1675 1675" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M1792 1024c0 204.364-80.472 398.56-224.955 543.04-144.483 144.48-338.68 224.95-543.044 224.95-204.36 0-398.56-80.47-543.04-224.95-144.48-144.482-224.95-338.676-224.95-543.04 0-204.365 80.47-398.562 224.96-543.045C625.44 336.47 819.64 256 1024 256c204.367 0 398.565 80.47 543.05 224.954C1711.532 625.437 1792 819.634 1792 1024zm-270.206 497.787C1654.256 1389.327 1728 1211.36 1728 1024c0-187.363-73.74-365.332-206.203-497.796C1389.332 393.74 1211.363 320 1024 320s-365.33 73.742-497.795 206.205C393.742 658.67 320 836.637 320 1024c0 187.36 73.744 365.326 206.206 497.787C658.67 1654.25 836.638 1727.99 1024 1727.99c187.362 0 365.33-73.74 497.794-206.203z" />
<path d="M1438.64 1177.41c0-.03-.005-.017-.01.004l.01-.004z" />
<path d="M1499.8 976.878c.03-.156-.024-.048-.11.107l.11-.107z" />
<path d="M1105.19 991.642zm-68.013-376.128c-8.087-10.14-18.028-19.965-29.89-29.408-13.29-10.582-29-20.76-47.223-30.443-35.07-18.624-74.482-31.61-115.265-38.046-39.78-6.28-80.84-6.256-120.39.917l1.37 31.562c1.8.164 7.7 3.9 14.36 8.32-20.68 5.94-39.77 14.447-39.48 39.683l.2 17.48 17.3-1.73c29.38-2.95 60.17-2.06 90.32 2.61 9.21 1.42 18.36 3.2 27.38 5.32l-4.33 1.15c-20.45 5.58-38.93 12.52-54.25 20.61-46.28 24.32-75.51 60.85-90.14 108.37-14.14 45.95-14.27 101.81-2.72 166.51l.06.06c15.14 84.57 64.16 316.39 104.11 505.39 19.78 93.59 37.38 176.83 47.14 224.4 3.26 15.84 5.03 31.02 5.52 45.52.3 9.08.09 17.96-.58 26.62-.45 5.8-1.11 11.51-1.96 17.112l31.62 4.75c.71-4.705 1.3-9.494 1.76-14.373 48.964 10.517 99.78 16.05 151.88 16.05 60.68 0 119.61-7.505 175.91-21.64 3.04 6.08 6.08 12.19 9.11 18.32l28.62-14.128c-2.11-4.27-4.235-8.55-6.37-12.84-23.005-46.124-47.498-93.01-68.67-133.534-15.39-29.466-29.01-55.53-39.046-75.58-26.826-53.618-53.637-119.47-68.28-182.368-8.78-37.705-13.128-74.098-10.308-105.627-15.31-6.28-26.69-11.8-31.968-15.59l-.01.015c-14.22-10.2-31.11-28.12-41.82-49.717-8.618-17.376-13.4-37.246-10.147-57.84 3.17-19.84 27.334-46.714 57.843-67.46v-.063c26.554-18.05 58.75-32.506 86.32-34.31 7.835-.51 16.31-1.008 23.99-1.45 33.45-1.95 50.243-2.93 84.475-11.42 10.88-2.697 26.19-6.56 43.53-11.09 2.364-40.7-5.947-87.596-21.04-133.234-22.004-66.53-58.68-131.25-97.627-170.21-12.543-12.55-28.17-22.79-45.9-30.933-16.88-7.753-35.64-13.615-55.436-17.782zm-10.658 178.553s6.77-42.485 58.39-33.977c27.96 4.654 37.89 29.833 37.89 29.833s-25.31-14.46-44.95-14.198c-40.33.53-51.35 18.342-51.35 18.342zm-240.45-18.802c48.49-19.853 72.11 11.298 72.11 11.298s-35.21-15.928-69.46 5.59c-34.19 21.477-32.92 43.452-32.92 43.452s-18.17-40.5 30.26-60.34zm296.5 95.4c0-6.677 2.68-12.694 7.01-17.02 4.37-4.37 10.42-7.074 17.1-7.074 6.73 0 12.79 2.7 17.15 7.05 4.33 4.33 7.01 10.36 7.01 17.05 0 6.74-2.7 12.81-7.07 17.18-4.33 4.33-10.37 7.01-17.1 7.01-6.68 0-12.72-2.69-17.05-7.03-4.36-4.37-7.07-10.43-7.07-17.16zm-268.42 51.27c0-8.535 3.41-16.22 8.93-21.738 5.55-5.55 13.25-8.982 21.81-8.982 8.51 0 16.18 3.415 21.7 8.934 5.55 5.55 8.98 13.25 8.98 21.78 0 8.53-3.44 16.23-8.98 21.79-5.52 5.52-13.19 8.93-21.71 8.93-8.55 0-16.26-3.43-21.82-8.99-5.52-5.52-8.93-13.2-8.93-21.74z" />
<path d="M1102.48 986.34zm390.074-64.347c-28.917-11.34-74.89-12.68-93.32-3.778-11.5 5.567-35.743 13.483-63.565 21.707-25.75 7.606-53.9 15.296-78.15 21.702-17.69 4.67-33.3 8.66-44.4 11.435-34.92 8.76-52.05 9.77-86.17 11.78-7.84.46-16.48.97-24.48 1.5-28.12 1.86-60.97 16.77-88.05 35.4v.06c-31.12 21.4-55.77 49.12-59.01 69.59-3.32 21.24 1.56 41.74 10.35 59.67 10.92 22.28 28.15 40.77 42.66 51.29l.01-.02c5.38 3.9 16.98 9.6 32.6 16.08 26.03 10.79 63.2 23.76 101.25 34.23 43.6 11.99 89.11 21.05 121.69 20.41 34.26-.69 77.73-10.52 114.54-24.67 22.15-8.52 42.21-18.71 56.88-29.58 17.85-13.22 28.7-28.42 28.4-44.74-.07-3.89-.72-7.63-1.97-11.21l-.02.01c-11.6-33.06-50.37-23.59-105.53-10.12-46.86 11.445-107.94 26.365-169.01 20.434-32.56-3.167-54.45-10.61-67.88-20.133-5.96-4.224-9.93-8.67-12.18-13.11-1.96-3.865-2.68-7.84-2.33-11.714.39-4.42 2.17-9.048 5.1-13.57l-.05-.03c7.86-12.118 23.082-9.72 43.93-6.43 25.91 4.08 58.2 9.172 99.013-3.61 39.63-12.378 87.76-29.9 131.184-47.39 42.405-17.08 80.08-34.078 100.74-46.18 25.46-14.87 37.57-29.428 40.59-42.866 2.725-12.152-.89-22.48-8.903-31.07-5.87-6.29-14.254-11.31-23.956-15.115z" />
</svg>
</a>
</li>
<li>
<a rel="noopener noreferrer" target="_blank" href="https://stackoverflow.com/search?q=<?php echo urlencode(implode('\\', $name) . ' ' . $message) ?>" title="Search for help on Stack Overflow.">
<!-- Stack Overflow icon by Picons.me, from https://www.iconfinder.com/Picons -->
<!-- Free for commercial use -->
<svg class="stackoverflow" height="16" viewBox="-1163 1657.697 56.693 56.693" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M-1126.04 1689.533l-16.577-9.778 2.088-3.54 16.578 9.778zM-1127.386 1694.635l-18.586-4.996 1.068-3.97 18.586 4.995zM-1127.824 1700.137l-19.165-1.767.378-4.093 19.165 1.767zM-1147.263 1701.293h19.247v4.11h-19.247z" />
<path d="M-1121.458 1710.947s0 .96-.032.96v.016h-30.796s-.96 0-.96-.016h-.032v-20.03h3.288v16.805h25.244v-16.804h3.288v19.07zM-1130.667 1667.04l10.844 15.903-3.396 2.316-10.843-15.903zM-1118.313 1663.044l3.29 18.963-4.05.703-3.29-18.963z" />
</svg>
</a>
</li>
</ul>
<span id="plain-exception"><?php echo $tpl->escape($plain_exception) ?></span>
<button id="copy-button" class="rightButton clipboard" data-clipboard-text="<?php echo $tpl->escape($plain_exception) ?>" title="Copy exception details to clipboard">
COPY
</button>
<button id="hide-error" class="rightButton" title="Hide error message" onclick="document.getElementsByClassName('Whoops')[0].style.display = 'none';">
HIDE
</button>
</div>
</div>

View file

@ -0,0 +1,3 @@
<header>
<?php $tpl->render($header) ?>
</header>

View file

@ -0,0 +1,34 @@
<?php
/**
* Layout template file for Whoops's pretty error output.
*/
?>
<!DOCTYPE html><?php echo $preface; ?>
<html>
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex,nofollow"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<title><?php echo $tpl->escape($page_title) ?></title>
<style><?php echo $stylesheet ?></style>
<style><?php echo $prismCss ?></style>
</head>
<body>
<div class="Whoops container">
<div class="stack-container">
<?php $tpl->render($panel_left_outer) ?>
<?php $tpl->render($panel_details_outer) ?>
</div>
</div>
<script data-manual><?php echo $prismJs ?></script>
<script><?php echo $zepto ?></script>
<script><?php echo $clipboard ?></script>
<script><?php echo $javascript ?></script>
</body>
</html>

View file

@ -0,0 +1,2 @@
<?php $tpl->render($frame_code) ?>
<?php $tpl->render($env_details) ?>

View file

@ -0,0 +1,3 @@
<div class="panel details-container cf">
<?php $tpl->render($panel_details) ?>
</div>

View file

@ -0,0 +1,4 @@
<?php
$tpl->render($header_outer);
$tpl->render($frames_description);
$tpl->render($frames_container);

View file

@ -0,0 +1,3 @@
<div class="panel left-panel cf <?php echo (!$has_frames ? 'empty' : '') ?>">
<?php $tpl->render($panel_left) ?>
</div>

View file

@ -0,0 +1,556 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception;
use InvalidArgumentException;
use Leaf\Exceptions\ErrorException;
use Leaf\Exceptions\Inspector;
use Leaf\Exception\Handler\CallbackHandler;
use Leaf\Exception\Handler\Handler;
use Leaf\Exception\Handler\HandlerInterface;
use Leaf\Exception\Util\Misc;
use Leaf\Exception\Util\SystemFacade;
use Throwable;
final class Run implements RunInterface
{
/**
* @var bool
*/
private $isRegistered;
/**
* @var bool
*/
private $allowQuit = true;
/**
* @var bool
*/
private $sendOutput = true;
/**
* @var integer|false
*/
private $sendHttpCode = 500;
/**
* @var integer|false
*/
private $sendExitCode = 1;
/**
* @var HandlerInterface[]
*/
private $handlerStack = [];
/**
* @var array
* @psalm-var list<array{patterns: string, levels: int}>
*/
private $silencedPatterns = [];
/**
* @var SystemFacade
*/
private $system;
/**
* In certain scenarios, like in shutdown handler, we can not throw exceptions.
*
* @var bool
*/
private $canThrowExceptions = true;
public function __construct(SystemFacade $system = null)
{
$this->system = $system ?: new SystemFacade;
}
/**
* Explicitly request your handler runs as the last of all currently registered handlers.
*
* @param HandlerInterface $handler
*
* @return Run
*/
public function appendHandler($handler)
{
array_unshift($this->handlerStack, $this->resolveHandler($handler));
return $this;
}
/**
* Explicitly request your handler runs as the first of all currently registered handlers.
*
* @param HandlerInterface $handler
*
* @return Run
*/
public function prependHandler($handler)
{
return $this->pushHandler($handler);
}
/**
* Register your handler as the last of all currently registered handlers (to be executed first).
* Prefer using appendHandler and prependHandler for clarity.
*
* @param Callable|HandlerInterface $handler
*
* @return Run
*
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface.
*/
public function pushHandler($handler)
{
$this->handlerStack[] = $this->resolveHandler($handler);
return $this;
}
/**
* Removes and returns the last handler pushed to the handler stack.
*
* @see Run::removeFirstHandler(), Run::removeLastHandler()
*
* @return HandlerInterface|null
*/
public function popHandler()
{
return array_pop($this->handlerStack);
}
/**
* Removes the first handler.
*
* @return void
*/
public function removeFirstHandler()
{
array_pop($this->handlerStack);
}
/**
* Removes the last handler.
*
* @return void
*/
public function removeLastHandler()
{
array_shift($this->handlerStack);
}
/**
* Returns an array with all handlers, in the order they were added to the stack.
*
* @return array
*/
public function getHandlers()
{
return $this->handlerStack;
}
/**
* Clears all handlers in the handlerStack, including the default PrettyPage handler.
*
* @return Run
*/
public function clearHandlers()
{
$this->handlerStack = [];
return $this;
}
/**
* Registers this instance as an error handler.
*
* @return Run
*/
public function register()
{
if (empty($this->getHandlers())) {
$this->pushHandler(new \Leaf\Exception\Handler\PrettyPageHandler);
}
if (!$this->isRegistered) {
// Workaround PHP bug 42098
// https://bugs.php.net/bug.php?id=42098
class_exists("\\Leaf\\Exceptions\\ErrorException");
class_exists("\\Leaf\\Exceptions\\FrameCollection");
class_exists("\\Leaf\\Exceptions\\Frame");
class_exists("\\Leaf\\Exceptions\\Inspector");
$this->system->setErrorHandler([$this, self::ERROR_HANDLER]);
$this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]);
$this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]);
$this->isRegistered = true;
}
return $this;
}
/**
* Unregisters all handlers registered by this Whoops\Run instance.
*
* @return Run
*/
public function unregister()
{
if ($this->isRegistered) {
$this->system->restoreExceptionHandler();
$this->system->restoreErrorHandler();
$this->isRegistered = false;
}
return $this;
}
/**
* Should Whoops allow Handlers to force the script to quit?
*
* @param bool|int $exit
*
* @return bool
*/
public function allowQuit($exit = null)
{
if (func_num_args() == 0) {
return $this->allowQuit;
}
return $this->allowQuit = (bool) $exit;
}
/**
* Silence particular errors in particular files.
*
* @param array|string $patterns List or a single regex pattern to match.
* @param int $levels Defaults to E_STRICT | E_DEPRECATED.
*
* @return Run
*/
public function silenceErrorsInPaths($patterns, $levels = 10240)
{
$this->silencedPatterns = array_merge(
$this->silencedPatterns,
array_map(
function ($pattern) use ($levels) {
return [
"pattern" => $pattern,
"levels" => $levels,
];
},
(array) $patterns
)
);
return $this;
}
/**
* Returns an array with silent errors in path configuration.
*
* @return array
*/
public function getSilenceErrorsInPaths()
{
return $this->silencedPatterns;
}
/**
* Should Whoops send HTTP error code to the browser if possible?
* Whoops will by default send HTTP code 500, but you may wish to
* use 502, 503, or another 5xx family code.
*
* @param bool|int $code
*
* @return int|false
*
* @throws InvalidArgumentException
*/
public function sendHttpCode($code = null)
{
if (func_num_args() == 0) {
return $this->sendHttpCode;
}
if (!$code) {
return $this->sendHttpCode = false;
}
if ($code === true) {
$code = 500;
}
if ($code < 400 || 600 <= $code) {
throw new InvalidArgumentException(
"Invalid status code '$code', must be 4xx or 5xx"
);
}
return $this->sendHttpCode = $code;
}
/**
* Should Whoops exit with a specific code on the CLI if possible?
* Whoops will exit with 1 by default, but you can specify something else.
*
* @param int $code
*
* @return int
*
* @throws InvalidArgumentException
*/
public function sendExitCode($code = null)
{
if (func_num_args() == 0) {
return $this->sendExitCode;
}
if ($code < 0 || 255 <= $code) {
throw new InvalidArgumentException(
"Invalid status code '$code', must be between 0 and 254"
);
}
return $this->sendExitCode = (int) $code;
}
/**
* Should Whoops push output directly to the client?
* If this is false, output will be returned by handleException.
*
* @param bool|int $send
*
* @return bool
*/
public function writeToOutput($send = null)
{
if (func_num_args() == 0) {
return $this->sendOutput;
}
return $this->sendOutput = (bool) $send;
}
/**
* Handles an exception, ultimately generating a Whoops error page.
*
* @param Throwable $exception
*
* @return string Output generated by handlers.
*/
public function handleException($exception)
{
// Walk the registered handlers in the reverse order
// they were registered, and pass off the exception
$inspector = $this->getInspector($exception);
// Capture output produced while handling the exception,
// we might want to send it straight away to the client,
// or return it silently.
$this->system->startOutputBuffering();
// Just in case there are no handlers:
$handlerResponse = null;
$handlerContentType = null;
try {
foreach (array_reverse($this->handlerStack) as $handler) {
$handler->setRun($this);
$handler->setInspector($inspector);
$handler->setException($exception);
// The HandlerInterface does not require an Exception passed to handle()
// and neither of our bundled handlers use it.
// However, 3rd party handlers may have already relied on this parameter,
// and removing it would be possibly breaking for users.
$handlerResponse = $handler->handle($exception);
// Collect the content type for possible sending in the headers.
$handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null;
if (in_array($handlerResponse, [Handler::LAST_HANDLER, Handler::QUIT])) {
// The Handler has handled the exception in some way, and
// wishes to quit execution (Handler::QUIT), or skip any
// other handlers (Handler::LAST_HANDLER). If $this->allowQuit
// is false, Handler::QUIT behaves like Handler::LAST_HANDLER
break;
}
}
$willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
} finally {
$output = $this->system->cleanOutputBuffer();
}
// If we're allowed to, send output generated by handlers directly
// to the output, otherwise, and if the script doesn't quit, return
// it so that it may be used by the caller
if ($this->writeToOutput()) {
// @todo Might be able to clean this up a bit better
if ($willQuit) {
// Cleanup all other output buffers before sending our output:
while ($this->system->getOutputBufferLevel() > 0) {
$this->system->endOutputBuffering();
}
// Send any headers if needed:
if (Misc::canSendHeaders() && $handlerContentType) {
header("Content-Type: {$handlerContentType}");
}
}
$this->writeToOutputNow($output);
}
if ($willQuit) {
// HHVM fix for https://github.com/facebook/hhvm/issues/4055
$this->system->flushOutputBuffer();
$this->system->stopExecution(
$this->sendExitCode()
);
}
return $output;
}
/**
* Converts generic PHP errors to \ErrorException instances, before passing them off to be handled.
*
* This method MUST be compatible with set_error_handler.
*
* @param int $level
* @param string $message
* @param string|null $file
* @param int|null $line
*
* @return bool
*
* @throws ErrorException
*/
public function handleError($level, $message, $file = null, $line = null)
{
if ($level&$this->system->getErrorReportingLevel()) {
foreach ($this->silencedPatterns as $entry) {
$pathMatches = (bool) preg_match($entry["pattern"], $file);
$levelMatches = $level&$entry["levels"];
if ($pathMatches && $levelMatches) {
// Ignore the error, abort handling
// See https://github.com/filp/whoops/issues/418
return true;
}
}
// XXX we pass $level for the "code" param only for BC reasons.
// see https://github.com/filp/whoops/issues/267
$exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line);
$app = \Leaf\Config::get("app")["instance"];
if ($app && $app->config("log.enabled")) {
$app->logger()->error($exception);
}
if ($this->canThrowExceptions) {
throw $exception;
} else {
$this->handleException($exception);
}
// Do not propagate errors which were already handled by Whoops.
return true;
}
// Propagate error to the next handler, allows error_get_last() to
// work on silenced errors.
return false;
}
/**
* Special case to deal with Fatal errors and the like.
*
* @return void
*/
public function handleShutdown()
{
// If we reached this step, we are in shutdown handler.
// An exception thrown in a shutdown handler will not be propagated
// to the exception handler. Pass that information along.
$this->canThrowExceptions = false;
$error = $this->system->getLastError();
if ($error && Misc::isLevelFatal($error['type'])) {
// If there was a fatal error,
// it was not handled in handleError yet.
$this->allowQuit = false;
$this->handleError(
$error['type'],
$error['message'],
$error['file'],
$error['line']
);
}
}
/**
* @param Throwable $exception
*
* @return Inspector
*/
private function getInspector($exception)
{
return new Inspector($exception);
}
/**
* Resolves the giving handler.
*
* @param HandlerInterface $handler
*
* @return HandlerInterface
*
* @throws InvalidArgumentException
*/
private function resolveHandler($handler)
{
if (is_callable($handler)) {
$handler = new CallbackHandler($handler);
}
if (!$handler instanceof HandlerInterface) {
throw new InvalidArgumentException(
"Handler must be a callable, or instance of "
. "Leaf\\Exception\\Handler\\HandlerInterface"
);
}
return $handler;
}
/**
* Echo something to the browser.
*
* @param string $output
*
* @return Run
*/
private function writeToOutputNow($output)
{
if ($this->sendHttpCode() && Misc::canSendHeaders()) {
$this->system->setHttpResponseCode(
$this->sendHttpCode()
);
}
echo $output;
return $this;
}
}

View file

@ -0,0 +1,140 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception;
use InvalidArgumentException;
use Leaf\Exceptions\ErrorException;
use Leaf\Exception\Handler\HandlerInterface;
interface RunInterface
{
const EXCEPTION_HANDLER = "handleException";
const ERROR_HANDLER = "handleError";
const SHUTDOWN_HANDLER = "handleShutdown";
/**
* Pushes a handler to the end of the stack
*
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface
* @param Callable|HandlerInterface $handler
* @return Run
*/
public function pushHandler($handler);
/**
* Removes the last handler in the stack and returns it.
* Returns null if there"s nothing else to pop.
*
* @return null|HandlerInterface
*/
public function popHandler();
/**
* Returns an array with all handlers, in the
* order they were added to the stack.
*
* @return array
*/
public function getHandlers();
/**
* Clears all handlers in the handlerStack, including
* the default PrettyPage handler.
*
* @return Run
*/
public function clearHandlers();
/**
* Registers this instance as an error handler.
*
* @return Run
*/
public function register();
/**
* Unregisters all handlers registered by this Whoops\Run instance
*
* @return Run
*/
public function unregister();
/**
* Should Whoops allow Handlers to force the script to quit?
*
* @param bool|int $exit
* @return bool
*/
public function allowQuit($exit = null);
/**
* Silence particular errors in particular files
*
* @param array|string $patterns List or a single regex pattern to match
* @param int $levels Defaults to E_STRICT | E_DEPRECATED
* @return \Leaf\Exception\Run
*/
public function silenceErrorsInPaths($patterns, $levels = 10240);
/**
* Should Whoops send HTTP error code to the browser if possible?
* Whoops will by default send HTTP code 500, but you may wish to
* use 502, 503, or another 5xx family code.
*
* @param bool|int $code
* @return int|false
*/
public function sendHttpCode($code = null);
/**
* Should Whoops exit with a specific code on the CLI if possible?
* Whoops will exit with 1 by default, but you can specify something else.
*
* @param int $code
* @return int
*/
public function sendExitCode($code = null);
/**
* Should Whoops push output directly to the client?
* If this is false, output will be returned by handleException
*
* @param bool|int $send
* @return bool
*/
public function writeToOutput($send = null);
/**
* Handles an exception, ultimately generating a Whoops error
* page.
*
* @param \Throwable $exception
* @return string Output generated by handlers
*/
public function handleException($exception);
/**
* Converts generic PHP errors to \ErrorException
* instances, before passing them off to be handled.
*
* This method MUST be compatible with set_error_handler.
*
* @param int $level
* @param string $message
* @param string $file
* @param int $line
*
* @return bool
* @throws ErrorException
*/
public function handleError($level, $message, $file = null, $line = null);
/**
* Special case to deal with Fatal errors and the like.
*/
public function handleShutdown();
}

View file

@ -0,0 +1,36 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Util;
/**
* Used as output callable for Symfony\Component\VarDumper\Dumper\HtmlDumper::dump()
*
* @see TemplateHelper::dump()
*/
class HtmlDumperOutput
{
private $output;
public function __invoke($line, $depth)
{
// A negative depth means "end of dump"
if ($depth >= 0) {
// Adds a two spaces indentation to the line
$this->output .= str_repeat(' ', $depth) . $line . "\n";
}
}
public function getOutput()
{
return $this->output;
}
public function clear()
{
$this->output = null;
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Util;
class Misc
{
/**
* Can we at this point in time send HTTP headers?
*
* Currently this checks if we are even serving an HTTP request,
* as opposed to running from a command line.
*
* If we are serving an HTTP request, we check if it's not too late.
*
* @return bool
*/
public static function canSendHeaders()
{
return isset($_SERVER["REQUEST_URI"]) && !headers_sent();
}
public static function isAjaxRequest()
{
return (
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
}
/**
* Check, if possible, that this execution was triggered by a command line.
* @return bool
*/
public static function isCommandLine()
{
return PHP_SAPI == 'cli';
}
/**
* Translate ErrorException code into the represented constant.
*
* @param int $error_code
* @return string
*/
public static function translateErrorCode($error_code)
{
$constants = get_defined_constants(true);
if (array_key_exists('Core', $constants)) {
foreach ($constants['Core'] as $constant => $value) {
if (substr($constant, 0, 2) == 'E_' && $value == $error_code) {
return $constant;
}
}
}
return "E_UNKNOWN";
}
/**
* Determine if an error level is fatal (halts execution)
*
* @param int $level
* @return bool
*/
public static function isLevelFatal($level)
{
$errors = E_ERROR;
$errors |= E_PARSE;
$errors |= E_CORE_ERROR;
$errors |= E_CORE_WARNING;
$errors |= E_COMPILE_ERROR;
$errors |= E_COMPILE_WARNING;
return ($level & $errors) > 0;
}
}

View file

@ -0,0 +1,144 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Util;
class SystemFacade
{
/**
* Turns on output buffering.
*
* @return bool
*/
public function startOutputBuffering()
{
return ob_start();
}
/**
* @param callable $handler
* @param int $types
*
* @return callable|null
*/
public function setErrorHandler(callable $handler, $types = 'use-php-defaults')
{
// Since PHP 5.4 the constant E_ALL contains all errors (even E_STRICT)
if ($types === 'use-php-defaults') {
$types = E_ALL;
}
return set_error_handler($handler, $types);
}
/**
* @param callable $handler
*
* @return callable|null
*/
public function setExceptionHandler(callable $handler)
{
return set_exception_handler($handler);
}
/**
* @return void
*/
public function restoreExceptionHandler()
{
restore_exception_handler();
}
/**
* @return void
*/
public function restoreErrorHandler()
{
restore_error_handler();
}
/**
* @param callable $function
*
* @return void
*/
public function registerShutdownFunction(callable $function)
{
register_shutdown_function($function);
}
/**
* @return string|false
*/
public function cleanOutputBuffer()
{
return ob_get_clean();
}
/**
* @return int
*/
public function getOutputBufferLevel()
{
return ob_get_level();
}
/**
* @return bool
*/
public function endOutputBuffering()
{
return ob_end_clean();
}
/**
* @return void
*/
public function flushOutputBuffer()
{
flush();
}
/**
* @return int
*/
public function getErrorReportingLevel()
{
return error_reporting();
}
/**
* @return array|null
*/
public function getLastError()
{
return error_get_last();
}
/**
* @param int $httpCode
*
* @return int
*/
public function setHttpResponseCode($httpCode)
{
if (!headers_sent()) {
// Ensure that no 'location' header is present as otherwise this
// will override the HTTP code being set here, and mask the
// expected error page.
header_remove('location');
}
return http_response_code($httpCode);
}
/**
* @param int $exitStatus
*/
public function stopExecution($exitStatus)
{
exit($exitStatus);
}
}

View file

@ -0,0 +1,352 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exception\Util;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Leaf\Exceptions\Frame;
/**
* Exposes useful tools for working with/in templates
*/
class TemplateHelper
{
/**
* An array of variables to be passed to all templates
* @var array
*/
private $variables = [];
/**
* @var HtmlDumper
*/
private $htmlDumper;
/**
* @var HtmlDumperOutput
*/
private $htmlDumperOutput;
/**
* @var AbstractCloner
*/
private $cloner;
/**
* @var string
*/
private $applicationRootPath;
public function __construct()
{
// root path for ordinary composer projects
$this->applicationRootPath = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
}
/**
* Escapes a string for output in an HTML document
*
* @param string $raw
* @return string
*/
public function escape($raw)
{
$flags = ENT_QUOTES;
// HHVM has all constants defined, but only ENT_IGNORE
// works at the moment
if (defined("ENT_SUBSTITUTE") && !defined("HHVM_VERSION")) {
$flags |= ENT_SUBSTITUTE;
} else {
// This is for 5.3.
// The documentation warns of a potential security issue,
// but it seems it does not apply in our case, because
// we do not blacklist anything anywhere.
$flags |= ENT_IGNORE;
}
$raw = str_replace(chr(9), ' ', $raw);
return htmlspecialchars($raw, $flags, "UTF-8");
}
/**
* Escapes a string for output in an HTML document, but preserves
* URIs within it, and converts them to clickable anchor elements.
*
* @param string $raw
* @return string
*/
public function escapeButPreserveUris($raw)
{
$escaped = $this->escape($raw);
return preg_replace(
"@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@",
"<a href=\"$1\" target=\"_blank\" rel=\"noreferrer noopener\">$1</a>",
$escaped
);
}
/**
* Makes sure that the given string breaks on the delimiter.
*
* @param string $delimiter
* @param string $s
* @return string
*/
public function breakOnDelimiter($delimiter, $s)
{
$parts = explode($delimiter, $s);
foreach ($parts as &$part) {
$part = '<span class="delimiter">' . $part . '</span>';
}
return implode($delimiter, $parts);
}
/**
* Replace the part of the path that all files have in common.
*
* @param string $path
* @return string
*/
public function shorten($path)
{
if ($this->applicationRootPath != "/") {
$path = str_replace($this->applicationRootPath, '&hellip;', $path);
}
return $path;
}
private function getDumper()
{
if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
$this->htmlDumperOutput = new HtmlDumperOutput();
// re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump.
$this->htmlDumper = new HtmlDumper($this->htmlDumperOutput);
$styles = [
'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal',
'num' => 'color:#BCD42A',
'const' => 'color: #4bb1b1;',
'str' => 'color:#BCD42A',
'note' => 'color:#ef7c61',
'ref' => 'color:#A0A0A0',
'public' => 'color:#FFFFFF',
'protected' => 'color:#FFFFFF',
'private' => 'color:#FFFFFF',
'meta' => 'color:#FFFFFF',
'key' => 'color:#BCD42A',
'index' => 'color:#ef7c61',
];
$this->htmlDumper->setStyles($styles);
}
return $this->htmlDumper;
}
/**
* Format the given value into a human readable string.
*
* @param mixed $value
* @return string
*/
public function dump($value)
{
$dumper = $this->getDumper();
if ($dumper) {
// re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump.
// exclude verbose information (e.g. exception stack traces)
if (class_exists('Symfony\Component\VarDumper\Caster\Caster')) {
$cloneVar = $this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE);
// Symfony VarDumper 2.6 Caster class dont exist.
} else {
$cloneVar = $this->getCloner()->cloneVar($value);
}
$dumper->dump(
$cloneVar,
$this->htmlDumperOutput
);
$output = $this->htmlDumperOutput->getOutput();
$this->htmlDumperOutput->clear();
return $output;
}
return htmlspecialchars(print_r($value, true));
}
/**
* Format the args of the given Frame as a human readable html string
*
* @param Frame $frame
* @return string the rendered html
*/
public function dumpArgs(Frame $frame)
{
// we support frame args only when the optional dumper is available
if (!$this->getDumper()) {
return '';
}
$html = '';
$numFrames = count($frame->getArgs());
if ($numFrames > 0) {
$html = '<ol class="linenums">';
foreach ($frame->getArgs() as $j => $frameArg) {
$html .= '<li>'. $this->dump($frameArg) .'</li>';
}
$html .= '</ol>';
}
return $html;
}
/**
* Convert a string to a slug version of itself
*
* @param string $original
* @return string
*/
public function slug($original)
{
$slug = str_replace(" ", "-", $original);
$slug = preg_replace('/[^\w\d\-\_]/i', '', $slug);
return strtolower($slug);
}
/**
* Given a template path, render it within its own scope. This
* method also accepts an array of additional variables to be
* passed to the template.
*
* @param string $template
* @param array $additionalVariables
*/
public function render($template, array $additionalVariables = null)
{
$variables = $this->getVariables();
// Pass the helper to the template:
$variables["tpl"] = $this;
if ($additionalVariables !== null) {
$variables = array_replace($variables, $additionalVariables);
}
call_user_func(function () {
extract(func_get_arg(1));
require func_get_arg(0);
}, $template, $variables);
}
/**
* Sets the variables to be passed to all templates rendered
* by this template helper.
*
* @param array $variables
*/
public function setVariables(array $variables)
{
$this->variables = $variables;
}
/**
* Sets a single template variable, by its name:
*
* @param string $variableName
* @param mixed $variableValue
*/
public function setVariable($variableName, $variableValue)
{
$this->variables[$variableName] = $variableValue;
}
/**
* Gets a single template variable, by its name, or
* $defaultValue if the variable does not exist
*
* @param string $variableName
* @param mixed $defaultValue
* @return mixed
*/
public function getVariable($variableName, $defaultValue = null)
{
return isset($this->variables[$variableName]) ?
$this->variables[$variableName] : $defaultValue;
}
/**
* Unsets a single template variable, by its name
*
* @param string $variableName
*/
public function delVariable($variableName)
{
unset($this->variables[$variableName]);
}
/**
* Returns all variables for this helper
*
* @return array
*/
public function getVariables()
{
return $this->variables;
}
/**
* Set the cloner used for dumping variables.
*
* @param AbstractCloner $cloner
*/
public function setCloner($cloner)
{
$this->cloner = $cloner;
}
/**
* Get the cloner used for dumping variables.
*
* @return AbstractCloner
*/
public function getCloner()
{
if (!$this->cloner) {
$this->cloner = new VarCloner();
}
return $this->cloner;
}
/**
* Set the application root path.
*
* @param string $applicationRootPath
*/
public function setApplicationRootPath($applicationRootPath)
{
$this->applicationRootPath = $applicationRootPath;
}
/**
* Return the application root path.
*
* @return string
*/
public function getApplicationRootPath()
{
return $this->applicationRootPath;
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exceptions;
use ErrorException as BaseErrorException;
/**
* Wraps ErrorException; mostly used for typing (at least now)
* to easily cleanup the stack trace of redundant info.
*/
class ErrorException extends BaseErrorException
{
}

View file

@ -0,0 +1,74 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exceptions;
class Formatter
{
/**
* Returns all basic information about the exception in a simple array
* for further convertion to other languages
* @param Inspector $inspector
* @param bool $shouldAddTrace
* @return array
*/
public static function formatExceptionAsDataArray(Inspector $inspector, $shouldAddTrace)
{
$exception = $inspector->getException();
$response = [
'type' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
];
if ($shouldAddTrace) {
$frames = $inspector->getFrames();
$frameData = [];
foreach ($frames as $frame) {
/** @var Frame $frame */
$frameData[] = [
'file' => $frame->getFile(),
'line' => $frame->getLine(),
'function' => $frame->getFunction(),
'class' => $frame->getClass(),
'args' => $frame->getArgs(),
];
}
$response['trace'] = $frameData;
}
return $response;
}
public static function formatExceptionPlain(Inspector $inspector)
{
$message = $inspector->getException()->getMessage();
$frames = $inspector->getFrames();
$plain = $inspector->getExceptionName();
$plain .= ' thrown with message "';
$plain .= $message;
$plain .= '"'."\n\n";
$plain .= "Stacktrace:\n";
foreach ($frames as $i => $frame) {
$plain .= "#". (count($frames) - $i - 1). " ";
$plain .= $frame->getClass() ?: '';
$plain .= $frame->getClass() && $frame->getFunction() ? ":" : "";
$plain .= $frame->getFunction() ?: '';
$plain .= ' in ';
$plain .= ($frame->getFile() ?: '<#unknown>');
$plain .= ':';
$plain .= (int) $frame->getLine(). "\n";
}
return $plain;
}
}

View file

@ -0,0 +1,295 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exceptions;
use InvalidArgumentException;
class Frame
{
/**
* @var array
*/
protected $frame;
/**
* @var string
*/
protected $fileContentsCache;
/**
* @var array[]
*/
protected $comments = [];
/**
* @var bool
*/
protected $application;
/**
* @param array[]
*/
public function __construct(array $frame)
{
$this->frame = $frame;
}
/**
* @param bool $shortened
* @return string|null
*/
public function getFile($shortened = false)
{
if (empty($this->frame['file'])) {
return null;
}
$file = $this->frame['file'];
// Check if this frame occurred within an eval().
// @todo: This can be made more reliable by checking if we've entered
// eval() in a previous trace, but will need some more work on the upper
// trace collector(s).
if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d|assert) code$/', $file, $matches)) {
$file = $this->frame['file'] = $matches[1];
$this->frame['line'] = (int) $matches[2];
}
if ($shortened && is_string($file)) {
// Replace the part of the path that all frames have in common, and add 'soft hyphens' for smoother line-breaks.
$dirname = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
if ($dirname !== '/') {
$file = str_replace($dirname, "&hellip;", $file);
}
$file = str_replace("/", "/&shy;", $file);
}
return $file;
}
/**
* @return int|null
*/
public function getLine()
{
return isset($this->frame['line']) ? $this->frame['line'] : null;
}
/**
* @return string|null
*/
public function getClass()
{
return isset($this->frame['class']) ? $this->frame['class'] : null;
}
/**
* @return string|null
*/
public function getFunction()
{
return isset($this->frame['function']) ? $this->frame['function'] : null;
}
/**
* @return array
*/
public function getArgs()
{
return isset($this->frame['args']) ? (array) $this->frame['args'] : [];
}
/**
* Returns the full contents of the file for this frame,
* if it's known.
* @return string|null
*/
public function getFileContents()
{
if ($this->fileContentsCache === null && $filePath = $this->getFile()) {
// Leave the stage early when 'Unknown' or '[internal]' is passed
// this would otherwise raise an exception when
// open_basedir is enabled.
if ($filePath === "Unknown" || $filePath === '[internal]') {
return null;
}
try {
$this->fileContentsCache = file_get_contents($filePath);
} catch (ErrorException $exception) {
// Internal file paths of PHP extensions cannot be opened
}
}
return $this->fileContentsCache;
}
/**
* Adds a comment to this frame, that can be received and
* used by other handlers. For example, the PrettyPage handler
* can attach these comments under the code for each frame.
*
* An interesting use for this would be, for example, code analysis
* & annotations.
*
* @param string $comment
* @param string $context Optional string identifying the origin of the comment
*/
public function addComment($comment, $context = 'global')
{
$this->comments[] = [
'comment' => $comment,
'context' => $context,
];
}
/**
* Returns all comments for this frame. Optionally allows
* a filter to only retrieve comments from a specific
* context.
*
* @param string $filter
* @return array[]
*/
public function getComments($filter = null)
{
$comments = $this->comments;
if ($filter !== null) {
$comments = array_filter($comments, function ($c) use ($filter) {
return $c['context'] == $filter;
});
}
return $comments;
}
/**
* Returns the array containing the raw frame data from which
* this Frame object was built
*
* @return array
*/
public function getRawFrame()
{
return $this->frame;
}
/**
* Returns the contents of the file for this frame as an
* array of lines, and optionally as a clamped range of lines.
*
* NOTE: lines are 0-indexed
*
* @example
* Get all lines for this file
* $frame->getFileLines(); // => array( 0 => '<?php', 1 => '...', ...)
* @example
* Get one line for this file, starting at line 10 (zero-indexed, remember!)
* $frame->getFileLines(9, 1); // array( 9 => '...' )
*
* @throws InvalidArgumentException if $length is less than or equal to 0
* @param int $start
* @param int $length
* @return string[]|null
*/
public function getFileLines($start = 0, $length = null)
{
if (null !== ($contents = $this->getFileContents())) {
$lines = explode("\n", $contents);
// Get a subset of lines from $start to $end
if ($length !== null) {
$start = (int) $start;
$length = (int) $length;
if ($start < 0) {
$start = 0;
}
if ($length <= 0) {
throw new InvalidArgumentException(
"\$length($length) cannot be lower or equal to 0"
);
}
$lines = array_slice($lines, $start, $length, true);
}
return $lines;
}
}
/**
* Implements the Serializable interface, with special
* steps to also save the existing comments.
*
* @see Serializable::serialize
* @return string
*/
public function serialize()
{
$frame = $this->frame;
if (!empty($this->comments)) {
$frame['_comments'] = $this->comments;
}
return serialize($frame);
}
/**
* Unserializes the frame data, while also preserving
* any existing comment data.
*
* @see Serializable::unserialize
* @param string $serializedFrame
*/
public function unserialize($serializedFrame)
{
$frame = unserialize($serializedFrame);
if (!empty($frame['_comments'])) {
$this->comments = $frame['_comments'];
unset($frame['_comments']);
}
$this->frame = $frame;
}
/**
* Compares Frame against one another
* @param Frame $frame
* @return bool
*/
public function equals(Frame $frame)
{
if (!$this->getFile() || $this->getFile() === 'Unknown' || !$this->getLine()) {
return false;
}
return $frame->getFile() === $this->getFile() && $frame->getLine() === $this->getLine();
}
/**
* Returns whether this frame belongs to the application or not.
*
* @return boolean
*/
public function isApplication()
{
return $this->application;
}
/**
* Mark as an frame belonging to the application.
*
* @param boolean $application
*/
public function setApplication($application)
{
$this->application = $application;
}
}

View file

@ -0,0 +1,200 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exceptions;
use Countable;
use IteratorAggregate;
use UnexpectedValueException;
/**
* Exposes a fluent interface for dealing with an ordered list
* of stack-trace frames.
*/
class FrameCollection implements IteratorAggregate, Countable
{
/**
* @var array[]
*/
private $frames;
/**
* @param array $frames
*/
public function __construct(array $frames)
{
$this->frames = array_map(function ($frame) {
return new Frame($frame);
}, $frames);
}
/**
* Filters frames using a callable, returns the same FrameCollection
*
* @param callable $callable
* @return FrameCollection
*/
public function filter($callable)
{
$this->frames = array_values(array_filter($this->frames, $callable));
return $this;
}
/**
* Map the collection of frames
*
* @param callable $callable
* @return FrameCollection
*/
public function map($callable)
{
// Contain the map within a higher-order callable
// that enforces type-correctness for the $callable
$this->frames = array_map(function ($frame) use ($callable) {
$frame = call_user_func($callable, $frame);
if (!$frame instanceof Frame) {
throw new UnexpectedValueException(
"Callable to " . __CLASS__ . "::map must return a Frame object"
);
}
return $frame;
}, $this->frames);
return $this;
}
/**
* Returns an array with all frames, does not affect
* the internal array.
*
* @todo If this gets any more complex than this,
* have getIterator use this method.
* @see FrameCollection::getIterator
* @return array
*/
public function getArray()
{
return $this->frames;
}
/**
* @see IteratorAggregate::getIterator
*/
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->frames);
}
/**
* @see ArrayAccess::offsetExists
* @param int $offset
*/
public function offsetExists($offset): bool
{
return isset($this->frames[$offset]);
}
/**
* @see ArrayAccess::offsetGet
* @param int $offset
*/
public function offsetGet($offset)
{
return $this->frames[$offset];
}
/**
* @see ArrayAccess::offsetSet
* @param int $offset
*/
public function offsetSet($offset, $value): void
{
throw new \Exception(__CLASS__ . ' is read only');
}
/**
* @see ArrayAccess::offsetUnset
* @param int $offset
*/
public function offsetUnset($offset): void
{
throw new \Exception(__CLASS__ . ' is read only');
}
/**
* @see Countable::count
* @return int
*/
public function count(): int
{
return count($this->frames);
}
/**
* Count the frames that belongs to the application.
*
* @return int
*/
public function countIsApplication()
{
return count(array_filter($this->frames, function (Frame $f) {
return $f->isApplication();
}));
}
/**
* @see Serializable::serialize
* @return string
*/
public function serialize()
{
return serialize($this->frames);
}
/**
* @see Serializable::unserialize
* @param string $serializedFrames
*/
public function unserialize($serializedFrames)
{
$this->frames = unserialize($serializedFrames);
}
/**
* @param Frame[] $frames Array of Frame instances, usually from $e->getPrevious()
*/
public function prependFrames(array $frames)
{
$this->frames = array_merge($frames, $this->frames);
}
/**
* Gets the innermost part of stack trace that is not the same as that of outer exception
*
* @param FrameCollection $parentFrames Outer exception frames to compare tail against
* @return Frame[]
*/
public function topDiff(FrameCollection $parentFrames)
{
$diff = $this->frames;
$parentFrames = $parentFrames->getArray();
$p = count($parentFrames) - 1;
for ($i = count($diff) - 1; $i >= 0 && $p >= 0; $i--) {
/** @var Frame $tailFrame */
$tailFrame = $diff[$i];
if ($tailFrame->equals($parentFrames[$p])) {
unset($diff[$i]);
}
$p--;
}
return $diff;
}
}

View file

@ -0,0 +1,324 @@
<?php
/**
* Whoops - php errors for cool kids
* @author Filipe Dobreira <http://github.com/filp>
*/
namespace Leaf\Exceptions;
use Leaf\Exception\Util\Misc;
class Inspector
{
/**
* @var \Throwable
*/
private $exception;
/**
* @var \Leaf\Exceptions\FrameCollection
*/
private $frames;
/**
* @var \Leaf\Exceptions\Inspector
*/
private $previousExceptionInspector;
/**
* @var \Throwable[]
*/
private $previousExceptions;
/**
* @param \Throwable $exception The exception to inspect
*/
public function __construct($exception)
{
$this->exception = $exception;
}
/**
* @return \Throwable
*/
public function getException()
{
return $this->exception;
}
/**
* @return string
*/
public function getExceptionName()
{
return get_class($this->exception);
}
/**
* @return string
*/
public function getExceptionMessage()
{
return $this->extractDocrefUrl($this->exception->getMessage())['message'];
}
/**
* @return string[]
*/
public function getPreviousExceptionMessages()
{
return array_map(function ($prev) {
/** @var \Throwable $prev */
return $this->extractDocrefUrl($prev->getMessage())['message'];
}, $this->getPreviousExceptions());
}
/**
* @return int[]
*/
public function getPreviousExceptionCodes()
{
return array_map(function ($prev) {
/** @var \Throwable $prev */
return $prev->getCode();
}, $this->getPreviousExceptions());
}
/**
* Returns a url to the php-manual related to the underlying error - when available.
*
* @return string|null
*/
public function getExceptionDocrefUrl()
{
return $this->extractDocrefUrl($this->exception->getMessage())['url'];
}
private function extractDocrefUrl($message)
{
$docref = [
'message' => $message,
'url' => null,
];
// php embbeds urls to the manual into the Exception message with the following ini-settings defined
// http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root
if (!ini_get('html_errors') || !ini_get('docref_root')) {
return $docref;
}
$pattern = "/\[<a href='([^']+)'>(?:[^<]+)<\/a>\]/";
if (preg_match($pattern, $message, $matches)) {
// -> strip those automatically generated links from the exception message
$docref['message'] = preg_replace($pattern, '', $message, 1);
$docref['url'] = $matches[1];
}
return $docref;
}
/**
* Does the wrapped Exception has a previous Exception?
* @return bool
*/
public function hasPreviousException()
{
return $this->previousExceptionInspector || $this->exception->getPrevious();
}
/**
* Returns an Inspector for a previous Exception, if any.
* @todo Clean this up a bit, cache stuff a bit better.
* @return Inspector
*/
public function getPreviousExceptionInspector()
{
if ($this->previousExceptionInspector === null) {
$previousException = $this->exception->getPrevious();
if ($previousException) {
$this->previousExceptionInspector = new Inspector($previousException);
}
}
return $this->previousExceptionInspector;
}
/**
* Returns an array of all previous exceptions for this inspector's exception
* @return \Throwable[]
*/
public function getPreviousExceptions()
{
if ($this->previousExceptions === null) {
$this->previousExceptions = [];
$prev = $this->exception->getPrevious();
while ($prev !== null) {
$this->previousExceptions[] = $prev;
$prev = $prev->getPrevious();
}
}
return $this->previousExceptions;
}
/**
* Returns an iterator for the inspected exception's
* frames.
* @return \Leaf\Exceptions\FrameCollection
*/
public function getFrames()
{
if ($this->frames === null) {
$frames = $this->getTrace($this->exception);
// Fill empty line/file info for call_user_func_array usages (PHP Bug #44428)
foreach ($frames as $k => $frame) {
if (empty($frame['file'])) {
// Default values when file and line are missing
$file = '[internal]';
$line = 0;
$next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : [];
if ($this->isValidNextFrame($next_frame)) {
$file = $next_frame['file'];
$line = $next_frame['line'];
}
$frames[$k]['file'] = $file;
$frames[$k]['line'] = $line;
}
}
// Find latest non-error handling frame index ($i) used to remove error handling frames
$i = 0;
foreach ($frames as $k => $frame) {
if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) {
$i = $k;
}
}
// Remove error handling frames
if ($i > 0) {
array_splice($frames, 0, $i);
}
$firstFrame = $this->getFrameFromException($this->exception);
array_unshift($frames, $firstFrame);
$this->frames = new FrameCollection($frames);
if ($previousInspector = $this->getPreviousExceptionInspector()) {
// Keep outer frame on top of the inner one
$outerFrames = $this->frames;
$newFrames = clone $previousInspector->getFrames();
// I assume it will always be set, but let's be safe
if ($newFrames->count() > 0) {
$newFrames->offsetGet(0)->addComment(
$previousInspector->getExceptionMessage(),
'Exception message:'
);
}
$newFrames->prependFrames($outerFrames->topDiff($newFrames));
$this->frames = $newFrames;
}
}
return $this->frames;
}
/**
* Gets the backtrace from an exception.
*
* If xdebug is installed
*
* @param \Throwable $e
* @return array
*/
protected function getTrace($e)
{
$traces = $e->getTrace();
// Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default
if (!$e instanceof \ErrorException) {
return $traces;
}
if (!Misc::isLevelFatal($e->getSeverity())) {
return $traces;
}
if (!extension_loaded('xdebug') || !function_exists('xdebug_is_enabled') || !xdebug_is_enabled()) {
return $traces;
}
// Use xdebug to get the full stack trace and remove the shutdown handler stack trace
$stack = array_reverse(xdebug_get_function_stack());
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$traces = array_diff_key($stack, $trace);
return $traces;
}
/**
* Given an exception, generates an array in the format
* generated by Exception::getTrace()
* @param \Throwable $exception
* @return array
*/
protected function getFrameFromException($exception)
{
return [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => get_class($exception),
'args' => [
$exception->getMessage(),
],
];
}
/**
* Given an error, generates an array in the format
* generated by ErrorException
* @param ErrorException $exception
* @return array
*/
protected function getFrameFromError(ErrorException $exception)
{
return [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'class' => null,
'args' => [],
];
}
/**
* Determine if the frame can be used to fill in previous frame's missing info
* happens for call_user_func and call_user_func_array usages (PHP Bug #44428)
*
* @param array $frame
* @return bool
*/
protected function isValidNextFrame(array $frame)
{
if (empty($frame['file'])) {
return false;
}
if (empty($frame['line'])) {
return false;
}
if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) {
return false;
}
return true;
}
}

21
vendor/leafs/fetch/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Michael Darko-Duodu
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.

311
vendor/leafs/fetch/README.md vendored Normal file
View file

@ -0,0 +1,311 @@
<!-- markdownlint-disable no-inline-html -->
<p align="center">
<br><br>
<img src="https://leaf-docs.netlify.app/images/logo.png" height="100"/>
<h1 align="center">Fetch</h1>
<br><br>
</p>
# Leaf Fetch
<!-- [![Latest Stable Version](https://poser.pugx.org/leafs/leaf/v/stable)](https://packagist.org/packages/leafs/leaf)
[![Total Downloads](https://poser.pugx.org/leafs/leaf/downloads)](https://packagist.org/packages/leafs/leaf)
[![License](https://poser.pugx.org/leafs/leaf/license)](https://packagist.org/packages/leafs/leaf) -->
Clean, simple, developer friendly interface for making network requests with PHP. Fetch is based on curl and uses elements from Unirest PHP and an API that closely resembles Axios. All of these combined makes Fetch the best and simplest way to make PHP network requests.
## fetch example
```php
use function Leaf\fetch;
$res = fetch("https://jsonplaceholder.typicode.com/todos/");
echo json_encode($res->data);
```
You can also use the fetch class
```php
use Leaf\Fetch;
$res = Fetch::request([
"url" => 'https://jsonplaceholder.typicode.com/todos/1',
]);
echo json_encode($res->data);
```
## Installation
You can quickly install leaf fetch with composer.
```sh
composer require leafs/fetch
```
If you want to keep up to date with all the changes with leaf fetch you can follow the main branch
```sh
composer require leafs/fetch dev-main
```
## The `fetch` method
Leaf fetch provides the fetch method as an easy way to make HTTP requests. This allows you to quickly make requests without bringing up the whole fetch class and without even having to build up your own request array.
```php
// make a get request
$res = fetch("https://jsonplaceholder.typicode.com/todos/");
// make a post request
$res = fetch("https://jsonplaceholder.typicode.com/posts", [
"title" => "foo",
"body" => "bar",
"userId" => 1,
]);
// build a custom request array
$res = fetch([
"method" => "GET",
"url" => 'https://jsonplaceholder.typicode.com/todos/1',
"data" => [
"firstName" => 'Fred',
"lastName" => 'Flintstone'
]
]);
// get response body
echo json_encode($res->data);
```
## The `Fetch` class
The fetch class contains all the options and methods needed to make a network request.
### baseUrl
You might have noticed that all the requests above needed us to type a long URL to make the requests, however, we can add a base url so we don't have to type it over and over again.
```php
Fetch::baseUrl("https://jsonplaceholder.typicode.com");
```
And from there you can make requests like this:
```php
// make a get request
$res = fetch("/todos");
// make a post request
$res = fetch("/posts", [
"title" => "foo",
"body" => "bar",
"userId" => 1,
]);
// use the get shortcut method
$res = Fetch::get("/todos/10");
// echo response
echo json_encode($res);
```
### shortcut methods
The fetch class comes with shortcut methods named after http methods `get`, `post`, `put`, `patch`, ...
```php
$res = Fetch::post("/posts", [
"title" => "foo",
"body" => "bar",
"userId" => 2,
]);
$res = Fetch::get("/todos/10");
Fetch::delete("/todos/10");
// ...
```
### request
As you've seen earlier, the fetch class also provides a `request` method which is also used under the hood by the `fetch` function. `request` allows you to manually build up your request object with whatever data you need.
```php
use Leaf\Fetch;
$res = Fetch::request([
"method" => "GET",
"url" => "https://jsonplaceholder.typicode.com/todos",
]);
echo json_encode($res->data);
```
### Request object
This is the array which is used to construct the request to be sent. The available fields are:
```php
[
// `url` is the server URL that will be used for the request
"url" => null,
// `method` is the request method to be used when making the request
"method" => "GET", // default
// `baseURL` will be prepended to `url` unless `url` is absolute.
// It can be convenient to set `baseURL` for an instance of axios to pass relative URLs
// to methods of that instance.
"baseUrl" => "",
// `transformRequest` allows changes to the request data before it is sent to the server
// This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'
// The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
// FormData or Stream
// You may modify the headers object.
// "transformRequest" => function ($data, $headers) {
// // Do whatever you want to transform the data
// return $data;
// },
// `transformResponse` allows changes to the response data to be made before
// it is passed to then/catch
// "transformResponse" => function ($data) {
// // Do whatever you want to transform the data
// return $data;
// },
// `headers` are custom headers to be sent
"headers" => [],
// `params` are the URL parameters to be sent with the request
// Must be a plain object or a URLSearchParams object
"params" => [],
// `paramsSerializer` is an optional function in charge of serializing `params`
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
// "paramsSerializer" => function ($params) {
// return Qs.stringify($params, ["arrayFormat" => "brackets"]);
// },
// `data` is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', 'DELETE , and 'PATCH'
// When no `transformRequest` is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser "only" => FormData, File, Blob
// - Node "only" => Stream, Buffer
"data" => [],
// `timeout` specifies the number of milliseconds before the request times out.
// If the request takes longer than `timeout`, the request will be aborted.
"timeout" => 0, // default is `0` (no timeout)
// `withCredentials` indicates whether or not cross-site Access-Control requests
// should be made using credentials
"withCredentials" => false, // default
// `adapter` allows custom handling of requests which makes testing easier.
// Return a promise and supply a valid response (see lib/adapters/README.md).
// "adapter" => function ($config) {
// /* ... */
// },
// `auth` indicates that HTTP Basic auth should be used, and supplies credentials.
// This will set an `Authorization` header, overwriting any existing
// `Authorization` custom headers you have set using `headers`.
// Please note that only HTTP Basic auth is configurable through this parameter.
// For Bearer tokens and such, use `Authorization` custom headers instead.
"auth" => [],
// `responseType` indicates the type of data that the server will respond with
// options "are" => 'arraybuffer', 'document', 'json', 'text', 'stream'
// browser "only" => 'blob'
"responseType" => "json", // default
// `responseEncoding` indicates encoding to use for decoding responses (Node.js only)
// "Note" => Ignored for `responseType` of 'stream' or client-side requests
"responseEncoding" => "utf8", // default
// `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
"xsrfCookieName" => "XSRF-TOKEN", // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
"xsrfHeaderName" => "X-XSRF-TOKEN", // default
// `onUploadProgress` allows handling of progress events for uploads
// browser only
// "onUploadProgress" => function ($progressEvent) {
// // Do whatever you want with the native progress event
// },
// `onDownloadProgress` allows handling of progress events for downloads
// browser only
// "onDownloadProgress" => function ($progressEvent) {
// // Do whatever you want with the native progress event
// },
// `maxContentLength` defines the max size of the http response content in bytes allowed in node.js
"maxContentLength" => 2000,
// `maxBodyLength` (Node only option) defines the max size of the http request content in bytes allowed
"maxBodyLength" => 2000,
// `validateStatus` defines whether to resolve or reject the promise for a given
// HTTP response status code. If `validateStatus` returns `true` (or is set to `null`
// or `undefined`), the promise will be resolved; otherwise, the promise will be
// rejected.
// "validateStatus" => function ($status) {
// return $status >= 200 && $status < 300; // default
// },
// `maxRedirects` defines the maximum number of redirects to follow in node.js.
// If set to 0, no redirects will be followed.
"maxRedirects" => 5, // default
// `socketPath` defines a UNIX Socket to be used in node.js.
// e.g. '/var/run/docker.sock' to send requests to the docker daemon.
// Only either `socketPath` or `proxy` can be specified.
// If both are specified, `socketPath` is used.
"socketPath" => null, // default
// `proxy` defines the hostname, port, and protocol of the proxy server.
// You can also define your proxy using the conventional `http_proxy` and
// `https_proxy` environment variables. If you are using environment variables
// for your proxy configuration, you can also define a `no_proxy` environment
// variable as a comma-separated list of domains that should not be proxied.
// Use `false` to disable proxies, ignoring environment variables.
// `auth` indicates that HTTP Basic auth should be used to connect to the proxy, and
// supplies credentials.
// This will set an `Proxy-Authorization` header, overwriting any existing
// `Proxy-Authorization` custom headers you have set using `headers`.
// If the proxy server uses HTTPS, then you must set the protocol to `https`.
"proxy" => [],
// `decompress` indicates whether or not the response body should be decompressed
// automatically. If set to `true` will also remove the 'content-encoding' header
// from the responses objects of all decompressed responses
// - Node only (XHR cannot turn off decompression)
"decompress" => true, // default
// If false, fetch will try to parse json responses
"rawResponse" => false,
// CURLOPT_SSL_VERIFYHOST accepts only 0 (false) or 2 (true).
// Future versions of libcurl will treat values 1 and 2 as equals
"verifyHost" => true, // default
"verifyPeer" => true, // default
// Set additional options for curl.
"curl" => [],
];
```
## View Leaf's docs [here](https://leafphp.netlify.app/#/)
Built with ❤ by [**Mychi Darko**](https://mychi.netlify.app)

33
vendor/leafs/fetch/composer.json vendored Normal file
View file

@ -0,0 +1,33 @@
{
"name": "leafs/fetch",
"description": "Plain simple PHP http client",
"keywords": [
"xhr",
"fetch",
"http",
"leaf",
"php",
"network",
"client"
],
"homepage": "https://github.com/leafsphp/redis",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Michael Darko",
"email": "mickdd22@gmail.com",
"homepage": "https://mychi.netlify.app",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"Leaf\\": "src"
}
},
"minimum-stability": "stable",
"require": {
"ext-curl": "*"
}
}

471
vendor/leafs/fetch/src/Fetch.php vendored Normal file
View file

@ -0,0 +1,471 @@
<?php
namespace Leaf;
/**
* Leaf Fetch
* ---------
* Plain simple PHP http client.
*/
class Fetch
{
const HEAD = 'HEAD';
const GET = 'GET';
const POST = 'POST';
const PUT = 'PUT';
const PATCH = 'PATCH';
const DELETE = 'DELETE';
const OPTIONS = 'OPTIONS';
const OVERRIDE = '_METHOD';
private static $handler = null;
/**
* Default fetch options
*/
protected static $options = [
// `url` is the server URL that will be used for the request
"url" => null,
// `method` is the request method to be used when making the request
"method" => "GET", // default
// `baseURL` will be prepended to `url` unless `url` is absolute.
// It can be convenient to set `baseURL` for an instance of axios to pass relative URLs
// to methods of that instance.
"baseUrl" => "",
// `transformRequest` allows changes to the request data before it is sent to the server
// This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'
// The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
// FormData or Stream
// You may modify the headers object.
// "transformRequest" => function ($data, $headers) {
// // Do whatever you want to transform the data
// return $data;
// },
// `transformResponse` allows changes to the response data to be made before
// it is passed to then/catch
// "transformResponse" => function ($data) {
// // Do whatever you want to transform the data
// return $data;
// },
// `headers` are custom headers to be sent
"headers" => [],
// `params` are the URL parameters to be sent with the request
// Must be a plain object or a URLSearchParams object
"params" => [],
// `paramsSerializer` is an optional function in charge of serializing `params`
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
// "paramsSerializer" => function ($params) {
// return Qs.stringify($params, ["arrayFormat" => "brackets"]);
// },
// `data` is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', 'DELETE , and 'PATCH'
// When no `transformRequest` is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser "only" => FormData, File, Blob
// - Node "only" => Stream, Buffer
"data" => [],
// `timeout` specifies the number of milliseconds before the request times out.
// If the request takes longer than `timeout`, the request will be aborted.
"timeout" => 0, // default is `0` (no timeout)
// `withCredentials` indicates whether or not cross-site Access-Control requests
// should be made using credentials
"withCredentials" => false, // default
// `adapter` allows custom handling of requests which makes testing easier.
// Return a promise and supply a valid response (see lib/adapters/README.md).
// "adapter" => function ($config) {
// /* ... */
// },
// `auth` indicates that HTTP Basic auth should be used, and supplies credentials.
// This will set an `Authorization` header, overwriting any existing
// `Authorization` custom headers you have set using `headers`.
// Please note that only HTTP Basic auth is configurable through this parameter.
// For Bearer tokens and such, use `Authorization` custom headers instead.
"auth" => [],
// `responseType` indicates the type of data that the server will respond with
// options "are" => 'arraybuffer', 'document', 'json', 'text', 'stream'
// browser "only" => 'blob'
"responseType" => "json", // default
// `responseEncoding` indicates encoding to use for decoding responses (Node.js only)
// "Note" => Ignored for `responseType` of 'stream' or client-side requests
"responseEncoding" => "utf8", // default
// `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
"xsrfCookieName" => "XSRF-TOKEN", // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
"xsrfHeaderName" => "X-XSRF-TOKEN", // default
// `onUploadProgress` allows handling of progress events for uploads
// browser only
// "onUploadProgress" => function ($progressEvent) {
// // Do whatever you want with the native progress event
// },
// `onDownloadProgress` allows handling of progress events for downloads
// browser only
// "onDownloadProgress" => function ($progressEvent) {
// // Do whatever you want with the native progress event
// },
// `maxContentLength` defines the max size of the http response content in bytes allowed in node.js
"maxContentLength" => 2000,
// `maxBodyLength` (Node only option) defines the max size of the http request content in bytes allowed
"maxBodyLength" => 2000,
// `validateStatus` defines whether to resolve or reject the promise for a given
// HTTP response status code. If `validateStatus` returns `true` (or is set to `null`
// or `undefined`), the promise will be resolved; otherwise, the promise will be
// rejected.
// "validateStatus" => function ($status) {
// return $status >= 200 && $status < 300; // default
// },
// `maxRedirects` defines the maximum number of redirects to follow in node.js.
// If set to 0, no redirects will be followed.
"maxRedirects" => 5, // default
// `socketPath` defines a UNIX Socket to be used in node.js.
// e.g. '/var/run/docker.sock' to send requests to the docker daemon.
// Only either `socketPath` or `proxy` can be specified.
// If both are specified, `socketPath` is used.
"socketPath" => null, // default
// `proxy` defines the hostname, port, and protocol of the proxy server.
// You can also define your proxy using the conventional `http_proxy` and
// `https_proxy` environment variables. If you are using environment variables
// for your proxy configuration, you can also define a `no_proxy` environment
// variable as a comma-separated list of domains that should not be proxied.
// Use `false` to disable proxies, ignoring environment variables.
// `auth` indicates that HTTP Basic auth should be used to connect to the proxy, and
// supplies credentials.
// This will set an `Proxy-Authorization` header, overwriting any existing
// `Proxy-Authorization` custom headers you have set using `headers`.
// If the proxy server uses HTTPS, then you must set the protocol to `https`.
"proxy" => [],
// `decompress` indicates whether or not the response body should be decompressed
// automatically. If set to `true` will also remove the 'content-encoding' header
// from the responses objects of all decompressed responses
// - Node only (XHR cannot turn off decompression)
"decompress" => true, // default
// If false, fetch will try to parse json responses
"rawResponse" => false,
// CURLOPT_SSL_VERIFYHOST accepts only 0 (false) or 2 (true).
// Future versions of libcurl will treat values 1 and 2 as equals
"verifyHost" => true, // default
"verifyPeer" => true, // default
// Set additional options for curl.
"curl" => [],
];
public static function baseUrl($url)
{
static::$options["baseUrl"] = $url;
}
/**
* Base method for network requests
*/
public static function request($options)
{
$options = array_merge(static::$options, $options);
return static::call($options);
}
/**
* Make a get request
*/
public static function get($url, $config = [])
{
return static::request(array_merge($config, ["url" => $url]));
}
/**
* Make a post request
*/
public static function post($url, $data, $config = [])
{
return static::request(array_merge($config, ["url" => $url, "data" => $data, "method" => static::POST]));
}
/**
* Make a put request
*/
public static function put($url, $data, $config = [])
{
return static::request(array_merge($config, ["url" => $url, "data" => $data, "method" => static::PUT]));
}
/**
* Make a patch request
*/
public static function patch($url, $data, $config = [])
{
return static::request(array_merge($config, ["url" => $url, "data" => $data, "method" => static::PATCH]));
}
/**
* Make a delete request
*/
public static function delete($url, $config = [])
{
return static::request(array_merge($config, ["url" => $url, "method" => static::DELETE]));
}
/**
* Make an options request
*/
public static function options($url, $config = [])
{
return static::request(array_merge($config, ["url" => $url, "method" => static::OPTIONS]));
}
private static function call($request)
{
static::$handler = curl_init();
if ($request["method"] !== static::GET) {
if ($request["method"] === static::POST) {
curl_setopt(static::$handler, CURLOPT_POST, true);
} else {
if ($request["method"] === static::HEAD) {
curl_setopt(static::$handler, CURLOPT_NOBODY, true);
}
curl_setopt(static::$handler, CURLOPT_CUSTOMREQUEST, $request["method"]);
}
curl_setopt(static::$handler, CURLOPT_POSTFIELDS, $request["data"]);
} else if (is_array($request["data"])) {
if (strpos($request["url"], '?') !== false) {
$request["url"] .= '&';
} else {
$request["url"] .= '?';
}
$request["url"] .= urldecode(http_build_query(self::buildHTTPCurlQuery($request["data"])));
}
$curl_base_options = [
CURLOPT_URL => $request["baseUrl"] . $request["url"],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => $request["maxRedirects"],
CURLOPT_HTTPHEADER => $request["headers"],
CURLOPT_HEADER => true,
CURLOPT_SSL_VERIFYPEER => $request["verifyPeer"],
CURLOPT_SSL_VERIFYHOST => $request["verifyHost"] === false ? 0 : 2,
// If an empty string, '', is set, a header containing all supported encoding types is sent
CURLOPT_ENCODING => ""
];
foreach ($request["curl"] as $key => $value) {
$curl_base_options[$key] = $value;
}
curl_setopt_array(static::$handler, $curl_base_options);
if ($request["timeout"] !== null) {
curl_setopt(static::$handler, CURLOPT_TIMEOUT, $request["timeout"]);
}
// if (self::$cookie) {
// curl_setopt(static::$handler, CURLOPT_COOKIE, self::$cookie);
// }
// if (self::$cookieFile) {
// curl_setopt(static::$handler, CURLOPT_COOKIEFILE, self::$cookieFile);
// curl_setopt(static::$handler, CURLOPT_COOKIEJAR, self::$cookieFile);
// }
// supporting deprecated http auth method
// if (!empty($username)) {
// curl_setopt_array(static::$handler, array(
// CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
// CURLOPT_USERPWD => $username . ':' . $password
// ));
// }
// if (!empty($request["auth"]["user"])) {
// curl_setopt_array(static::$handler, [
// CURLOPT_HTTPAUTH => $request["auth"]["method"],
// CURLOPT_USERPWD => $request["auth"]["user"] . ":" . $request["auth"]["pass"]
// ]);
// }
// if ($request["proxy"]["address"] !== false) {
// curl_setopt_array(static::$handler, [
// CURLOPT_PROXYTYPE => $request["proxy"]["type"],
// CURLOPT_PROXY => $request["proxy"]["address"],
// CURLOPT_PROXYPORT => $request["proxy"]["port"],
// CURLOPT_HTTPPROXYTUNNEL => $request["proxy"]["tunnel"],
// CURLOPT_PROXYAUTH => $request["proxy"]["auth"]["method"],
// CURLOPT_PROXYUSERPWD => $request["proxy"]["auth"]["user"] . ":" . $request["proxy"]["auth"]["pass"]
// ]);
// }
$response = curl_exec(static::$handler);
$error = curl_error(static::$handler);
$info = self::getInfo();
if ($error) {
throw new \Exception($error);
}
// Split the full response in its headers and body
$header_size = $info['header_size'];
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);
$httpCode = $info['http_code'];
if (!$request["rawResponse"]) {
$body = json_decode($body);
}
return (object) [
// `data` is the response that was provided by the server
"data" => $body,
// `status` is the HTTP status code from the server response
"status" => $httpCode,
// `headers` the HTTP headers that the server responded with
// All header names are lower cased and can be accessed using the bracket notation.
// Example: `response.headers['content-type']`
"headers" => static::parseHeaders($header),
// `request` is the request that generated this response
"request" => $request,
];
// return new Response($httpCode, $body, $header, self::$jsonOpts);
}
/**
* if PECL_HTTP is not available use a fall back function
*
* thanks to ricardovermeltfoort@gmail.com
* http://php.net/manual/en/function.http-parse-headers.php#112986
* @param string $raw_headers raw headers
* @return array
*
* @author Mashape (https://www.mashape.com)
*/
private static function parseHeaders($raw_headers)
{
if (function_exists('http_parse_headers')) {
return \http_parse_headers($raw_headers);
} else {
$key = '';
$headers = array();
foreach (explode("\n", $raw_headers) as $i => $h) {
$h = explode(':', $h, 2);
if (isset($h[1])) {
if (!isset($headers[$h[0]])) {
$headers[$h[0]] = trim($h[1]);
} elseif (is_array($headers[$h[0]])) {
$headers[$h[0]] = array_merge($headers[$h[0]], array(trim($h[1])));
} else {
$headers[$h[0]] = array_merge(array($headers[$h[0]]), array(trim($h[1])));
}
$key = $h[0];
} else {
if (substr($h[0], 0, 1) == "\t") {
$headers[$key] .= "\r\n\t" . trim($h[0]);
} elseif (!$key) {
$headers[0] = trim($h[0]);
}
}
}
return $headers;
}
}
public static function getInfo($opt = false)
{
if (!$opt) {
return curl_getinfo(static::$handler);
}
return curl_getinfo(static::$handler, $opt);
}
/**
* This function is useful for serializing multidimensional arrays, and avoid getting
* the 'Array to string conversion' notice
* @param array|object $data array to flatten.
* @param bool|string $parent parent key or false if no parent
* @return array
*
* @author Mashape (https://www.mashape.com)
*/
public static function buildHTTPCurlQuery($data, $parent = false)
{
$result = array();
if (is_object($data)) {
$data = get_object_vars($data);
}
foreach ($data as $key => $value) {
if ($parent) {
$new_key = sprintf('%s[%s]', $parent, $key);
} else {
$new_key = $key;
}
if (!$value instanceof \CURLFile and (is_array($value) or is_object($value))) {
$result = array_merge($result, self::buildHTTPCurlQuery($value, $new_key));
} else {
$result[$new_key] = $value;
}
}
return $result;
}
}
/**
* Shortcut method for making network requests.
*
* @param array|string $options The url or request to hit.
*/
function fetch($options, $params = [])
{
if (is_string($options)) {
$options = ["url" => $options];
if (count($params) > 0) {
$options["method"] = Fetch::POST;
$options["data"] = $params;
}
}
return Fetch::request($options);
}

184
vendor/leafs/http/README.md vendored Normal file
View file

@ -0,0 +1,184 @@
<!-- markdownlint-disable no-inline-html -->
<p align="center">
<br><br>
<img src="https://leafphp.netlify.app/assets/img/leaf3-logo.png" height="100"/>
<br>
</p>
<h1 align="center">Leaf Http v2</h1>
<p align="center">
<a href="https://packagist.org/packages/leafs/http"
><img
src="https://poser.pugx.org/leafs/http/v/stable"
alt="Latest Stable Version"
/></a>
<a href="https://packagist.org/packages/leafs/http"
><img
src="https://poser.pugx.org/leafs/http/downloads"
alt="Total Downloads"
/></a>
<a href="https://packagist.org/packages/leafs/http"
><img
src="https://poser.pugx.org/leafs/http/license"
alt="License"
/></a>
</p>
<br />
<br />
Leaf's core http functionality packaged as a serve-yourself module. **Although seperated from Leaf core, it is still part of the default initial installation and doesn't need to be installed manually (unless you want a particular version).**
## Installation
You can easily install Leaf using the [Leaf CLI](https://cli.leafphp.dev)
```sh
leaf install http
```
[Composer](https://getcomposer.org/).
```bash
composer require leafs/http
```
## Basic Usage
The http module comes with a bunch of packages which help you manage the input and output of your app or API. This module comes with the most used Http related functionality like requests, responses, ...
The available classes in the Http module are:
- [`Leaf\Http\Request`](https://leafphp.dev/modules/http/request)
- [`Leaf\Http\Response`](https://leafphp.dev/modules/http/response)
- [`Leaf\Http\Headers`](https://leafphp.dev/modules/http/headers)
- [`Leaf\Http\Cache`](https://leafphp.dev/modules/http/cache)
- [`Leaf\Http\Status`](https://leafphp.dev/modules/http/status)
**The docs for Leaf Http v2 can be found at [https://leafphp.dev/modules/http/v/2/](https://leafphp.dev/modules/http/v/2/).**
## Request
This is a developer friendly interface which allows you to interact with data coming into your application. [Read the docs](https://leafphp.dev/modules/http/request)
## Response
This interface allows you to output data from your application in different forms. [Read the docs](https://leafphp.dev/modules/http/response)
## Headers
This interface allows you to manage headers in your application. [Read the docs](https://leafphp.dev/modules/http/headers)
## Cache
This interface allows you to manage http cache in your app. [Read the docs](https://leafphp.dev/modules/http/cache)
## Status
This interface allows you to manage http status codes in your app. [Read the docs](https://leafphp.dev/modules/http/status)
**There are also some add on classes which can be installed on demand.**
## Session (module)
This module allows you to manage session in your application. [Read the docs](/modules/session/)
## Cookies (module)
This module allows you to manage cookies in your application. [Read the docs](/modules/cookies)
## 💬 Stay In Touch
- [Twitter](https://twitter.com/leafphp)
- [Join the forum](https://github.com/leafsphp/leaf/discussions/37)
- [Chat on discord](https://discord.com/invite/Pkrm9NJPE3)
## 📓 Learning Leaf 3
- Leaf has a very easy to understand [documentation](https://leafphp.dev) which contains information on all operations in Leaf.
- You can also check out our [youtube channel](https://www.youtube.com/channel/UCllE-GsYy10RkxBUK0HIffw) which has video tutorials on different topics
- We are also working on codelabs which will bring hands-on tutorials you can follow and contribute to.
## 😇 Contributing
We are glad to have you. All contributions are welcome! To get started, familiarize yourself with our [contribution guide](https://leafphp.dev/community/contributing.html) and you'll be ready to make your first pull request 🚀.
To report a security vulnerability, you can reach out to [@mychidarko](https://twitter.com/mychidarko) or [@leafphp](https://twitter.com/leafphp) on twitter. We will coordinate the fix and eventually commit the solution in this project.
### Code contributors
<table>
<tr>
<td align="center">
<a href="https://github.com/mychidarko">
<img src="https://avatars.githubusercontent.com/u/26604242?v=4" width="120px" alt=""/>
<br />
<sub>
<b>Michael Darko</b>
</sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Dreamer0x01">
<img src="https://avatars.githubusercontent.com/u/12978365?v=4" width="120px" alt=""/>
<br />
<sub>
<b>Dreamer0x01</b>
</sub>
</a>
</td>
</tr>
</table>
## 🤩 Sponsoring Leaf
Your cash contributions go a long way to help us make Leaf even better for you. You can sponsor Leaf and any of our packages on [open collective](https://opencollective.com/leaf) or check the [contribution page](https://leafphp.dev/support/) for a list of ways to contribute.
And to all our existing cash/code contributors, we love you all ❤️
### Cash contributors
<table>
<tr>
<td align="center">
<a href="https://opencollective.com/aaron-smith3">
<img src="https://images.opencollective.com/aaron-smith3/08ee620/avatar/256.png" width="120px" alt=""/>
<br />
<sub><b>Aaron Smith</b></sub>
</a>
</td>
<td align="center">
<a href="https://opencollective.com/peter-bogner">
<img src="https://images.opencollective.com/peter-bogner/avatar/256.png" width="120px" alt=""/>
<br />
<sub><b>Peter Bogner</b></sub>
</a>
</td>
<td align="center">
<a href="#">
<img src="https://images.opencollective.com/guest-32634fda/avatar.png" width="120px" alt=""/>
<br />
<sub><b>Vano</b></sub>
</a>
</td>
<td align="center">
<a href="#">
<img
src="https://images.opencollective.com/guest-c72a498e/avatar.png"
width="120px"
alt=""
/>
<br />
<sub><b>Casprine</b></sub>
</a>
</td>
</tr>
</table>
## 🤯 Links/Projects
- [Aloe CLI](https://leafphp.dev/aloe-cli/)
- [Leaf Docs](https://leafphp.dev)
- [Leaf MVC](https://mvc.leafphp.dev)
- [Leaf API](https://api.leafphp.dev)
- [Leaf CLI](https://cli.leafphp.dev)

37
vendor/leafs/http/composer.json vendored Normal file
View file

@ -0,0 +1,37 @@
{
"name": "leafs/http",
"description": "Leaf PHP HTTP module.",
"keywords": [
"http",
"request",
"response",
"headers",
"leaf",
"php",
"framework"
],
"homepage": "https://leafphp.netlify.app/#/",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Michael Darko",
"email": "mickdd22@gmail.com",
"homepage": "https://mychi.netlify.app",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"Leaf\\Http\\": "src"
},
"files": [
"src/functions.php"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"leafs/anchor": "*"
}
}

102
vendor/leafs/http/src/Cache.php vendored Normal file
View file

@ -0,0 +1,102 @@
<?php
namespace Leaf\Http;
/**
* Leaf Http Caching
* ------------------------------------
* HTTP Caching made simple with Leaf
*
* @author Michael Darko
* @since 3.0.0
*/
class Cache
{
/**
* Set Last-Modified HTTP Response Header
*
* Set the HTTP 'Last-Modified' header and stop if a conditional
* GET request's `If-Modified-Since` header matches the last modified time
* of the resource. The `time` argument is a UNIX timestamp integer value.
* When the current request includes an 'If-Modified-Since' header that
* matches the specified last modified time, the application will stop
* and send a '304 Not Modified' response to the client.
*
* @param int $time The last modified UNIX timestamp
*/
public static function lastModified(int $time)
{
Headers::set('Last-Modified', gmdate('D, d M Y H:i:s T', $time));
if ($time === strtotime(Headers::get('If-Modified-Since'))) {
\Leaf\App::halt(304);
}
}
/**
* Set ETag HTTP Response Header
*
* Set the etag header and stop if the conditional GET request matches.
* The `value` argument is a unique identifier for the current resource.
* The `type` argument indicates whether the etag should be used as a strong or
* weak cache validator.
*
* When the current request includes an 'If-None-Match' header with
* a matching etag, execution is immediately stopped. If the request
* method is GET or HEAD, a '304 Not Modified' response is sent.
*
* @param string $value The etag value
* @param string $type The type of etag to create; either "strong" or "weak"
*/
public static function etag(string $value, string $type = "strong")
{
if (!in_array($type, ["strong", "weak"])) {
trigger_error("Invalid Leaf::etag type. Expected either \"strong\" or \"weak\".");
}
$value = "\"$value\"";
if ($type === "weak") {
$value = "W/" . $value;
}
Headers::set("ETag", $value);
if ($etagsHeader = Headers::get("If-None-Match")) {
$etags = preg_split("@\s*,\s*@", $etagsHeader);
if (in_array($value, $etags) || in_array("*", $etags)) {
$_304Methods = [Request::METHOD_GET, Request::METHOD_HEAD];
if (in_array(Request::getMethod(), $_304Methods)) {
\Leaf\App::halt(304);
} else {
// according to https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
// all methods besides GET and HEAD should return a 421 (Precondition Failed)
\Leaf\App::halt(412);
}
}
}
}
/**
* Set Expires HTTP response header
*
* The `Expires` header tells the HTTP client the time at which
* the current resource should be considered stale. At that time the HTTP
* client will send a conditional GET request to the server; the server
* may return a 200 OK if the resource has changed, else a 304 Not Modified
* if the resource has not changed. The `Expires` header should be used in
* conjunction with the `etag()` or `lastModified()` methods above.
*
* @param string|int $time If string, a time to be parsed by `strtotime()`; If int, a UNIX timestamp;
*/
public static function expires($time)
{
if (is_string($time)) {
$time = strtotime($time);
}
Headers::set('Expires', gmdate('D, d M Y H:i:s T', $time));
}
}

180
vendor/leafs/http/src/Headers.php vendored Normal file
View file

@ -0,0 +1,180 @@
<?php
namespace Leaf\Http;
/**
* HTTP Headers
* ---------------------
* Response header management made simple with Leaf
*
* @author Michael Darko
* @since 2.0.0
*/
class Headers
{
/**
* @var int
*/
protected static $httpCode = 200;
/**
* Get or Set an HTTP code for response
*
* @param int|null $httpCode The current response code.
*/
public static function status(int $httpCode = null)
{
if ($httpCode === null) return self::$httpCode;
self::$httpCode = $httpCode;
}
/**
* Force an HTTP code for response using PHP's `http_response_code`
*
* @param int $httpCode The response code to set
*/
public static function resetStatus($httpCode = 200)
{
return http_response_code($httpCode);
}
/**
* Get all headers passed into application
*
* @param bool $safeOutput Try to sanitize header data
*/
public static function all(bool $safeOutput = false): array
{
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
return \Leaf\Config::get('request.headers');
}
return ($safeOutput === false) ?
self::findHeaders() :
\Leaf\Anchor::sanitize(self::findHeaders());
}
/**
* Return a particular header passed into app
*
* @param array|string $params The header(s) to return
* @param bool $safeOutput Try to sanitize header data
*
* @return array|string|null
*/
public static function get($params, bool $safeOutput = false)
{
if (is_string($params)) return self::all($safeOutput)[$params] ?? null;
$data = [];
foreach ($params as $param) {
$data[$param] = self::get($param, $safeOutput);
}
return $data;
}
/**
* Set a new header
*/
public static function set($key, string $value = "", $replace = true, int $httpCode = 200): void
{
if (!is_array($key)) {
$code = $httpCode ?? self::$httpCode;
if (!$code) {
header("$key: $value", $replace);
} else {
header("$key: $value", $replace, $code);
}
} else {
foreach ($key as $header => $headerValue) {
self::set($header, $headerValue, $replace, $httpCode);
}
}
}
/**
* Remove a header
*/
public static function remove($keys)
{
if (!is_array($keys)) {
header_remove($keys);
} else {
foreach ($keys as $key) {
self::remove($key);
}
}
}
/**
* Check if a header is present
*
* @param string $header The header to check
*/
public static function has(string $header)
{
return in_array($header, static::all());
}
/**
* Set the content-type to plain text
*/
public static function contentPlain($code = 200): void
{
self::set("Content-Type", "text/plain", true, $code ?? self::$httpCode);
}
/**
* Set the content-type to html
*/
public static function contentHtml($code = 200): void
{
self::set("Content-Type", "text/html", true, $code ?? self::$httpCode);
}
/**
* Set the content-type to xml
*/
public static function contentXml($code = 200): void
{
self::set("Content-Type", "application/xml", true, $code ?? self::$httpCode);
}
/**
* Set the content-type to json
*/
public static function contentJSON($code = 200): void
{
self::set("Content-Type", "application/json", true, $code ?? self::$httpCode);
}
/**
* Quickly set an access control header
*/
public static function accessControl($key, $value = "", $code = 200)
{
if (is_string($key)) {
self::set("Access-Control-$key", $value, true, $code ?? self::$httpCode);
} else {
foreach ($key as $header => $headerValue) {
self::accessControl($header, $headerValue, $code);
}
}
}
protected static function findHeaders()
{
if (function_exists("getallheaders") && \getallheaders()) {
return \getallheaders();
}
$headers = [];
foreach ($_SERVER as $name => $value) {
if ((substr($name, 0, 5) == 'HTTP_') || ($name == 'CONTENT_TYPE') || ($name == 'CONTENT_LENGTH')) {
$headers[str_replace([' ', 'Http'], ['-', 'HTTP'], ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
return $headers;
}
}

519
vendor/leafs/http/src/Request.php vendored Normal file
View file

@ -0,0 +1,519 @@
<?php
namespace Leaf\Http;
/**
* Leaf HTTP Request
* --------
*
* This class provides an object-oriented way to interact with the current
* HTTP request being handled by your application as well as retrieve the input,
* cookies, and files that were submitted with the request.
*
* @author Michael Darko
* @since 1.0.0
* @version 2.0
*/
class Request
{
const METHOD_HEAD = 'HEAD';
const METHOD_GET = 'GET';
const METHOD_POST = 'POST';
const METHOD_PUT = 'PUT';
const METHOD_PATCH = 'PATCH';
const METHOD_DELETE = 'DELETE';
const METHOD_OPTIONS = 'OPTIONS';
const METHOD_OVERRIDE = '_METHOD';
/**
* @var array
*/
protected static $formDataMediaTypes = ['application/x-www-form-urlencoded'];
/**
* Get HTTP method
* @return string
*/
public static function getMethod(): string
{
return $_SERVER['REQUEST_METHOD'];
}
/**
* Check for request method type
*
* @param string $type The type of request to check for
* @return bool
*/
public static function typeIs(string $type): bool
{
return static::getMethod() === strtoupper($type);
}
/**
* Find if request has a particular header
*
* @param string $header Header to check for
* @return bool
*/
public static function hasHeader(String $header): bool
{
return !!Headers::get($header);
}
/**
* Is this an AJAX request?
* @return bool
*/
public static function isAjax(): bool
{
if (static::params('isajax')) {
return true;
}
if (Headers::get('X_REQUESTED_WITH') && Headers::get('X_REQUESTED_WITH') === 'XMLHttpRequest') {
return true;
}
return false;
}
/**
* Is this an XHR request? (alias of Leaf_Http_Request::isAjax)
* @return bool
*/
public static function isXhr(): bool
{
return static::isAjax();
}
/**
* Access stream that allows you to read raw data from the request body. **This is not for form data**
*
* @param boolean $safeData Sanitize data?
*/
public static function input($safeData = true)
{
$handler = fopen('php://input', 'r');
$data = stream_get_contents($handler);
if (Headers::get('Content-Type') === 'application/x-www-form-urlencoded') {
$d = $data;
$data = [];
foreach (explode('&', $d) as $chunk) {
$param = explode('=', $chunk);
$data[$param[0]] = $param[1];
}
} else if (strpos(Headers::get('Content-Type') ?? '', 'application/json') !== 0 && strpos(Headers::get('Content-Type'), 'multipart/form-data') !== 0) {
$safeData = false;
$data = [$data];
} else {
if (!$data) {
$data = json_encode([]);
}
$parsedData = json_decode($data, true);
$data = is_array($parsedData) ? $parsedData : [$parsedData];
}
return $safeData ? \Leaf\Anchor::sanitize($data) : $data;
}
/**
* Fetch GET and POST data
*
* This method returns a union of GET and POST data as a key-value array, or the value
* of the array key if requested. If the array key does not exist, NULL is returned,
* unless there is a default value specified.
*
* @param string|null $key
* @param mixed|null $default
*
* @return mixed
*/
public static function params(string $key = null, mixed $default = null): mixed
{
$union = static::body();
if ($key) {
return $union[$key] ?? $default;
}
return $union;
}
/**
* Attempt to retrieve data from the request.
*
* Data which is not found in the request parameters will
* be completely removed instead of returning null. Use `get`
* if you want to return null or `params` if you want to set
* a default value.
*
* @param array $params The parameters to return
* @param bool $safeData Sanitize output?
* @param bool $noEmptyString Remove empty strings from return data?
*/
public static function try(array $params, bool $safeData = true, bool $noEmptyString = false)
{
$data = static::get($params, $safeData);
$dataKeys = array_keys($data);
foreach ($dataKeys as $key) {
if (!isset($data[$key])) {
unset($data[$key]);
continue;
}
if ($noEmptyString && !strlen($data[$key])) {
unset($data[$key]);
}
}
return $data;
}
/**
* Get raw request data
*
* @param string|array $item The items to output
* @param mixed $default The default value to return if no data is available
*/
public static function rawData($item = null, $default = null)
{
return \Leaf\Anchor::deepGet(static::input(false), $item) ?? $default;
}
/**
* Return only get request data
*
* @param string|array $item The items to output
* @param mixed $default The default value to return if no data is available
*/
public static function urlData($item = null, $default = null)
{
return \Leaf\Anchor::deepGet($_GET, $item) ?? $default;
}
/**
* Return only get request data
*
* @param string|array $item The items to output
* @param mixed $default The default value to return if no data is available
*/
public static function postData($item = null, $default = null)
{
return \Leaf\Anchor::deepGet($_POST, $item) ?? $default;
}
/**
* Returns request data
*
* This methods returns data passed into the request (request or form data).
* This method returns get, post, put patch, delete or raw faw form data or NULL
* if the data isn't found.
*
* @param array|string $params The parameter(s) to return
* @param bool $safeData Sanitize output
*/
public static function get($params, bool $safeData = true)
{
if (is_string($params)) {
return static::body($safeData)[$params] ?? null;
}
$data = [];
foreach ($params as $param) {
$data[$param] = static::get($param, $safeData);
}
return $data;
}
/**
* Get all the request data as an associative array
*
* @param bool $safeData Sanitize output
*/
public static function body(bool $safeData = true)
{
$finalData = array_merge(static::urlData(), $_FILES, static::postData(), static::input());
return $safeData ?
\Leaf\Anchor::sanitize($finalData) :
$finalData;
}
/**
* Get all files passed into the request.
*
* @param array|string|null $filenames The file(s) you want to get
*/
public static function files($filenames = null)
{
if ($filenames == null) {
return $_FILES;
}
if (is_string($filenames)) {
return $_FILES[$filenames] ?? null;
}
$files = [];
foreach ($filenames as $filename) {
$files[$filename] = $_FILES[$filename] ?? null;
}
return $files;
}
/**
* Fetch COOKIE data
*
* This method returns a key-value array of Cookie data sent in the HTTP request, or
* the value of a array key if requested; if the array key does not exist, NULL is returned.
*
* @param string|null $key
* @return array|string|null
*/
public static function cookies(string $key = null)
{
return $key === null ?
Cookie::all() :
Cookie::get($key);
}
/**
* Does the Request body contain parsed form data?
* @return bool
*/
public static function isFormData(): bool
{
$method = static::getMethod();
return ($method === self::METHOD_POST && is_null(static::getContentType())) || in_array(static::getMediaType(), self::$formDataMediaTypes);
}
/**
* Get Headers
*
* This method returns a key-value array of headers sent in the HTTP request, or
* the value of a hash key if requested; if the array key does not exist, NULL is returned.
*
* @param array|string|null $key The header(s) to return
* @param bool $safeData Attempt to sanitize headers
*
* @return array|string|null
*/
public static function headers($key = null, bool $safeData = true)
{
return ($key === null) ?
Headers::all($safeData) :
Headers::get($key, $safeData);
}
/**
* Get Content Type
* @return string|null
*/
public static function getContentType(): ?string
{
return Headers::get('CONTENT_TYPE');
}
/**
* Get Media Type (type/subtype within Content Type header)
* @return string|null
*/
public static function getMediaType(): ?string
{
$contentType = static::getContentType();
if ($contentType) {
$contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
return strtolower($contentTypeParts[0]);
}
return null;
}
/**
* Get Media Type Params
* @return array
*/
public static function getMediaTypeParams(): array
{
$contentType = static::getContentType();
$contentTypeParams = [];
if ($contentType) {
$contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
$contentTypePartsLength = count($contentTypeParts);
for ($i = 1; $i < $contentTypePartsLength; $i++) {
$paramParts = explode('=', $contentTypeParts[$i]);
$contentTypeParams[strtolower($paramParts[0])] = $paramParts[1];
}
}
return $contentTypeParams;
}
/**
* Get Content Charset
* @return string|null
*/
public static function getContentCharset(): ?string
{
$mediaTypeParams = static::getMediaTypeParams();
if (isset($mediaTypeParams['charset'])) {
return $mediaTypeParams['charset'];
}
return null;
}
/**
* Get Content-Length
* @return int
*/
public static function getContentLength(): int
{
return Headers::get('CONTENT_LENGTH') ?? 0;
}
/**
* Get Host
* @return string
*/
public static function getHost(): string
{
if (isset($_SERVER['HTTP_HOST'])) {
if (preg_match('/^(\[[a-fA-F0-9:.]+\])(:\d+)?\z/', $_SERVER['HTTP_HOST'], $matches)) {
return $matches[1];
} else if (strpos($_SERVER['HTTP_HOST'], ':') !== false) {
$hostParts = explode(':', $_SERVER['HTTP_HOST']);
return $hostParts[0];
}
return $_SERVER['HTTP_HOST'];
}
return $_SERVER['SERVER_NAME'];
}
/**
* Get Host with Port
* @return string
*/
public static function getHostWithPort(): string
{
return sprintf('%s:%s', static::getHost(), static::getPort());
}
/**
* Get Port
* @return int
*/
public static function getPort(): int
{
return (int) $_SERVER['SERVER_PORT'] ?? 80;
}
/**
* Get Scheme (https or http)
* @return string
*/
public static function getScheme(): string
{
return empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ? 'http' : 'https';
}
/**
* Get Script Name (physical path)
* @return string
*/
public static function getScriptName(): string
{
return $_SERVER['SCRIPT_NAME'];
}
/**
* Get Path (physical path + virtual path)
* @return string
*/
public static function getPath(): string
{
return static::getScriptName() . static::getPathInfo();
}
/**
* Get Path Info (virtual path)
* @return string|null
*/
public static function getPathInfo(): ?string
{
return $_SERVER['REQUEST_URI'] ?? null;
}
/**
* Get URL (scheme + host [ + port if non-standard ])
* @return string
*/
public static function getUrl(): string
{
$url = static::getScheme() . '://' . static::getHost();
if ((static::getScheme() === 'https' && static::getPort() !== 443) || (static::getScheme() === 'http' && static::getPort() !== 80)) {
$url .= ':' . static::getPort();
}
return $url;
}
/**
* Get IP
* @return string
*/
public static function getIp(): string
{
$keys = ['X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP', 'REMOTE_ADDR'];
foreach ($keys as $key) {
if (isset($_SERVER[$key])) {
return $_SERVER[$key];
}
}
return $_SERVER['REMOTE_ADDR'];
}
/**
* Get Referrer
* @return string|null
*/
public static function getReferrer(): ?string
{
return Headers::get('HTTP_REFERER');
}
/**
* Get Referer (for those who can't spell)
* @return string|null
*/
public static function getReferer(): ?string
{
return static::getReferrer();
}
/**
* Get User Agent
* @return string|null
*/
public static function getUserAgent(): ?string
{
return Headers::get('HTTP_USER_AGENT');
}
}

458
vendor/leafs/http/src/Response.php vendored Normal file
View file

@ -0,0 +1,458 @@
<?php
namespace Leaf\Http;
/**
* Leaf HTTP Response
* -----------
* This is a simple abstraction over top an HTTP response. This
* provides methods to set the HTTP status, the HTTP headers,
* and the HTTP body.
*
* @author Michael Darko
* @since 1.0.0
* @version 2.0
*/
class Response
{
/**
* @var array
*/
public $headers = [];
/**
* @var array
*/
public $cookies = [];
/**
* @var string
*/
protected $content = '';
/**
* @var int HTTP status code
*/
protected $status = 200;
/**
* @var string HTTP Version
*/
protected $version;
/**
* Get/Set Http Version
*/
public function httpVersion(?string $version = null)
{
if (!$version || (is_string($version) && strlen($version) === 0)) {
return $this->version ?? $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1';
}
$this->version = 'HTTP/' . str_replace('HTTP/', '', $version);
return $this;
}
/**
* Output plain text
*
* @param mixed $data The data to output
* @param int $code The response status code
*/
public function plain($data, int $code = 200)
{
$this->status = $code;
$this->headers['Content-Type'] = 'text/plain';
$this->content = $data;
$this->send();
}
/**
* Output xml text
*
* @param string $data The data to output
* @param int $code The response status code
*/
public function xml($data, int $code = 200)
{
$this->status = $code;
$this->headers['Content-Type'] = 'application/xml';
$this->content = $data;
$this->send();
}
/**
* Output json encoded data with an HTTP code/message
*
* @param mixed $data The data to output
* @param int $code The response status code
* @param bool $showCode Show response code in body?
*/
public function json($data, int $code = 200, bool $showCode = false)
{
$this->status = $code;
if ($showCode) {
$dataToPrint = [
'data' => $data,
'status' => [
'code' => $code,
'message' => Status::$statusTexts[$code] ?? 'unknown status',
],
];
} else {
$dataToPrint = $data;
}
$this->headers['Content-Type'] = 'application/json';
$this->content = json_encode($dataToPrint);
$this->send();
}
/**
* Output data from an HTML or PHP file
*
* @param string $file The file to output
* @param int $code The http status code
*/
public function page(string $file, int $code = 200)
{
$this->status = $code;
$this->headers['Content-Type'] = 'text/html';
\ob_start();
require $file;
$this->content = ob_get_contents();
ob_end_clean();
$this->send();
}
/**
* Output some html/PHP
*
* @param string $markup The data to output
* @param int $code The http status code
*/
public function markup(string $markup, int $code = 200)
{
$this->status = $code;
$this->headers['Content-Type'] = 'text/html';
$this->content = <<<EOT
$markup
EOT;
$this->send();
}
/**
* Output plain text
*
* @param string $file Path to the file to download
* @param string|null $name The of the file as shown to user
* @param int $code The response status code
*/
public function download(string $file, string $name = null, int $code = 200)
{
$this->status = $code;
if (!file_exists($file)) {
Headers::contentHtml();
trigger_error("$file not found. Confirm your file path.");
}
if ($name === null) $name = basename($file);
$this->headers = array_merge($this->headers, [
'Content-Length' => filesize($file),
'Content-Disposition' => "attachment; filename=$name",
]);
$this->content = $file;
$this->send();
}
/**
* The HTTP 204 No Content success status response code indicates
* that a request has succeeded, but that the client doesn't
* need to navigate away from its current page.
*/
public function noContent()
{
$this->status = 204;
$this->send();
}
/**
* Output some data and break the application
*
* @param mixed $data The data to output
* @param int $code The Http status code
*/
public function exit($data, int $code = 500)
{
$this->status = $code;
if (is_array($data)) {
$this->headers['Content-Type'] = 'application/json';
$this->content = json_encode($data);
} else {
$this->content = $data;
}
$this->send();
exit();
}
/**
* Redirect
*
* This method prepares this response to return an HTTP Redirect response
* to the HTTP client.
*
* @param string $url The redirect destination
* @param int $status The redirect HTTP status code
*/
public function redirect(string $url, int $status = 302)
{
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
\Leaf\Config::set('response.redirect', [$url, $status]);
return;
}
Headers::status($status);
Headers::set('Location', $url, true, $status);
}
/**
* Force set HTTP status code
*
* @param int $httpCode The response code to set
*/
public function status($code = null)
{
$this->status = $code;
Headers::status($code);
return $this;
}
/**
* set header
*
* @param string|array $name Header name
* @param string|null $value Header value
* @param boolean $replace Replace existing header
* @param int $httpCode The HTTP status code
*/
public function withHeader($name, ?string $value = '', $replace = true, int $httpCode = 200)
{
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
$this->headers = array_merge(
$this->headers,
is_array($name) ? $name : [$name => $value]
);
\Leaf\Config::set('response.headers', $this->headers);
return $this;
}
$this->status = $httpCode;
Headers::status($httpCode);
if (is_array($name)) {
$this->headers = array_merge($this->headers, $name);
return $this;
}
if ($replace === false || $httpCode !== 200) {
Headers::set($name, $value, $replace, $httpCode);
} else {
$this->headers[$name] = $value;
}
return $this;
}
/**
* Shorthand method of setting a cookie + value + expire time
*
* @param string $name The name of the cookie
* @param string $value The value of cookie
* @param string $expire When the cookie expires. Default: 7 days
*/
public function withCookie(string $name, string $value, int $expire = null)
{
$this->cookies[$name] = [$value, $expire ?? (time() + 604800)];
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
\Leaf\Config::set('response.cookies', $this->cookies);
}
return $this;
}
/**
* Delete cookie
*
* @param mixed $name The name of the cookie
*/
public function withoutCookie($name)
{
$this->cookies[$name] = ['', -1];
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
\Leaf\Config::set('response.cookies', $this->cookies);
}
return $this;
}
/**
* Flash a piece of data to the session.
*
* @param string|array $name The key of the item to set
* @param string $value The value of flash item
*/
public function withFlash($key, string $value)
{
if (!class_exists('Leaf\Http\Session')) {
Headers::contentHtml();
trigger_error('Leaf session not found. Run `leaf install session` or `composer require leafs/session`');
}
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->withFlash($k, $v);
}
}
\Leaf\Flash::set($key, $value);
return $this;
}
/**
* Get message for HTTP status code
*
* @param int $status
* @return string|null
*/
public static function getMessageForCode(int $status): ?string
{
return Status::$statusTexts[$status] ?? 'unknown status';
}
/**
* Sends HTTP headers.
*
* @param int $code The http status code to attach
*
* @return $this
*/
public function sendHeaders()
{
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
\Leaf\Config::set('response.headers', $this->headers);
return $this;
}
// headers have already been sent by the developer
if (headers_sent()) {
return $this;
}
Headers::set($this->headers);
// status
header(sprintf('%s %s %s', $this->httpVersion(), $this->status, Status::$statusTexts[$this->status]), true, $this->status);
return $this;
}
/**
* Send cookies
*/
public function sendCookies()
{
if (class_exists('Leaf\Eien\Server') && PHP_SAPI === 'cli') {
\Leaf\Config::set('response.cookies', $this->cookies);
return $this;
}
if (!class_exists('Leaf\Http\Cookie')) {
Headers::contentHtml();
trigger_error('Leaf cookie not found. Run `leaf install cookie` or `composer require leafs/cookie`');
}
foreach ($this->cookies as $key => $value) {
Cookie::set($key, $value[0], ['expire' => $value[1]]);
}
return $this;
}
/**
* Sends content for the current web response.
*
* @param string $content The content to output
*
* @return $this
*/
public function sendContent()
{
if (strpos($this->headers['Content-Disposition'] ?? '', 'attachment') !== false) {
readfile($this->content);
} else {
echo $this->content;
}
return $this;
}
/**
* Send the Http headers and content
*
* @return $this
*/
public function send()
{
$this->sendHeaders()->sendCookies()->sendContent();
if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (\function_exists('litespeed_finish_request')) {
\litespeed_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
$this->closeOutputBuffers(0, true);
}
}
/**
* Cleans or flushes output buffers up to target level.
*
* Resulting level can be greater than target level if a non-removable buffer has been encountered.
*
* @final
*/
public static function closeOutputBuffers(int $targetLevel, bool $flush): void
{
$status = ob_get_status(true);
$level = \count($status);
$flags = \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE);
while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
if ($flush) {
ob_end_flush();
} else {
ob_end_clean();
}
}
}
}

153
vendor/leafs/http/src/Status.php vendored Normal file
View file

@ -0,0 +1,153 @@
<?php
namespace Leaf\Http;
/**
* Leaf Status Code Helper
*
* @since 2.0
* @version 1.0
*/
class Status {
public const HTTP_CONTINUE = 100;
public const HTTP_SWITCHING_PROTOCOLS = 101;
public const HTTP_PROCESSING = 102; // RFC2518
public const HTTP_EARLY_HINTS = 103; // RFC8297
public const HTTP_OK = 200;
public const HTTP_CREATED = 201;
public const HTTP_ACCEPTED = 202;
public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
public const HTTP_NO_CONTENT = 204;
public const HTTP_RESET_CONTENT = 205;
public const HTTP_PARTIAL_CONTENT = 206;
public const HTTP_MULTI_STATUS = 207; // RFC4918
public const HTTP_ALREADY_REPORTED = 208; // RFC5842
public const HTTP_IM_USED = 226; // RFC3229
public const HTTP_MULTIPLE_CHOICES = 300;
public const HTTP_MOVED_PERMANENTLY = 301;
public const HTTP_FOUND = 302;
public const HTTP_SEE_OTHER = 303;
public const HTTP_NOT_MODIFIED = 304;
public const HTTP_USE_PROXY = 305;
public const HTTP_RESERVED = 306;
public const HTTP_TEMPORARY_REDIRECT = 307;
public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
public const HTTP_BAD_REQUEST = 400;
public const HTTP_UNAUTHORIZED = 401;
public const HTTP_PAYMENT_REQUIRED = 402;
public const HTTP_FORBIDDEN = 403;
public const HTTP_NOT_FOUND = 404;
public const HTTP_METHOD_NOT_ALLOWED = 405;
public const HTTP_NOT_ACCEPTABLE = 406;
public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
public const HTTP_REQUEST_TIMEOUT = 408;
public const HTTP_CONFLICT = 409;
public const HTTP_GONE = 410;
public const HTTP_LENGTH_REQUIRED = 411;
public const HTTP_PRECONDITION_FAILED = 412;
public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
public const HTTP_REQUEST_URI_TOO_LONG = 414;
public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
public const HTTP_EXPECTATION_FAILED = 417;
public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
public const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540
public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
public const HTTP_LOCKED = 423; // RFC4918
public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04
public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
public const HTTP_INTERNAL_SERVER_ERROR = 500;
public const HTTP_NOT_IMPLEMENTED = 501;
public const HTTP_BAD_GATEWAY = 502;
public const HTTP_SERVICE_UNAVAILABLE = 503;
public const HTTP_GATEWAY_TIMEOUT = 504;
public const HTTP_VERSION_NOT_SUPPORTED = 505;
public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
public const HTTP_LOOP_DETECTED = 508; // RFC5842
public const HTTP_NOT_EXTENDED = 510; // RFC2774
public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
/**
* Status codes translation table.
*
* The list of codes is complete according to the
* {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry}
* (last updated 2021-10-01).
*
* Unless otherwise noted, the status code is defined in RFC2616.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @var array
*/
public static $statusTexts = [
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing', // RFC2518
103 => 'Early Hints',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status', // RFC4918
208 => 'Already Reported', // RFC5842
226 => 'IM Used', // RFC3229
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect', // RFC7238
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Content Too Large', // RFC-ietf-httpbis-semantics
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot', // RFC2324
421 => 'Misdirected Request', // RFC7540
422 => 'Unprocessable Content', // RFC-ietf-httpbis-semantics
423 => 'Locked', // RFC4918
424 => 'Failed Dependency', // RFC4918
425 => 'Too Early', // RFC-ietf-httpbis-replay-04
426 => 'Upgrade Required', // RFC2817
428 => 'Precondition Required', // RFC6585
429 => 'Too Many Requests', // RFC6585
431 => 'Request Header Fields Too Large', // RFC6585
451 => 'Unavailable For Legal Reasons', // RFC7725
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates', // RFC2295
507 => 'Insufficient Storage', // RFC4918
508 => 'Loop Detected', // RFC5842
510 => 'Not Extended', // RFC2774
511 => 'Network Authentication Required', // RFC6585
];
}

51
vendor/leafs/http/src/functions.php vendored Normal file
View file

@ -0,0 +1,51 @@
<?php
if (!function_exists('request')) {
/**
* Return request or request data
*
* @param array|string $data Get data from request
*
* @return \Leaf\Http\Request
*/
function request()
{
if (class_exists('\Leaf\Config')) {
$request = Leaf\Config::get("request")["instance"] ?? null;
if (!$request) {
$request = new \Leaf\Http\Request;
Leaf\Config::set("request", ["instance" => $request]);
}
return $request;
}
return new \Leaf\Http\Request();
}
}
if (!function_exists('response')) {
/**
* Return response or set response data
*
* @param array|string $data The JSON response to set
*
* @return \Leaf\Http\Response
*/
function response()
{
if (class_exists('\Leaf\Config')) {
$response = Leaf\Config::get("response")["instance"] ?? null;
if (!$response) {
$response = new \Leaf\Http\Response;
Leaf\Config::set("response", ["instance" => $response]);
}
return $response;
}
return new \Leaf\Http\Response();
}
}

21
vendor/leafs/leaf/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Michael Darko-Duodu
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.

336
vendor/leafs/leaf/README.md vendored Normal file
View file

@ -0,0 +1,336 @@
<!-- markdownlint-disable no-inline-html -->
<p align="center">
<br><br>
<img src="https://leafphp.dev/logo-circle.png" height="100"/>
<br>
</p>
<h1 align="center">Leaf 3</h1>
<p align="center">
<a href="https://packagist.org/packages/leafs/leaf"
><img
src="https://poser.pugx.org/leafs/leaf/v/stable"
alt="Latest Stable Version"
/></a>
<a href="https://packagist.org/packages/leafs/leaf"
><img
src="https://poser.pugx.org/leafs/leaf/downloads"
alt="Total Downloads"
/></a>
<a href="https://packagist.org/packages/leafs/leaf"
><img
src="https://poser.pugx.org/leafs/leaf/license"
alt="License"
/></a>
</p>
<br />
<br />
Leaf is a PHP framework that helps you create clean, simple but powerful web apps and APIs quickly and easily. Leaf introduces a cleaner and much simpler structure to the PHP language while maintaining it's flexibility. With a simple structure and a shallow learning curve, it's an excellent way to rapidly build powerful and high performant web apps and APIs.
Leaf 3 brings a much cleaner, faster and simpler workflow to your apps. Powered by an ecosystem of powerful modules with zero setup and it's ease of use, Leaf now allows you to tackle complexities no matter the scale.
## 📦 Installation
**Install Leaf 3:**
You can create a Leaf 3 app with the [Leaf CLI](https://cli.leafphp.dev)
```sh
leaf create <project-name> --v3 --basic
```
`<project-name>` is your project name
You can also use [Composer](https://getcomposer.org/) to install Leaf 3 in your project quickly.
```bash
composer require leafs/leaf
```
**Install Leaf 2:**
You can install Leaf 2 in your project with Leaf CLI or composer.
```sh
leaf create <project-name> --basic --v2
```
Or with composer
```sh
composer require leafs/leaf v2.6
```
## 🗂 Basic Usage
This is a simple demonstration of Leaf's simplicity.
After [installing](#installation) Leaf, create an _index.php_ file.
```php
<?php
require __DIR__ . "/vendor/autoload.php";
app()->get("/", function () {
response()->json([
"message" => "Welcome!"
]);
});
app()->run();
```
**If you use the Leaf CLI, this is already done for you 🚀.**
You may quickly test this using the built-in PHP server:
```sh
php -S localhost:8000
```
Or with the Leaf CLI:
```sh
leaf serve
```
**You can view the full documentation [here](https://leafphp.dev/)**
## ✈️ The Leaf Ecosystem (Libs & Frameworks)
| Project | Status | Description |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| [leaf](https://github.com/leafsphp/leaf) | [![Latest Stable Version](https://poser.pugx.org/leafs/leaf/v/stable)](https://packagist.org/packages/leafs/leaf) [![Total Downloads](https://poser.pugx.org/leafs/leaf/downloads)](https://packagist.org/packages/leafs/leaf) | Create websites and APIs quickly |
| [leafmvc](https://github.com/leafsphp/leafmvc) | [![Latest Stable Version](https://poser.pugx.org/leafs/mvc/v/stable)](https://packagist.org/packages/leafs/mvc) [![Total Downloads](https://poser.pugx.org/leafs/mvc/downloads)](https://packagist.org/packages/leafs/mvc) | An MVC wrapper for leaf (for general development) |
| [leafapi](https://github.com/leafsphp/leafapi) | [![Latest Stable Version](https://poser.pugx.org/leafs/api/v/stable)](https://packagist.org/packages/leafs/api) [![Total Downloads](https://poser.pugx.org/leafs/api/downloads)](https://packagist.org/packages/leafs/api) | An MVC wrapper for leaf geared towards API development |
| [skeleton](https://github.com/leafsphp/skeleton) | [![Latest Stable Version](https://poser.pugx.org/leafs/skeleton/v/stable)](https://packagist.org/packages/leafs/skeleton) [![Total Downloads](https://poser.pugx.org/leafs/skeleton/downloads)](https://packagist.org/packages/leafs/skeleton) | Leaf boilerplate for rapid development |
| [leaf-ui](https://github.com/leafsphp/leaf-ui) | [![Latest Stable Version](https://poser.pugx.org/leafs/ui/v/stable)](https://packagist.org/packages/leafs/ui) [![Total Downloads](https://poser.pugx.org/leafs/ui/downloads)](https://packagist.org/packages/leafs/ui) | A PHP library for building user interfaces |
| [cli](https://github.com/leafsphp/cli) | [![Latest Stable Version](https://poser.pugx.org/leafs/cli/v/stable)](https://packagist.org/packages/leafs/cli) [![Total Downloads](https://poser.pugx.org/leafs/cli/downloads)](https://packagist.org/packages/leafs/cli) | CLI for creating & interacting with your leaf apps |
## 🧩 The Leaf Ecosystem (Modules)
| Project | Status | Description |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| [alchemy](https://github.com/leafsphp/alchemy) | [![Latest Stable Version](https://poser.pugx.org/leafs/alchemy/v/stable)](https://packagist.org/packages/leafs/alchemy) [![Total Downloads](https://poser.pugx.org/leafs/alchemy/downloads)](https://packagist.org/packages/leafs/alchemy) | Simpler tests for your PHP apps |
| [aloe](https://github.com/leafsphp/aloe) | [![Latest Stable Version](https://poser.pugx.org/leafs/aloe/v/stable)](https://packagist.org/packages/leafs/aloe) [![Total Downloads](https://poser.pugx.org/leafs/aloe/downloads)](https://packagist.org/packages/leafs/aloe) | Smart console helper for leaf mvc, leaf api and skeleton |
| [anchor](https://github.com/leafsphp/anchor) | [![Latest Stable Version](https://poser.pugx.org/leafs/anchor/v/stable)](https://packagist.org/packages/leafs/anchor) [![Total Downloads](https://poser.pugx.org/leafs/anchor/downloads)](https://packagist.org/packages/leafs/anchor) | Basic security tools |
| [auth](https://github.com/leafsphp/auth) | [![Latest Stable Version](https://poser.pugx.org/leafs/auth/v/stable)](https://packagist.org/packages/leafs/auth) [![Total Downloads](https://poser.pugx.org/leafs/auth/downloads)](https://packagist.org/packages/leafs/auth) | Simple but powerful authentication system for your apps |
| [bareui](https://github.com/leafsphp/bareui) | [![Latest Stable Version](https://poser.pugx.org/leafs/bareui/v/stable)](https://packagist.org/packages/leafs/bareui) [![Total Downloads](https://poser.pugx.org/leafs/bareui/downloads)](https://packagist.org/packages/leafs/bareui) | Dead simple templating engine with no compilation (blazing speed) |
| [blade](https://github.com/leafsphp/blade) | [![Latest Stable Version](https://poser.pugx.org/leafs/blade/v/stable)](https://packagist.org/packages/leafs/blade) [![Total Downloads](https://poser.pugx.org/leafs/blade/downloads)](https://packagist.org/packages/leafs/blade) | Laravel blade templating port for leaf |
| [cookie](https://github.com/leafsphp/cookie) | [![Latest Stable Version](https://poser.pugx.org/leafs/cookie/v/stable)](https://packagist.org/packages/leafs/cookie) [![Total Downloads](https://poser.pugx.org/leafs/cookie/downloads)](https://packagist.org/packages/leafs/cookie) | Cookie management without the tears |
| [cors](https://github.com/leafsphp/cors) | [![Latest Stable Version](https://poser.pugx.org/leafs/cors/v/stable)](https://packagist.org/packages/leafs/cors) [![Total Downloads](https://poser.pugx.org/leafs/cors/downloads)](https://packagist.org/packages/leafs/cors) | CORS operations made simple |
| [csrf](https://github.com/leafsphp/csrf) | [![Latest Stable Version](https://poser.pugx.org/leafs/csrf/v/stable)](https://packagist.org/packages/leafs/csrf) [![Total Downloads](https://poser.pugx.org/leafs/csrf/downloads)](https://packagist.org/packages/leafs/csrf) | Basic CSRF protection |
| [date](https://github.com/leafsphp/date) | [![Latest Stable Version](https://poser.pugx.org/leafs/date/v/stable)](https://packagist.org/packages/leafs/date) [![Total Downloads](https://poser.pugx.org/leafs/date/downloads)](https://packagist.org/packages/leafs/date) | PHP dates for humans |
| [db](https://github.com/leafsphp/db) | [![Latest Stable Version](https://poser.pugx.org/leafs/db/v/stable)](https://packagist.org/packages/leafs/db) [![Total Downloads](https://poser.pugx.org/leafs/db/downloads)](https://packagist.org/packages/leafs/db) | Leaf Db from v2 (actively maintained) |
| [db-old](https://github.com/leafsphp/db-old) | [![Latest Stable Version](https://poser.pugx.org/leafs/db-old/v/stable)](https://packagist.org/packages/leafs/db-old) [![Total Downloads](https://poser.pugx.org/leafs/db-old/downloads)](https://packagist.org/packages/leafs/db-old) | Leaf Db from v1 (still maintained) |
| [exception](https://github.com/leafsphp/exceptions) | [![Latest Stable Version](https://poser.pugx.org/leafs/exception/v/stable)](https://packagist.org/packages/leafs/exception) [![Total Downloads](https://poser.pugx.org/leafs/exception/downloads)](https://packagist.org/packages/leafs/exception) | Leaf's exception wrapper (fork of whoops) |
| [experiments](https://github.com/leafsphp/experimental-modules) | [![Latest Stable Version](https://poser.pugx.org/leafs/experimental/v/stable)](https://packagist.org/packages/leafs/experimental) [![Total Downloads](https://poser.pugx.org/leafs/experimental/downloads)](https://packagist.org/packages/leafs/experimental) | collection of experimental modules |
| [fetch](https://github.com/leafsphp/fetch) | [![Latest Stable Version](https://poser.pugx.org/leafs/fetch/v/stable)](https://packagist.org/packages/leafs/fetch) [![Total Downloads](https://poser.pugx.org/leafs/fetch/downloads)](https://packagist.org/packages/leafs/fetch) | HTTP requests made simple |
| [form](https://github.com/leafsphp/form) | [![Latest Stable Version](https://poser.pugx.org/leafs/form/v/stable)](https://packagist.org/packages/leafs/form) [![Total Downloads](https://poser.pugx.org/leafs/form/downloads)](https://packagist.org/packages/leafs/form) | Form processes and validation |
| [fs](https://github.com/leafsphp/fs) | [![Latest Stable Version](https://poser.pugx.org/leafs/fs/v/stable)](https://packagist.org/packages/leafs/fs) [![Total Downloads](https://poser.pugx.org/leafs/fs/downloads)](https://packagist.org/packages/leafs/fs) | Awesome filesystem operations + file uploads |
| [http](https://github.com/leafsphp/http) | [![Latest Stable Version](https://poser.pugx.org/leafs/http/v/stable)](https://packagist.org/packages/leafs/http) [![Total Downloads](https://poser.pugx.org/leafs/http/downloads)](https://packagist.org/packages/leafs/http) | Http operations made simple (request, response, ...) |
| [logger](https://github.com/leafsphp/logger) | [![Latest Stable Version](https://poser.pugx.org/leafs/logger/v/stable)](https://packagist.org/packages/leafs/logger) [![Total Downloads](https://poser.pugx.org/leafs/logger/downloads)](https://packagist.org/packages/leafs/logger) | leaf logger module |
| [mail](https://github.com/leafsphp/mail) | [![Latest Stable Version](https://poser.pugx.org/leafs/mail/v/stable)](https://packagist.org/packages/leafs/mail) [![Total Downloads](https://poser.pugx.org/leafs/mail/downloads)](https://packagist.org/packages/leafs/mail) | Mailing made easy with leaf |
| [mvc-core](https://github.com/leafsphp/mvc-core) | [![Latest Stable Version](https://poser.pugx.org/leafs/mvc-core/v/stable)](https://packagist.org/packages/leafs/mvc-core) [![Total Downloads](https://poser.pugx.org/leafs/mvc-core/downloads)](https://packagist.org/packages/leafs/mvc-core) | Core MVC tools powering our MVC wrappers |
| [password](https://github.com/leafsphp/password) | [![Latest Stable Version](https://poser.pugx.org/leafs/password/v/stable)](https://packagist.org/packages/leafs/password) [![Total Downloads](https://poser.pugx.org/leafs/password/downloads)](https://packagist.org/packages/leafs/password) | Password encryption/validation/hashing in one box |
| [redis](https://github.com/leafsphp/redis) | [![Latest Stable Version](https://poser.pugx.org/leafs/redis/v/stable)](https://packagist.org/packages/leafs/redis) [![Total Downloads](https://poser.pugx.org/leafs/redis/downloads)](https://packagist.org/packages/leafs/redis) | Redis module |
| [router](https://github.com/leafsphp/router) | [![Latest Stable Version](https://poser.pugx.org/leafs/router/v/stable)](https://packagist.org/packages/leafs/router) [![Total Downloads](https://poser.pugx.org/leafs/router/downloads)](https://packagist.org/packages/leafs/router) | Default router for leaf php |
| [session](https://github.com/leafsphp/session) | [![Latest Stable Version](https://poser.pugx.org/leafs/session/v/stable)](https://packagist.org/packages/leafs/session) [![Total Downloads](https://poser.pugx.org/leafs/session/downloads)](https://packagist.org/packages/leafs/session) | PHP sessions made simple |
| [tilly](https://github.com/leafsphp/tilly) | [![Latest Stable Version](https://poser.pugx.org/leafs/tilly/v/stable)](https://packagist.org/packages/leafs/tilly) [![Total Downloads](https://poser.pugx.org/leafs/tilly/downloads)](https://packagist.org/packages/leafs/tilly) | Simple utility 'toolkit' for PHP applications |
| [veins](https://github.com/leafsphp/veins) | [![Latest Stable Version](https://poser.pugx.org/leafs/veins/v/stable)](https://packagist.org/packages/leafs/veins) [![Total Downloads](https://poser.pugx.org/leafs/veins/downloads)](https://packagist.org/packages/leafs/veins) | Leaf veins templating engine |
| [viewi](https://github.com/leafsphp/viewi) | [![Latest Stable Version](https://poser.pugx.org/leafs/viewi/v/stable)](https://packagist.org/packages/leafs/viewi) [![Total Downloads](https://poser.pugx.org/leafs/viewi/downloads)](https://packagist.org/packages/leafs/viewi) | Leaf integration with Viewi PHP |
## 💬 Stay In Touch
- [Twitter](https://twitter.com/leafphp)
- [Join the forum](https://github.com/leafsphp/leaf/discussions/37)
- [Chat on discord](https://discord.com/invite/Pkrm9NJPE3)
## 📓 Learning Leaf 3
- Leaf has a very easy to understand [documentation](https://leafphp.dev) which contains information on all operations in Leaf.
- You can also check out our [youtube channel](https://www.youtube.com/channel/UCllE-GsYy10RkxBUK0HIffw) which has video tutorials on different topics
- You can also learn from [codelabs](https://codelabs.leafphp.dev) and contribute as well.
## 😇 Contributing
We are glad to have you. All contributions are welcome! To get started, familiarize yourself with our [contribution guide](https://leafphp.dev/community/contributing.html) and you'll be ready to make your first pull request 🚀.
To report a security vulnerability, you can reach out to [@mychidarko](https://twitter.com/mychidarko) or [@leafphp](https://twitter.com/leafphp) on twitter. We will coordinate the fix and eventually commit the solution in this project.
### Code contributors
<table>
<tr>
<td align="center">
<a href="https://github.com/mychidarko">
<img src="https://avatars.githubusercontent.com/u/26604242?v=4" width="100%" alt=""/>
<br />
<sub>
<b>Michael Darko</b>
</sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ftonato">
<img src="https://avatars.githubusercontent.com/u/5417662?v=4" width="100%" alt=""/>
<br />
<sub><b>Ademílson F. Tonato</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/herber">
<img src="https://avatars.githubusercontent.com/u/22559657?&v=4" width="100%" alt=""/>
<br />
<sub><b>Tobias Herber</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/pjotrsavitski">
<img src="https://avatars.githubusercontent.com/u/518331?&v=4" width="100%" alt=""/>
<br />
<sub><b>Pjotr Savitski</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/pablouser1">
<img src="https://avatars.githubusercontent.com/u/17802865?&v=4" width="100%" alt=""/>
<br />
<sub><b>Pablo Ferreiro</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/monkeywithacupcake">
<img src="https://avatars.githubusercontent.com/u/7316730?v=4" width="100%" alt=""/>
<br />
<sub><b>jess</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Awilum">
<img src="https://avatars.githubusercontent.com/u/477114?v=4" width="100%" alt=""/>
<br />
<sub><b>Sergey Romanenko</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Kristories">
<img src="https://avatars.githubusercontent.com/u/774338?v=4" width="100%" alt=""/>
<br />
<sub><b>Wahyu Kristianto</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/jeijei4">
<img src="https://avatars.githubusercontent.com/u/21228328?v=4" width="100%" alt=""/>
<br />
<sub><b>JeiHO</b></sub>
</a>
</td>
</tr>
</table>
## 🤩 Sponsoring Leaf
Your cash contributions go a long way to help us make Leaf even better for you. You can sponsor Leaf and any of our packages on [open collective](https://opencollective.com/leaf) or check the [contribution page](https://leafphp.dev/support/) for a list of ways to contribute.
And to all our existing cash/code contributors, we love you all ❤️
### Sponsors
<table>
<tr>
<td align="center">
<a href="https://netlify.com/">
<img src="https://user-images.githubusercontent.com/26604242/186772985-b21c2850-3cc3-41c6-9299-fe9cc5bac46a.png" width="120px" alt=""/>
<br />
<sub><b>Netlify</b></sub>
</a>
</td>
<td align="center">
<a href="#">
<img
src="https://images.opencollective.com/guest-c72a498e/avatar.png"
width="120px"
alt=""
/>
<br />
<sub><b>Casprine</b></sub>
</a>
</td>
<td align="center">
<a href="#">
<img src="https://images.opencollective.com/guest-32634fda/avatar.png" width="120px" alt=""/>
<br />
<sub><b>Vano</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/doc-han">
<img src="https://avatars.githubusercontent.com/u/35382021?v=4" width="120px" alt=""/>
<br />
<sub><b>Farhan Yahaya</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://www.lucaschaplain.design/">
<img src="https://images.opencollective.com/sptaule/aa5f956/avatar/256.png" width="120px" alt=""/>
<br />
<sub><b>Lucas Chaplain</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/dawoodiddris">
<img src="https://avatars.githubusercontent.com/u/31323758?v=4" width="120px" alt=""/>
<br />
<sub><b>Dawuda Iddris</b></sub>
</a>
</td>
<td align="center">
<a href="https://opencollective.com/aaron-smith3">
<img src="https://images.opencollective.com/aaron-smith3/08ee620/avatar/256.png" width="120px" alt=""/>
<br />
<sub><b>Aaron Smith</b></sub>
</a>
</td>
<td align="center">
<a href="https://opencollective.com/peter-bogner">
<img src="https://images.opencollective.com/peter-bogner/avatar/256.png" width="120px" alt=""/>
<br />
<sub><b>Peter Bogner</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/kdgyimah">
<img src="https://avatars.githubusercontent.com/u/29569594?v=4" width="120px" alt=""/>
<br />
<sub><b>King David Gyimah</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/andykwesi">
<img src="https://avatars.githubusercontent.com/u/36931378?v=4" width="120px" alt=""/>
<br />
<sub><b>Andy Kwesi Apenteng</b></sub>
</a>
</td>
</tr>
</table>
## 🤯 Links/Projects
- [Leaf Docs](https://leafphp.dev)
- [Leaf MVC](https://mvc.leafphp.dev)
- [Leaf API](https://api.leafphp.dev)
- [Leaf CLI](https://cli.leafphp.dev)
- [Aloe CLI](https://leafphp.dev/aloe-cli/)

52
vendor/leafs/leaf/composer.json vendored Normal file
View file

@ -0,0 +1,52 @@
{
"name": "leafs/leaf",
"description": "Simple, performant and powerful PHP micro-framework for rapid web app & API development",
"keywords": [
"microframework",
"rest",
"router",
"leaf",
"php",
"framework"
],
"homepage": "https://leafphp.dev",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Michael Darko",
"email": "mickdd22@gmail.com",
"homepage": "https://mychi.netlify.app",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"Leaf\\": "src"
},
"files": [
"src/functions.php"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"leafs/http": "*",
"leafs/router": "*",
"leafs/anchor": "*",
"leafs/exception": "*"
},
"require-dev": {
"pestphp/pest": "^1.21",
"friendsofphp/php-cs-fixer": "^3.0"
},
"scripts": {
"format": "vendor/bin/php-cs-fixer fix --config=.php_cs.dist.php --allow-risky=yes",
"test": "vendor/bin/pest"
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
}
}

433
vendor/leafs/leaf/src/App.php vendored Normal file
View file

@ -0,0 +1,433 @@
<?php
declare(strict_types=1);
namespace Leaf;
/**
* Leaf PHP Framework
* --------
* The easiest way to build simple but powerful apps and APIs quickly.
*
* @author Michael Darko <mickdd22@gmail.com>
* @copyright 2019-2022 Michael Darko
* @link https://leafphp.dev
* @license MIT
* @package Leaf
*/
class App extends Router
{
/**
* Leaf container instance
* @var \Leaf\Helpers\Container
*/
protected $container;
/**
* Callable to be invoked on application error
*/
protected $errorHandler;
/********************************************************************************
* Instantiation and Configuration
*******************************************************************************/
/**
* Constructor
* @param array $userSettings Associative array of application settings
*/
public function __construct(array $userSettings = [])
{
if (class_exists('\Leaf\BareUI')) {
View::attach(\Leaf\BareUI::class, 'template');
}
$this->setupErrorHandler();
$this->container = new \Leaf\Helpers\Container();
$this->loadConfig($userSettings);
if (class_exists('\Leaf\Anchor\CSRF')) {
if (!Anchor\CSRF::token()) {
Anchor\CSRF::init();
}
if (!Anchor\CSRF::verify()) {
$csrfError = Anchor\CSRF::errors()['token'];
Http\Response::status(400);
echo Exception\General::csrf($csrfError);
exit();
}
}
}
protected function loadConfig(array $userSettings = [])
{
if (count($userSettings) > 0) {
Config::set($userSettings);
}
$this->setupDefaultContainer();
$this->loadViewEngines();
}
protected function setupErrorHandler()
{
if (Anchor::toBool($this->config('debug')) === true) {
$debugConfig = [E_ALL, 1];
$this->errorHandler = (new \Leaf\Exception\Run());
$this->errorHandler->register();
} else {
$debugConfig = [0, 0];
$this->setErrorHandler(['\Leaf\Exception\General', 'defaultError'], true);
}
error_reporting($debugConfig[0]);
ini_set('display_errors', (string) $debugConfig[1]);
}
/**
* Set a custom error screen.
*
* @param callable|array $handler The function to be executed
*/
public function setErrorHandler($handler, bool $wrapper = true)
{
if (Anchor::toBool($this->config('debug')) === false) {
$errorHandler = $handler;
if ($this->errorHandler instanceof \Leaf\Exception\Run) {
$this->errorHandler->unregister();
}
if ($handler instanceof \Leaf\Exception\Handler\Handler) {
$this->errorHandler = new \Leaf\Exception\Run();
$this->errorHandler->pushHandler($handler)->register();
}
if ($wrapper) {
$errorHandler = function ($errno, $errstr = '', $errfile = '', $errline = '') use ($handler) {
$exception = Exception\General::toException($errno, $errstr, $errfile, $errline);
Http\Headers::resetStatus(500);
call_user_func_array($handler, [$exception]);
exit();
};
}
set_error_handler($errorHandler);
}
}
/**
* This method adds a method to the global leaf instance
* Register a method and use it globally on the Leaf Object
*/
public function register($name, $value)
{
$this->container->singleton($name, $value);
}
public function loadViewEngines()
{
$views = View::$engines;
if (count($views) > 0) {
foreach ($views as $key => $value) {
$this->container->singleton($key, function () use ($value) {
return $value;
});
}
}
}
private function setupDefaultContainer()
{
// Default request
$this->container->singleton('request', function () {
return new \Leaf\Http\Request();
});
// Default response
$this->container->singleton('response', function () {
return new \Leaf\Http\Response();
});
// Default headers
$this->container->singleton('headers', function () {
return new \Leaf\Http\Headers();
});
if ($this->config('log.enabled')) {
if (class_exists('Leaf\Log')) {
// Default log writer
$this->container->singleton('logWriter', function ($c) {
$logWriter = Config::get('log.writer');
$file = $this->config('log.dir') . $this->config('log.file');
return is_object($logWriter) ? $logWriter : new \Leaf\LogWriter($file, $this->config('log.open') ?? true);
});
// Default log
$this->container->singleton('log', function ($c) {
$log = new \Leaf\Log($c->logWriter);
$log->enabled($this->config('log.enabled'));
$log->level($this->config('log.level'));
return $log;
});
}
}
// Default mode
$mode = $this->config('mode');
if (_env('APP_ENV')) {
$mode = _env('APP_ENV');
}
if (_env('LEAF_MODE')) {
$mode = _env('LEAF_MODE');
}
if (isset($_ENV['LEAF_MODE'])) {
$mode = $_ENV['LEAF_MODE'];
} else {
$envMode = getenv('LEAF_MODE');
if ($envMode !== false) {
$mode = $envMode;
}
}
Config::set([
'mode' => $mode,
'app' => [
'instance' => $this,
'container' => $this->container,
],
]);
}
public function __get($name)
{
return $this->container->get($name);
}
public function __set($name, $value)
{
$this->container->set($name, $value);
}
public function __isset($name)
{
return $this->container->has($name);
}
public function __unset($name)
{
$this->container->remove($name);
}
/**
* Configure Leaf Settings
*
* This method defines application settings and acts as a setter and a getter.
*
* If only one argument is specified and that argument is a string, the value
* of the setting identified by the first argument will be returned, or NULL if
* that setting does not exist.
*
* If only one argument is specified and that argument is an associative array,
* the array will be merged into the existing application settings.
*
* If two arguments are provided, the first argument is the name of the setting
* to be created or updated, and the second argument is the setting value.
*
* @param string|array $name If a string, the name of the setting to set or retrieve. Else an associated array of setting names and values
* @param mixed $value If name is a string, the value of the setting identified by $name
* @return mixed The value of a setting if only one argument is a string
*/
public function config($name, $value = null)
{
if ($value === null && is_string($name)) {
return Config::get($name);
}
Config::set($name, $value);
$this->loadConfig();
$this->setupErrorHandler();
}
/**
* Swap out Leaf request instance
*
* @param mixed $class The new request class to attach
*/
public function setRequestClass($class)
{
$this->container->singleton('request', function () use ($class) {
return new $class();
});
}
/**
* Swap out Leaf response instance
*
* @param mixed $class The new response class to attach
*/
public function setResponseClass($class)
{
$this->container->singleton('response', function () use ($class) {
return new $class();
});
}
/********************************************************************************
* Logging
*******************************************************************************/
/**
* Get application log
*
* @return \Leaf\Log|null|void
*/
public function logger()
{
if (!$this->log) {
trigger_error('You need to enable logging to use this feature! Set log.enabled to true and install the logger module');
}
return $this->log;
}
/********************************************************************************
* Application Accessors
*******************************************************************************/
/**
* Get the Request Headers
* @return \Leaf\Http\Headers
*/
public function headers()
{
return $this->headers;
}
/**
* Get the Request object
* @return \Leaf\Http\Request
*/
public function request()
{
return $this->request;
}
/**
* Get the Response object
* @return \Leaf\Http\Response
*/
public function response()
{
return $this->response;
}
/**
* Create mode-specific code
*
* @param string $mode The mode to run code in
* @param callable $callback The code to run in selected mode.
*/
public static function script($mode, $callback)
{
static::hook('router.before', function () use ($mode, $callback) {
$appMode = Config::get('mode') ?? 'development';
if ($mode === $appMode) {
return $callback();
}
});
}
/********************************************************************************
* Helper Methods
*******************************************************************************/
/**
* Get the absolute path to this Leaf application's root directory
*
* This method returns the absolute path to the Leaf application's
* directory. If the Leaf application is installed in a public-accessible
* sub-directory, the sub-directory path will be included. This method
* will always return an absolute path WITH a trailing slash.
*
* @return string
*/
public function root()
{
return rtrim($_SERVER['DOCUMENT_ROOT'], '/') . rtrim($this->request->getScriptName(), '/') . '/';
}
/**
* Clean current output buffer
*/
protected function cleanBuffer()
{
if (ob_get_level() !== 0) {
ob_clean();
}
}
/**
* Halt
*
* Stop the application and immediately send the response with a
* specific status and body to the HTTP client. This may send any
* type of response: info, success, redirect, client error, or server error.
*
* @param int $status The HTTP response status
* @param string $message The HTTP response body
*/
public static function halt($status, $message = '')
{
if (ob_get_level() !== 0) {
ob_clean();
}
Http\Headers::status($status);
Http\Response::markup($message);
exit();
}
/**
* Evade CORS errors
*
* Cors handler
*
* @param $options Config for cors
*/
public function cors($options = [])
{
if (class_exists('Leaf\Http\Cors')) {
Http\Cors::config($options);
} else {
trigger_error('Cors module not found! Run `composer require leafs/cors` to install the CORS module. This is required to configure CORS.');
}
}
/**
* @inheritdoc
*/
public static function run(?callable $callback = null)
{
if (class_exists('Leaf\Eien\Server')) {
server()
->wrap(function () use ($callback) {
parent::run($callback);
})
->listen();
} else {
return parent::run($callback);
}
}
}

90
vendor/leafs/leaf/src/Config.php vendored Normal file
View file

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Leaf;
/**
* Leaf Config
* -------------
* Configure your leaf app
*/
class Config
{
protected static $settings = [
'app' => ['down' => false, 'instance' => null],
'mode' => 'development',
'debug' => true,
'log' => [
'writer' => null,
'level' => null,
'enabled' => false,
'dir' => __DIR__ . '/../../../../storage/logs/',
'file' => 'log.txt',
'open' => true,
],
'http' => ['version' => '1.1'],
'views' => ['path' => null, 'cachePath' => null],
];
/**
* Set configuration value(s)
*
* @param string|array $item The config(s) to set
* @param mixed $value The value for config. Ignored if $item is an array.
*/
public static function set($item, $value = null)
{
if (is_string($item)) {
if (!strpos($item, '.')) {
static::$settings[$item] = $value;
} else {
static::$settings = array_merge(
static::$settings,
static::mapConfig($item, $value)
);
}
} else {
foreach ($item as $k => $v) {
static::set($k, $v);
}
}
}
/**
* Map nested config to their parents recursively
*/
protected static function mapConfig(string $item, $value = null)
{
$config = explode('.', $item);
if (count($config) > 2) {
trigger_error('Nested config can\'t be more than 1 level deep');
}
return [$config[0] => array_merge(
static::$settings[$config[0]] ?? [],
[$config[1] => $value]
)];
}
/**
* Get configuration
*
* @param string|null $item The config to get. Returns all items if nothing is specified.
*/
public static function get($item = null)
{
if ($item) {
$items = explode('.', $item);
if (count($items) > 1) {
return static::$settings[$items[0]][$items[1]] ?? null;
}
return static::$settings[$item] ?? null;
}
return static::$settings;
}
}

View file

@ -0,0 +1,241 @@
<?php
declare(strict_types=1);
namespace Leaf\Helpers;
/**
* Leaf service container
* ----
* A simple container solution for your leaf apps
*
* @since v2.0
*/
class Container
{
/**
* Key-value array of arbitrary data
* @var array
*/
protected $data = [];
/**
* Constructor
* @param array $items Pre-populate set with this key-value array
*/
public function __construct($items = [])
{
$this->replace($items);
}
/**
* Normalize data key
*
* Used to transform data key into the necessary
* key format for this set. Used in subclasses
* like \Leaf\Http\Headers.
*
* @param string $key The data key
* @return mixed The transformed/normalized data key
*/
protected function normalizeKey($key)
{
return $key;
}
/**
* Set data key to value
* @param string $key The data key
* @param mixed $value The data value
*/
public function set($key, $value)
{
$this->data[$this->normalizeKey($key)] = $value;
}
/**
* Get data value with key
* @param string $key The data key
* @param mixed $default The value to return if data key does not exist
* @return mixed The data value, or the default value
*/
public function get($key, $default = null)
{
if ($this->has($key)) {
$isInvokable = is_object($this->data[$this->normalizeKey($key)]) && method_exists($this->data[$this->normalizeKey($key)], '__invoke');
return $isInvokable ? $this->data[$this->normalizeKey($key)]($this) : $this->data[$this->normalizeKey($key)];
}
return $default;
}
/**
* Add data to set
* @param array $items Key-value array of data to append to this set
*/
public function replace($items)
{
foreach ($items as $key => $value) {
$this->set($key, $value); // Ensure keys are normalized
}
}
/**
* Set the content-type of response to json
*/
public function contentJson()
{
$this->replace(['Content-Type' => 'text/json']);
}
/**
* Set the content-type of response to html
*/
public function contentHtml()
{
$this->replace(['Content-Type' => 'text/html']);
}
/**
* Fetch set data
* @return array This set's key-value data array
*/
public function all()
{
return $this->data;
}
/**
* Fetch set data keys
* @return array This set's key-value data array keys
*/
public function keys()
{
return array_keys($this->data);
}
/**
* Does this set contain a key?
* @param string $key The data key
* @return bool
*/
public function has($key)
{
return array_key_exists($this->normalizeKey($key), $this->data);
}
/**
* Remove value with key from this set
* @param string $key The data key
*/
public function remove($key)
{
unset($this->data[$this->normalizeKey($key)]);
}
/**
* Property Overloading
*/
public function __get($key)
{
return $this->get($key);
}
public function __set($key, $value)
{
$this->set($key, $value);
}
public function __isset($key)
{
return $this->has($key);
}
public function __unset($key)
{
$this->remove($key);
}
/**
* Clear all values
*/
public function clear()
{
$this->data = [];
}
/**
* Array Access
*/
public function offsetExists($offset)
{
return $this->has($offset);
}
public function offsetGet($offset)
{
return $this->get($offset);
}
public function offsetSet($offset, $value)
{
$this->set($offset, $value);
}
public function offsetUnset($offset)
{
$this->remove($offset);
}
/**
* Countable
*/
public function count()
{
return count($this->data);
}
/**
* IteratorAggregate
*/
public function getIterator()
{
return new \ArrayIterator($this->data);
}
/**
* Ensure a value or object will remain globally unique
*
* @param string $key The value or object name
* @param \Closure $value The closure that defines the object
*
* @return mixed
*/
public function singleton($key, $value)
{
$this->set($key, function ($c) use ($value) {
static $object;
if (null === $object) {
$object = $value($c);
}
return $object;
});
}
/**
* Protect closure from being directly invoked
* @param \Closure $callable A closure to keep from being invoked and evaluated
* @return \Closure
*/
public function protect(\Closure $callable)
{
return function () use ($callable) {
return $callable;
};
}
}

74
vendor/leafs/leaf/src/Middleware.php vendored Normal file
View file

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Leaf;
/**
* Middleware
*
* @package Leaf
* @author Michael Darko
* @since 1.5.0
*/
abstract class Middleware
{
/**
* @var \Leaf\App Reference to the primary application instance
*/
protected $app;
/**
* @var mixed Reference to the next downstream middleware
*/
protected $next;
/**
* Set next middleware
*
* This method injects the next downstream middleware into
* this middleware so that it may optionally be called
* when appropriate.
*
* @param \Leaf\Middleware
*/
final public function setNextMiddleware($nextMiddleware)
{
$this->next = $nextMiddleware;
}
/**
* Get next middleware
*
* This method retrieves the next downstream middleware
* previously injected into this middleware.
*
* @return \Leaf\Middleware
*/
final public function getNextMiddleware()
{
return $this->next;
}
/**
* Call the next middleware
*/
final public function next()
{
$nextMiddleware = $this->next;
if (!$nextMiddleware) {
return;
}
$nextMiddleware->call();
}
/**
* Call
*
* Perform actions specific to this middleware and optionally
* call the next downstream middleware.
*/
abstract public function call();
}

46
vendor/leafs/leaf/src/View.php vendored Normal file
View file

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Leaf;
/**
* Leaf View Engine
* -----------
* View engine manager for Leaf.
*
* @author Michael Darko <mickdd22@gmail.com>
* @since v2.4.4
*/
class View
{
public static $engines = [];
/**
* Attach view engine to Leaf view
*
* @param mixed $className The class to attach
* @param string|null $name The key to save view engine with
*/
public static function attach($className, $name = null)
{
$class = new $className();
static::$engines[$name ?? static::getDiIndex($class)] = $class;
Config::set("views.engine", $name ?? static::getDiIndex($class));
}
private static function getDiIndex($class)
{
$className = strtolower(get_class($class));
$fullName = explode("\\", $className);
$className = $fullName[count($fullName) - 1];
return $className;
}
public static function __callstatic($name, $arguments)
{
return static::$engines[$name];
}
}

42
vendor/leafs/leaf/src/functions.php vendored Normal file
View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
if (!function_exists('app')) {
/**
* Return the Leaf instance
*
* @return Leaf\App
*/
function app()
{
$app = Leaf\Config::get("app")["instance"] ?? null;
if (!$app) {
$app = new Leaf\App();
Leaf\Config::set("app", ["instance" => $app]);
}
return $app;
}
}
if (!function_exists('_env')) {
/**
* Gets the value of an environment variable.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
function _env($key, $default = null)
{
$env = array_merge(getenv() ?? [], $_ENV ?? []);
if (!isset($env[$key]) || (isset($env[$key]) && $env[$key] === null)) {
$env[$key] = $default;
}
return $env[$key] ?? $default;
}
}

200
vendor/leafs/router/README.md vendored Normal file
View file

@ -0,0 +1,200 @@
<!-- markdownlint-disable no-inline-html -->
<p align="center">
<br><br>
<img src="https://leafphp.netlify.app/assets/img/leaf3-logo.png" height="100"/>
<br>
</p>
<h1 align="center">Leaf Router</h1>
<p align="center">
<a href="https://packagist.org/packages/leafs/router"
><img
src="https://poser.pugx.org/leafs/router/v/stable"
alt="Latest Stable Version"
/></a>
<a href="https://packagist.org/packages/leafs/router"
><img
src="https://poser.pugx.org/leafs/router/downloads"
alt="Total Downloads"
/></a>
<a href="https://packagist.org/packages/leafs/router"
><img
src="https://poser.pugx.org/leafs/router/license"
alt="License"
/></a>
</p>
<br />
<br />
Leaf router is the core routing engine which powers the Leaf PHP framework. Leaf router is now served as a serve-yourself module which can even be used outside the Leaf ecosystem.
**Leaf Router is still built into Leaf Core and doesn't need to be installed separately.**
## Installation
You can easily install Leaf using [Composer](https://getcomposer.org/).
```bash
composer require leafs/router
```
## Basic Usage
If you are using leaf router with Leaf, you can build your leaf apps just as you've always done:
```php
<?php
require __DIR__ . "vendor/autoload.php";
// GET example
app()->get("/", function () {
response()->json([
"message" => "Welcome!"
]);
});
// MATCH example
app()->match("GET", "/test", function () {
response()->json([
"message" => "Test!"
]);
});
app()->run();
```
If however, you are using leaf router outside of the leaf framework, you simply need to call these methods on the `Leaf\Router` object:
```php
<?php
use Leaf\Router;
require __DIR__ . "vendor/autoload.php";
// GET example
Router::get("/", function () {
echo json_encode([
"message" => "Welcome!"
]);
});
// MATCH example
Router::match("GET", "/test", function () {
echo json_encode([
"message" => "Test!"
]);
});
Router::run();
```
You may quickly test this using the built-in PHP server:
```bash
php -S localhost:8000
```
## 💬 Stay In Touch
- [Twitter](https://twitter.com/leafphp)
- [Join the forum](https://github.com/leafsphp/leaf/discussions/37)
- [Chat on discord](https://discord.com/invite/Pkrm9NJPE3)
## 📓 Learning Leaf 3
- Leaf has a very easy to understand [documentation](https://leafphp.dev) which contains information on all operations in Leaf.
- You can also check out our [youtube channel](https://www.youtube.com/channel/UCllE-GsYy10RkxBUK0HIffw) which has video tutorials on different topics
- We are also working on codelabs which will bring hands-on tutorials you can follow and contribute to.
## 😇 Contributing
We are glad to have you. All contributions are welcome! To get started, familiarize yourself with our [contribution guide](https://leafphp.dev/community/contributing.html) and you'll be ready to make your first pull request 🚀.
To report a security vulnerability, you can reach out to [@mychidarko](https://twitter.com/mychidarko) or [@leafphp](https://twitter.com/leafphp) on twitter. We will coordinate the fix and eventually commit the solution in this project.
### Code contributors
<table>
<tr>
<td align="center">
<a href="https://github.com/mychidarko">
<img src="https://avatars.githubusercontent.com/u/26604242?v=4" width="120px" alt=""/>
<br />
<sub>
<b>Michael Darko</b>
</sub>
</a>
</td>
</tr>
</table>
## 🤩 Sponsoring Leaf
Your cash contributions go a long way to help us make Leaf even better for you. You can sponsor Leaf and any of our packages on [open collective](https://opencollective.com/leaf) or check the [contribution page](https://leafphp.dev/support/) for a list of ways to contribute.
And to all our existing cash/code contributors, we love you all ❤️
### Cash contributors
<table>
<tr>
<td align="center">
<a href="https://opencollective.com/aaron-smith3">
<img src="https://images.opencollective.com/aaron-smith3/08ee620/avatar/256.png" width="120px" alt=""/>
<br />
<sub><b>Aaron Smith</b></sub>
</a>
</td>
<td align="center">
<a href="https://opencollective.com/peter-bogner">
<img src="https://images.opencollective.com/peter-bogner/avatar/256.png" width="120px" alt=""/>
<br />
<sub><b>Peter Bogner</b></sub>
</a>
</td>
<td align="center">
<a href="#">
<img src="https://images.opencollective.com/guest-32634fda/avatar.png" width="120px" alt=""/>
<br />
<sub><b>Vano</b></sub>
</a>
</td>
<td align="center">
<a href="#">
<img
src="https://images.opencollective.com/guest-c72a498e/avatar.png"
width="120px"
alt=""
/>
<br />
<sub><b>Casprine</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/doc-han">
<img src="https://avatars.githubusercontent.com/u/35382021?v=4" width="120px" alt=""/>
<br />
<sub><b>Farhan Yahaya</b></sub>
</a>
</td>
<td align="center">
<a href="https://www.lucaschaplain.design/">
<img src="https://images.opencollective.com/sptaule/aa5f956/avatar/256.png" width="120px" alt=""/>
<br />
<sub><b>Lucas Chaplain</b></sub>
</a>
</td>
</tr>
</table>
## 🤯 Links/Projects
- [Aloe CLI](https://leafphp.dev/aloe-cli/)
- [Leaf Docs](https://leafphp.dev)
- [Leaf MVC](https://mvc.leafphp.dev)
- [Leaf API](https://api.leafphp.dev)
- [Leaf CLI](https://cli.leafphp.dev)

41
vendor/leafs/router/composer.json vendored Normal file
View file

@ -0,0 +1,41 @@
{
"name": "leafs/router",
"description": "Leaf router module for Leaf PHP.",
"keywords": [
"rest",
"router",
"leaf",
"php",
"framework"
],
"homepage": "https://leafphp.netlify.app/#/modules/router",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Michael Darko",
"email": "mickdd22@gmail.com",
"homepage": "https://mychi.netlify.app",
"role": "Developer"
}
],
"autoload": {
"psr-4": {
"Leaf\\": "src"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"leafs/http": "*",
"leafs/anchor": "*"
},
"require-dev": {
"pestphp/pest": "^1.21"
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
}
}

17
vendor/leafs/router/phpunit.xml vendored Normal file
View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Test Suite">
<directory suffix=".test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
</phpunit>

326
vendor/leafs/router/src/Router.php vendored Normal file
View file

@ -0,0 +1,326 @@
<?php
declare(strict_types=1);
namespace Leaf;
use Leaf\Router\Core;
/**
* Leaf Router
* ---------------
* Super simple and powerful routing with Leaf
*
* @author Michael Darko
* @since 1.2.0
* @version 3.0
*/
class Router extends Core
{
/**
* Set the 404 handling function.
*
* @param object|callable $handler The function to be executed
*/
public static function set404($handler = null)
{
if (is_callable($handler)) {
static::$notFoundHandler = $handler;
} else {
static::$notFoundHandler = function () {
\Leaf\Exception\General::default404();
};
}
}
/**
* Set a custom maintenance mode callback.
*
* @param callable|null $handler The function to be executed
*/
public static function setDown(?callable $handler = null)
{
static::$downHandler = $handler;
}
/**
* Mounts a collection of callbacks onto a base route.
*
* @param string $path The route sub pattern/path to mount the callbacks on
* @param callable|array $handler The callback method
*/
public static function mount(string $path, $handler)
{
$groupOptions = [
'namespace' => null,
];
list($handler, $groupOptions) = static::mapHandler(
$handler,
$groupOptions
);
$namespace = static::$namespace;
$groupRoute = static::$groupRoute;
if ($groupOptions['namespace']) {
static::$namespace = $groupOptions['namespace'];
}
static::$groupRoute = $path;
call_user_func($handler);
static::$namespace = $namespace;
static::$groupRoute = $groupRoute;
}
/**
* Alias for mount
*
* @param string $path The route sub pattern/path to mount the callbacks on
* @param callable|array $handler The callback method
*/
public static function group(string $path, $handler)
{
static::mount($path, $handler);
}
// ------------------- main routing stuff -----------------------
/**
* Store a route and it's handler
*
* @param string $methods Allowed HTTP methods (separated by `|`)
* @param string $pattern The route pattern/path to match
* @param string|array|callable $handler The handler for route when matched
*/
public static function match(string $methods, string $pattern, $handler)
{
$pattern = static::$groupRoute . '/' . trim($pattern, '/');
$pattern = static::$groupRoute ? rtrim($pattern, '/') : $pattern;
$routeOptions = [
'name' => null,
'middleware' => null,
'namespace' => null,
];
if (is_string($handler)) {
$namespace = static::$namespace;
if ($routeOptions['namespace']) {
static::$namespace = $routeOptions['namespace'];
}
$handler = str_replace('\\\\', '\\', static::$namespace . "\\$handler");
static::$namespace = $namespace;
}
list($handler, $routeOptions) = static::mapHandler(
$handler,
$routeOptions
);
foreach (explode('|', $methods) as $method) {
static::$routes[$method][] = [
'pattern' => $pattern,
'handler' => $handler,
'name' => $routeOptions['name'] ?? ''
];
}
static::$appRoutes[] = [
'methods' => explode('|', $methods),
'pattern' => $pattern,
'handler' => $handler,
'name' => $routeOptions['name'] ?? ''
];
if ($routeOptions['name']) {
static::$namedRoutes[$routeOptions['name']] = $pattern;
}
if ($routeOptions['middleware']) {
static::before($methods, $pattern, $routeOptions['middleware']);
}
}
/**
* Add a route with all available HTTP methods
*
* @param string $pattern The route pattern/path to match
* @param string|array|callable The handler for route when matched
*/
public static function all(string $pattern, $handler)
{
static::match(
'GET|POST|PUT|DELETE|OPTIONS|PATCH|HEAD',
$pattern,
$handler
);
}
/**
* Add a route with GET method
*
* @param string $pattern The route pattern/path to match
* @param string|array|callable The handler for route when matched
*/
public static function get(string $pattern, $handler)
{
static::match('GET', $pattern, $handler);
}
/**
* Add a route with POST method
*
* @param string $pattern The route pattern/path to match
* @param string|array|callable The handler for route when matched
*/
public static function post(string $pattern, $handler)
{
static::match('POST', $pattern, $handler);
}
/**
* Add a route with PUT method
*
* @param string $pattern The route pattern/path to match
* @param string|array|callable The handler for route when matched
*/
public static function put(string $pattern, $handler)
{
static::match('PUT', $pattern, $handler);
}
/**
* Add a route with PATCH method
*
* @param string $pattern The route pattern/path to match
* @param string|array|callable The handler for route when matched
*/
public static function patch(string $pattern, $handler)
{
static::match('PATCH', $pattern, $handler);
}
/**
* Add a route with OPTIONS method
*
* @param string $pattern The route pattern/path to match
* @param string|array|callable The handler for route when matched
*/
public static function options(string $pattern, $handler)
{
static::match('OPTIONS', $pattern, $handler);
}
/**
* Add a route with DELETE method
*
* @param string $pattern The route pattern/path to match
* @param string|array|callable The handler for route when matched
*/
public static function delete(string $pattern, $handler)
{
static::match('DELETE', $pattern, $handler);
}
/**
* Add a route that sends an HTTP redirect
*
* @param string $from The url to redirect from
* @param string $to The url to redirect to
* @param int $status The http status code for redirect
*/
public static function redirect(
string $from,
string $to,
int $status = 302
) {
static::get($from, function () use ($to, $status) {
return header("location: $to", true, $status);
});
}
/**
* Create a resource route for using controllers.
*
* This creates a routes that implement CRUD functionality in a controller
* `/posts` creates:
* - `/posts` - GET | HEAD - Controller@index
* - `/posts` - POST - Controller@store
* - `/posts/{id}` - GET | HEAD - Controller@show
* - `/posts/create` - GET | HEAD - Controller@create
* - `/posts/{id}/edit` - GET | HEAD - Controller@edit
* - `/posts/{id}/edit` - POST | PUT | PATCH - Controller@update
* - `/posts/{id}/delete` - POST | DELETE - Controller@destroy
*
* @param string $pattern The base route to use eg: /post
* @param string $controller to handle route eg: PostController
*/
public static function resource(string $pattern, string $controller)
{
static::match('GET|HEAD', $pattern, "$controller@index");
static::post($pattern, "$controller@store");
static::match('GET|HEAD', "$pattern/create", "$controller@create");
static::match('POST|DELETE', "$pattern/{id}/delete", "$controller@destroy");
static::match('POST|PUT|PATCH', "$pattern/{id}/edit", "$controller@update");
static::match('GET|HEAD', "$pattern/{id}/edit", "$controller@edit");
static::match('GET|HEAD', "$pattern/{id}", "$controller@show");
}
/**
* Create a resource route for using controllers without the create and edit actions.
*
* This creates a routes that implement CRUD functionality in a controller
* `/posts` creates:
* - `/posts` - GET | HEAD - Controller@index
* - `/posts` - POST - Controller@store
* - `/posts/{id}` - GET | HEAD - Controller@show
* - `/posts/{id}/edit` - POST | PUT | PATCH - Controller@update
* - `/posts/{id}/delete` - POST | DELETE - Controller@destroy
*
* @param string $pattern The base route to use eg: /post
* @param string $controller to handle route eg: PostController
*/
public static function apiResource(string $pattern, string $controller)
{
static::match('GET|HEAD', $pattern, "$controller@index");
static::post($pattern, "$controller@store");
static::match('POST|DELETE', "$pattern/{id}/delete", "$controller@destroy");
static::match('POST|PUT|PATCH', "$pattern/{id}/edit", "$controller@update");
static::match('GET|HEAD', "$pattern/{id}", "$controller@show");
}
/**
* Redirect to another route
*
* @param string|array $route The route to redirect to
* @param array|null $data Data to pass to the next route
*/
public static function push($route, ?array $data = null)
{
if (is_array($route)) {
if (!isset(static::$namedRoutes[$route[0]])) {
trigger_error('Route named ' . $route[0] . ' not found');
}
$route = static::$namedRoutes[$route[0]];
}
if ($data) {
$args = '?';
foreach ($data as $key => $value) {
$args .= "$key=$value&";
}
$data = rtrim($args, '&');
}
return header("location: $route$data");
}
}

490
vendor/leafs/router/src/Router/Core.php vendored Normal file
View file

@ -0,0 +1,490 @@
<?php
declare(strict_types=1);
namespace Leaf\Router;
/**
* Leaf Router [Core]
* ---------------
* Core module for leaf router
*
* @author Michael Darko
* @since 3.0
* @version 1.0
*/
class Core
{
/**
* Callable to be invoked if no matching routes are found
*/
protected static $notFoundHandler;
/**
* Callable to be invoked if app is down
*/
protected static $downHandler;
/**
* Router configuration
*/
protected static $config = [
'mode' => 'development',
'debug' => true,
'app.down' => false,
];
/**
* 'Middleware' to run at specific times
*/
protected static $hooks = [
'router.before' => false,
'router.before.route' => false,
'router.before.dispatch' => false,
'router.after.dispatch' => false,
'router.after.route' => false,
'router.after' => false,
];
/**
* Leaf app middleware
*/
protected static $middleware = [];
/**
* Route specific middleware
*/
protected static $routeSpecificMiddleware = [];
/**
* All added routes and their handlers
*/
protected static $routes = [];
/**
* Sorted list of routes and their handlers
*/
protected static $appRoutes = [];
/**
* All named routes
*/
protected static $namedRoutes = [];
/**
* Current group base path
*/
protected static $groupRoute = '';
/**
* Default controller namespace
*/
protected static $namespace = '';
/**
* The Request Method that needs to be handled
*/
protected static $requestedMethod = '';
/**
* The Server Base Path for Router Execution
*/
protected static $serverBasePath = '';
/**
* Configure leaf router
*/
public static function configure(array $config)
{
static::$config = array_merge(static::$config, $config);
}
/**
* Force call the Leaf URL handler
*
* @param string $method The method to call
* @param string $url The uri to force
*/
public static function handleUrl(string $method, string $url)
{
if (isset(static::$routes[$method])) {
static::handle(
static::$routes[$method],
true,
$url
);
}
}
/**
* Get all routes registered in your leaf app
*/
public static function routes(): array
{
return static::$appRoutes;
}
/**
* Set a global namespace for your handlers
*
* @param string $namespace The global namespace to set
*/
public static function setNamespace(string $namespace)
{
static::$namespace = $namespace;
}
/**
* Get the global handler namespace.
*
* @return string The given namespace if exists
*/
public static function getNamespace(): string
{
return static::$namespace;
}
/**
* Map handler and options
*/
protected static function mapHandler($handler, $options): array
{
if (is_array($handler)) {
$handlerData = $handler;
if (isset($handler['handler'])) {
$handler = $handler['handler'];
unset($handlerData['handler']);
} else {
foreach ($handler as $key => $value) {
if (
(is_numeric($key) && is_callable($value))
|| is_numeric($key) && is_string($value) && strpos($value, '@')
) {
$handler = $handler[$key];
unset($handlerData[$key]);
break;
}
}
}
foreach ($handlerData as $key => $value) {
if (isset($value)) {
$options[$key] = $value;
}
}
}
return [$handler, $options];
}
/**
* Add a router hook
*
* Available hooks
* - router.before
* - router.before.route
* - router.before.dispatch
* - router.after.dispatch
* - router.after.route
* - router.after
*
* @param string $name The hook to set
* @param callable|null $handler The hook handler
*/
public static function hook(string $name, callable $handler)
{
if (!isset(static::$hooks[$name])) {
trigger_error("$name is not a valid hook! Refer to the docs for all supported hooks");
}
static::$hooks[$name] = $handler;
}
/**
* Call a router hook
*
* @param string $name The hook to call
*/
private static function callHook(string $name)
{
return is_callable(static::$hooks[$name]) ? static::$hooks[$name]() : null;
}
/**
* Add a route specific middleware
*
* @param string $methods Allowed methods, separated by |
* @param string|array $path The path/route to apply middleware on
* @param callable $handler The middleware handler
*/
public static function before(string $methods, $path, callable $handler)
{
if (is_array($path)) {
if (!isset(static::$namedRoutes[$path[0]])) {
trigger_error('Route named ' . $path[0] . ' not found');
}
$path = static::$namedRoutes[$path[0]];
}
$path = static::$groupRoute . '/' . trim($path, '/');
$path = static::$groupRoute ? rtrim($path, '/') : $path;
foreach (explode('|', $methods) as $method) {
static::$routeSpecificMiddleware[$method][] = [
'pattern' => $path,
'handler' => $handler,
];
}
}
/**
* Add middleware
*
* This method prepends new middleware to the application middleware stack.
* The argument must be an instance that subclasses Leaf_Middleware.
*
* @param \Leaf\Middleware $newMiddleware The middleware to set
*/
public static function use($newMiddleware)
{
if (in_array($newMiddleware, static::$middleware)) {
$middleware_class = get_class($newMiddleware);
throw new \RuntimeException("Circular Middleware setup detected. Tried to queue the same Middleware instance ({$middleware_class}) twice.");
}
if (!empty(static::$middleware)) {
$newMiddleware->setNextMiddleware(static::$middleware[0]);
}
array_unshift(static::$middleware, $newMiddleware);
}
/**
* Return server base Path, and define it if isn't defined.
*
* @return string
*/
public static function getBasePath(): string
{
if (static::$serverBasePath === '') {
static::$serverBasePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)) . '/';
}
return static::$serverBasePath;
}
/**
* Explicilty sets the server base path. To be used when your entry script path differs from your entry URLs.
* @see https://github.com/bramus/router/issues/82#issuecomment-466956078
*
* @param string
*/
public static function setBasePath($serverBasePath)
{
static::$serverBasePath = $serverBasePath;
}
/**
* Define the current relative URI.
*
* @return string
*/
public static function getCurrentUri(): string
{
// Get the current Request URI and remove rewrite base path from it (= allows one to run the router in a sub folder)
$uri = substr(rawurldecode($_SERVER['REQUEST_URI']), strlen(static::getBasePath()));
if (strstr($uri, '?')) {
$uri = substr($uri, 0, strpos($uri, '?'));
}
return '/' . trim($uri, '/');
}
/**
* Get route info of the current route
*
* @return array The route info array
*/
public static function getRoute(): array
{
return [
'path' => static::getCurrentUri(),
'method' => \Leaf\Http\Request::getMethod(),
];
}
/**
* Dispatch your application routes
*/
public static function run(?callable $callback = null)
{
$config = static::$config;
if (class_exists('Leaf\App')) {
$config = array_merge($config, [
'mode' => \Leaf\Config::get('mode'),
'app.down' => \Leaf\Anchor::toBool(\Leaf\Config::get('app.down')) ?? false,
'debug' => \Leaf\Anchor::toBool(\Leaf\Config::get('debug')) ?? false,
]);
}
if ($config['app.down'] === true) {
if (!static::$downHandler) {
if (class_exists('Leaf\App')) {
static::$downHandler = function () {
\Leaf\Exception\General::defaultDown();
};
} else {
static::$downHandler = function () {
echo 'App is down for maintenance';
};
}
}
return static::invoke(static::$downHandler);
}
$middleware = static::$middleware;
if (is_callable($callback)) {
static::hook('router.after', $callback);
}
static::callHook('router.before');
if (count($middleware) > 0) {
if (is_string($middleware[0])) {
(new $middleware[0])->call();
} else {
$middleware[0]->call();
}
}
static::callHook('router.before.route');
static::$requestedMethod = \Leaf\Http\Request::getMethod();
if (isset(static::$routeSpecificMiddleware[static::$requestedMethod])) {
static::handle(static::$routeSpecificMiddleware[static::$requestedMethod]);
}
static::callHook('router.before.dispatch');
$numHandled = 0;
if (isset(static::$routes[static::$requestedMethod])) {
$numHandled = static::handle(
static::$routes[static::$requestedMethod],
true
);
}
static::callHook('router.after.dispatch');
if ($numHandled === 0) {
if (!static::$notFoundHandler) {
if (class_exists('Leaf\App')) {
static::$notFoundHandler = function () {
\Leaf\Exception\General::default404();
};
} else {
static::$notFoundHandler = function () {
echo 'Route not found';
};
}
}
static::invoke(static::$notFoundHandler);
}
// if it originally was a HEAD request, clean up after ourselves by emptying the output buffer
if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
ob_end_clean();
}
static::callHook('router.after.route');
restore_error_handler();
return static::callHook('router.after') ?? ($numHandled !== 0);
}
/**
* Handle a set of routes: if a match is found, execute the relating handling function.
*
* @param array $routes Collection of route patterns and their handling functions
* @param bool $quitAfterRun Does the handle function need to quit after one route was matched?
* @param string|null $uri The URI to call (automatically set if nothing is passed).
*
* @return int The number of routes handled
*/
private static function handle(array $routes, bool $quitAfterRun = false, ?string $uri = null): int
{
$numHandled = 0;
$uri = $uri ?? static::getCurrentUri();
foreach ($routes as $route) {
// Replace all curly braces matches {} into word patterns (like Laravel)
$route['pattern'] = preg_replace('/\/{(.*?)}/', '/(.*?)', $route['pattern']);
// we have a match!
if (preg_match_all('#^' . $route['pattern'] . '$#', $uri, $matches, PREG_OFFSET_CAPTURE)) {
// Rework matches to only contain the matches, not the orig string
$matches = array_slice($matches, 1);
// Extract the matched URL parameters (and only the parameters)
$params = array_map(function ($match, $index) use ($matches) {
// We have a following parameter: take the substring from the current param position until the next one's position (thank you PREG_OFFSET_CAPTURE)
if (isset($matches[$index + 1]) && isset($matches[$index + 1][0]) && is_array($matches[$index + 1][0])) {
return trim(substr($match[0][0], 0, $matches[$index + 1][0][1] - $match[0][1]), '/');
}
// We have no following parameters: return the whole lot
return isset($match[0][0]) ? trim($match[0][0], '/') : null;
}, $matches, array_keys($matches));
// Call the handling function with the URL parameters if the desired input is callable
static::invoke($route['handler'], $params);
++$numHandled;
if ($quitAfterRun) {
break;
}
}
}
return $numHandled;
}
private static function invoke($handler, $params = [])
{
if (is_callable($handler)) {
call_user_func_array(
$handler,
$params
);
}
// If not, check the existence of special parameters
elseif (stripos($handler, '@') !== false) {
list($controller, $method) = explode('@', $handler);
if (!class_exists($controller)) {
trigger_error("$controller not found. Cross-check the namespace if you're sure the file exists");
}
if (!method_exists($controller, $method)) {
trigger_error("$method method not found in $controller");
}
// First check if is a static method, directly trying to invoke it.
// If isn't a valid static method, we will try as a normal method invocation.
if (call_user_func_array([new $controller(), $method], $params) === false) {
// Try to call the method as a non-static method. (the if does nothing, only avoids the notice)
if (forward_static_call_array([$controller, $method], $params) === false);
}
}
}
}

49
vendor/leafs/router/tests/Pest.php vendored Normal file
View file

@ -0,0 +1,49 @@
<?php
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "uses()" function to bind a different classes or traits.
|
*/
// uses(Tests\TestCase::class)->in('Feature');
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
$_SERVER['REQUEST_URI'] = '/';
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
function something()
{
// ..
}

38
vendor/leafs/router/tests/core.test.php vendored Normal file
View file

@ -0,0 +1,38 @@
<?php
use Leaf\Router;
test('static call', function () {
expect(Router::routes())->toBeArray();
});
test('set 404', function () {
$r0 = new Router;
$r0->set404(function () {
echo '404';
});
ob_start();
$r0->run();
expect(ob_get_contents())->toBe('404');
ob_end_clean();
});
test('set down', function () {
$router = new Router;
$router->configure(['app.down' => true]);
$router->setDown(function () {
echo 'down';
});
ob_start();
$router->run();
expect(ob_get_contents())->toBe('down');
ob_end_clean();
// clean up
$router->configure(['app.down' => false]);
});

View file

@ -0,0 +1,84 @@
<?php
use Leaf\Router;
class TGroup
{
static $val = true;
}
test('route groups', function () {
$_SERVER['REQUEST_URI'] = '/group/route';
$router = new Router;
TGroup::$val = true;
$router->mount('/group', function () use ($router) {
$router->get('/route', function () {
TGroup::$val = false;
});
});
$router->run();
expect(TGroup::$val)->toBe(false);
});
test('route groups with array', function () {
$_SERVER['REQUEST_URI'] = '/group/route';
$router = new Router;
TGroup::$val = true;
$router->mount('/group', [function () use ($router) {
$router->get('/route', function () {
TGroup::$val = false;
});
}]);
$router->run();
expect(TGroup::$val)->toBe(false);
});
test('route groups with namespace', function () {
$_SERVER['REQUEST_URI'] = '/group/route';
$router = new Router;
$router->mount('/group', ['namespace' => 'App\Controllers', function () use ($router) {
$router->get('/route', 'ExampleController');
}]);
$router->run();
// check if the namespace was registered
expect(strpos(
json_encode($router->routes()),
'App\\\\Controllers\\\\ExampleController'
))->toBeTruthy();
});
test('route groups with different namespace', function () {
$_SERVER['REQUEST_URI'] = '/group/route';
$router = new Router;
$router->setNamespace('Controllers');
TGroup::$val = true;
$router->mount('/group', ['namespace' => 'App\Controllers', function () use ($router) {
$router->get('/route', 'ExampleController');
}]);
$router->run();
// check if the App\Controllers namespace was registered
// instead of the global Controllers namespace
expect(strpos(
json_encode($router->routes()),
'App\\\\Controllers\\\\ExampleController'
))->toBeTruthy();
});

View file

@ -0,0 +1,53 @@
<?php
use Leaf\Router;
test('hook execution order', function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['REQUEST_URI'] = '/post';
$router = new Router;
$router->hook('router.after.dispatch', function () {
echo '5';
});
$router->hook('router.before.route', function () {
echo '2';
});
$router->hook('router.before.dispatch', function () {
echo '3';
});
// our main route
$router->post('/post', function () {
echo '4';
});
$router->hook('router.after.route', function () {
echo '6';
});
$router->hook('router.after', function () {
echo '7';
});
$router->hook('router.before', function () {
echo '1';
});
ob_start();
$router->run();
expect(ob_get_contents())->toBe('1234567');
ob_end_clean();
// cleanup
$router->hook('router.before', function () {});
$router->hook('router.before.route', function () {});
$router->hook('router.before.dispatch', function () {});
$router->hook('router.after.dispatch', function () {});
$router->hook('router.after.route', function () {});
$router->hook('router.after', function () {});
});

View file

@ -0,0 +1,113 @@
<?php
use Leaf\Router;
class TMid
{
static $callstack = '';
}
test('leaf middleware', function () {
TMid::$callstack = '';
class AppMid {
public function call()
{
TMid::$callstack .= '1';
}
}
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/';
$router = new Router;
$router->use(new AppMid);
$router->get('/', function () {
TMid::$callstack .= '2';
});
$router->run();
expect(TMid::$callstack)->toBe('12');
});
test('in-route middleware', function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['REQUEST_URI'] = '/';
$m = function () {
echo '1';
};
$router = new Router;
$router->post('/', ['middleware' => $m, function () {
echo '2';
}]);
ob_start();
$router->run();
expect(ob_get_contents())->toBe('12');
ob_end_clean();
});
test('before route middleware', function () {
$_SERVER['REQUEST_METHOD'] = 'PUT';
$_SERVER['REQUEST_URI'] = '/';
$router = new Router;
$router->before('PUT', '/', function () {
echo '1';
});
$router->put('/', function () {
echo '2';
});
ob_start();
$router->run();
expect(ob_get_contents())->toBe('12');
ob_end_clean();
});
test('before router middleware', function () {
$_SERVER['REQUEST_METHOD'] = 'PATCH';
$_SERVER['REQUEST_URI'] = '/test';
$router = new Router;
$router->before('PATCH', '/.*', function () {
echo '1';
});
$router->patch('/test', function () {
echo '2';
});
ob_start();
$router->run();
expect(ob_get_contents())->toBe('12');
ob_end_clean();
});
test('after router middleware', function () {
$_SERVER['REQUEST_METHOD'] = 'PUT';
$_SERVER['REQUEST_URI'] = '/test';
$router = new Router;
$router->put('/test', function () {
echo '1';
});
ob_start();
$router->run(function () {
echo '2';
});
expect(ob_get_contents())->toBe('12');
ob_end_clean();
});

View file

@ -0,0 +1,110 @@
<?php
use Leaf\Router;
class TRoute
{
static $val = true;
}
test('router match', function () {
$_SERVER['REQUEST_METHOD'] = 'PUT';
$_SERVER['REQUEST_URI'] = '/put';
TRoute::$val = true;
$router = new Router;
$router->match('PUT', '/put', function () {
TRoute::$val = false;
});
$router->run();
expect(TRoute::$val)->toBe(false);
});
test('get route', function () {
TRoute::$val = true;
$router = new Router;
$router->get('/', function () {
TRoute::$val = false;
});
$router->run();
expect(TRoute::$val)->toBe(false);
});
test('post route', function () {
$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['REQUEST_URI'] = '/delete';
TRoute::$val = true;
$router = new Router;
$router->post('/delete', function () {
TRoute::$val = false;
});
$router->run();
expect(TRoute::$val)->toBe(false);
});
test('put route', function () {
$_SERVER['REQUEST_METHOD'] = 'PUT';
$_SERVER['REQUEST_URI'] = '/patch';
TRoute::$val = true;
$router = new Router;
$router->put('/patch', function () {
TRoute::$val = false;
});
$router->run();
expect(TRoute::$val)->toBe(false);
});
test('patch route', function () {
$_SERVER['REQUEST_METHOD'] = 'PATCH';
$_SERVER['REQUEST_URI'] = '/';
TRoute::$val = true;
$router = new Router;
$router->patch('/', function () {
TRoute::$val = false;
});
$router->run();
expect(TRoute::$val)->toBe(false);
});
test('options route', function () {
$_SERVER['REQUEST_METHOD'] = 'OPTIONS';
$_SERVER['REQUEST_URI'] = '/';
TRoute::$val = true;
$router = new Router;
$router->options('/', function () {
TRoute::$val = false;
});
$router->run();
expect(TRoute::$val)->toBe(false);
});
test('delete route', function () {
$_SERVER['REQUEST_METHOD'] = 'DELETE';
$_SERVER['REQUEST_URI'] = '/';
TRoute::$val = true;
$router = new Router;
$router->delete('/', function () {
TRoute::$val = false;
});
$router->run();
expect(TRoute::$val)->toBe(false);
});

View file

@ -0,0 +1,10 @@
<?php
namespace App\Controllers;
class ExampleController {
public function puts()
{
echo 'example controller';
}
};

View file

@ -0,0 +1,11 @@
<?php
namespace Controllers;
class TestController
{
public function puts(string $data)
{
return $data;
}
};