Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ Divergence is a PHP framework designed for rapid development and modern practice

## [Documentation](https://github.com/Divergence/docs#divergence-framework-documentation)
## [Getting Started](https://github.com/Divergence/docs/blob/release/gettingstarted.md#getting-started)
## [V3 Architecture](docs/v3-architecture.md)

## Minimal Model

Expand All @@ -33,7 +32,7 @@ class Article extends Model


## Purpose
This collection of classes contains my favorite building blocks for developing websites with PHP and they have an impressive track record with hundreds of currently active websites using one version or another of the classes in this framework. While they were originally written years ago they are all PSR compatible and support modern practices out of the box.
Divergence is a full-featured ActiveRecord framework built on a reflection-driven DTO-style backend. It is designed for performance, and it backs that up with benchmarks. It gives developers a fast procedural-global path for getting real work done, while its internal abstractions stay disciplined and modern. Divergence follows PSR-4, PSR-7, and PSR-15 wherever doing so strengthens the framework instead of turning it into ceremony.

Unit testing the code base and providing code coverage is a primary goal of this project.

Expand All @@ -44,7 +43,7 @@ Unit testing the code base and providing code coverage is a primary goal of this
* Declare relationships with static arrays or PHP 8 attributes (`#[Relation(...)]`).
* Built in support for relationships and object versioning.
* Speed up prototyping and automate new deployments by automatically creating tables based on your models when none are found.
* Built in support for MySQL and SQLite.
* Built in support for MySQL, PostgreSQL, and SQLite.

* Routing
* Simpler, faster, tree based routing system.
Expand All @@ -55,6 +54,7 @@ Unit testing the code base and providing code coverage is a primary goal of this
* Pre-made REST API controllers allow you to build APIs rapidly.
* 100% Unit test coverage for filters, sorters, and conditions.
* Build HTTP APIs in minutes by extending `RecordsRequestHandler` and setting the one config variable: the name of your model class.
* `RecordsRequestHandler` and `MediaRequestHandler` route response-producing actions through focused endpoint classes instead of one large handler method pile.
* Use a pre-made security trait with RecordsRequestHandler or extend it and write in your own permissions.
* Standard permissions interface allows reuse of permission traits from one model to another.

Expand Down Expand Up @@ -85,8 +85,14 @@ composer test
# MySQL suite only
composer test:mysql

# PostgreSQL suite only
composer test:pgsql

# SQLite in-memory suite only
composer test:sqlite

# Run merged coverage across MySQL, PostgreSQL, and SQLite
composer test:coverage
```

### Contributing To Divergence
Expand Down
18 changes: 18 additions & 0 deletions src/Controllers/Media/AbstractMediaEndpoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* This file is part of the Divergence package.
*
* (c) Henry Paradiz <henry.paradiz@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Divergence\Controllers\Media;

use Psr\Http\Message\ResponseInterface;

abstract class AbstractMediaEndpoint
{
abstract public function handle(...$arguments): ResponseInterface;
}
48 changes: 48 additions & 0 deletions src/Controllers/Media/Endpoints/Browse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Divergence\Controllers\Media\Endpoints;

use Divergence\Controllers\Media\AbstractMediaEndpoint;
use Divergence\Controllers\MediaRequestHandler;
use Divergence\Models\Tag;
use Psr\Http\Message\ResponseInterface;

class Browse extends AbstractMediaEndpoint
{
protected MediaRequestHandler $handler;

public function __construct(MediaRequestHandler $handler)
{
$this->handler = $handler;
}

public function handle(...$arguments): ResponseInterface
{
[$options, $conditions, $responseID, $responseData] = array_pad($arguments, 4, null);
$conditions ??= [];
$responseData ??= [];

if (!empty($_REQUEST['tag'])) {
if (!$Tag = Tag::getByHandle($_REQUEST['tag'])) {
return $this->handler->throwNotFoundError();
}

$conditions[] = 'ID IN (SELECT ContextID FROM tag_items WHERE TagID = '.$Tag->ID.' AND ContextClass = "Product")';
}

if (!empty($_REQUEST['ContextClass'])) {
$conditions['ContextClass'] = $_REQUEST['ContextClass'];
}

if (!empty($_REQUEST['ContextID']) && is_numeric($_REQUEST['ContextID'])) {
$conditions['ContextID'] = $_REQUEST['ContextID'];
}

return $this->parentBrowse($options, $conditions, $responseID, $responseData);
}

protected function parentBrowse($options, $conditions, $responseID, $responseData): ResponseInterface
{
return $this->handler->__call('handleBrowseRequest', [$options, $conditions, $responseID, $responseData]);
}
}
54 changes: 54 additions & 0 deletions src/Controllers/Media/Endpoints/Caption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Divergence\Controllers\Media\Endpoints;

use Divergence\Controllers\Media\AbstractMediaEndpoint;
use Divergence\Controllers\MediaRequestHandler;
use Divergence\Models\Media\Media;
use Exception;
use Psr\Http\Message\ResponseInterface;

class Caption extends AbstractMediaEndpoint
{
protected MediaRequestHandler $handler;

public function __construct(MediaRequestHandler $handler)
{
$this->handler = $handler;
}

public function handle(...$arguments): ResponseInterface
{
[$mediaId] = $arguments;

$GLOBALS['Session']->requireAccountLevel('Staff');

if (empty($mediaId) || !is_numeric($mediaId)) {
return $this->handler->throwNotFoundError();
}

try {
$Media = Media::getById($mediaId);
} catch (Exception $e) {
return $this->handler->throwUnauthorizedError();
}

if (!$Media) {
return $this->handler->throwNotFoundError();
}

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$Media->Caption = $_REQUEST['Caption'];
$Media->save();

return $this->handler->respond('mediaCaptioned', [
'success' => true,
'data' => $Media,
]);
}

return $this->handler->respond('mediaCaption', [
'data' => $Media,
]);
}
}
55 changes: 55 additions & 0 deletions src/Controllers/Media/Endpoints/Delete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Divergence\Controllers\Media\Endpoints;

use Divergence\Controllers\Media\AbstractMediaEndpoint;
use Divergence\Controllers\MediaRequestHandler;
use Psr\Http\Message\ResponseInterface;

class Delete extends AbstractMediaEndpoint
{
protected MediaRequestHandler $handler;

public function __construct(MediaRequestHandler $handler)
{
$this->handler = $handler;
}

public function handle(...$arguments): ResponseInterface
{
$GLOBALS['Session']->requireAccountLevel('Staff');

$mediaIds = [];

if ($mediaID = $this->handler->peekPath()) {
$mediaIds = [$mediaID];
} elseif (!empty($_REQUEST['mediaID'])) {
$mediaIds = [$_REQUEST['mediaID']];
} elseif (isset($_REQUEST['media']) && is_array($_REQUEST['media'])) {
$mediaIds = $_REQUEST['media'];
}

$deleted = [];

foreach ($mediaIds as $mediaID) {
if (!is_numeric($mediaID)) {
continue;
}

$Media = \Divergence\Models\Media\Media::getByID($mediaID);

if (!$Media) {
return $this->handler->throwNotFoundError();
}

if ($Media->destroy()) {
$deleted[] = $Media;
}
}

return $this->handler->respond('mediaDeleted', [
'success' => true,
'data' => $deleted,
]);
}
}
49 changes: 49 additions & 0 deletions src/Controllers/Media/Endpoints/Download.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Divergence\Controllers\Media\Endpoints;

use Divergence\Controllers\Media\AbstractMediaEndpoint;
use Divergence\Controllers\MediaRequestHandler;
use Divergence\Models\Media\Media;
use Divergence\Responders\MediaBuilder;
use Exception;
use Psr\Http\Message\ResponseInterface;

class Download extends AbstractMediaEndpoint
{
protected MediaRequestHandler $handler;

public function __construct(MediaRequestHandler $handler)
{
$this->handler = $handler;
}

public function handle(...$arguments): ResponseInterface
{
[$mediaId, $filename] = array_pad($arguments, 2, false);

if (empty($mediaId) || !is_numeric($mediaId)) {
return $this->handler->throwNotFoundError();
}

try {
$Media = Media::getById($mediaId);
} catch (Exception $e) {
return $this->handler->throwUnauthorizedError();
}

if (!$Media) {
return $this->handler->throwNotFoundError();
}

if (!$this->handler->checkReadAccess($Media)) {
return $this->handler->throwUnauthorizedError();
}

$filePath = $Media->getFilesystemPath('original');
$this->handler->responseBuilder = MediaBuilder::class;
$response = $this->handler->respondWithMedia($Media, 'original', $filePath);

return $response->withHeader('Content-Disposition', 'attachment; filename="'.($filename ? $filename : $filePath).'"');
}
}
44 changes: 44 additions & 0 deletions src/Controllers/Media/Endpoints/Info.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Divergence\Controllers\Media\Endpoints;

use Divergence\Controllers\Media\AbstractMediaEndpoint;
use Divergence\Controllers\MediaRequestHandler;
use Divergence\Models\Media\Media;
use Exception;
use Psr\Http\Message\ResponseInterface;

class Info extends AbstractMediaEndpoint
{
protected MediaRequestHandler $handler;

public function __construct(MediaRequestHandler $handler)
{
$this->handler = $handler;
}

public function handle(...$arguments): ResponseInterface
{
[$mediaID] = $arguments;

if (empty($mediaID) || !is_numeric($mediaID)) {
return $this->handler->throwNotFoundError();
}

try {
$Media = Media::getById($mediaID);
} catch (Exception $e) {
return $this->handler->throwUnauthorizedError();
}

if (!$Media) {
return $this->handler->throwNotFoundError();
}

if (!$this->handler->checkReadAccess($Media)) {
return $this->handler->throwUnauthorizedError();
}

return $this->handler->handleRecordRequest($Media);
}
}
Loading