Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugin marketplace - initial working state #2143

Merged
merged 1 commit into from
Dec 14, 2023
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
6 changes: 3 additions & 3 deletions .dev/.env
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ LEAN_ENABLE_MENU_TYPE = false # Enable to specifiy menu on
LEAN_SESSION_PASSWORD = '3evBlq9zdUEuzKvVJHWWx3QzsQhturBApxwcws2m' #Salting sessions. Replace with a strong password
LEAN_SESSION_EXPIRATION = 28800 # How many seconds after inactivity should we logout? 28800seconds = 8hours
LEAN_LOG_PATH = '' # Default Log Path (including filename), if not set /logs/error.log will be used
LEAN_PLUGINS = 'motivationalquotes' # Comma separated list of plugins to load
LEAN_PLUGINS = '' # Comma separated list of plugins to load

## Look & Feel, these settings are available in the UI and can be overwritten there.
LEAN_LOGO_PATH = '/dist/images/logo.svg' # Default logo path, can be changed later
LEAN_PRINT_LOGO_URL = '/dist/images/logo.jpg' # Default logo URL use for printing (must be jpg or png format)
LEAN_LOGO_PATH = '/dist/images/logo.svg' # Default logo path, can be changed later
LEAN_PRINT_LOGO_URL = '/dist/images/logo.jpg' # Default logo URL use for printing (must be jpg or png format)
LEAN_DEFAULT_THEME = 'default' # Default theme
LEAN_PRIMARY_COLOR = '#1b75bb' # Primary Theme color
LEAN_SECONDARY_COLOR = '#81B1A8' # Secondary Theme Color
Expand Down
3 changes: 2 additions & 1 deletion .dev/dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ RUN apt update && apt install -f -y libonig-dev libcurl4-openssl-dev libxml2-de
libfreetype6-dev libjpeg62-turbo-dev libpng-dev apt-utils vim curl sqlite3\
openssl
RUN pecl install xdebug
RUN docker-php-ext-install mysqli pdo_mysql mbstring exif pcntl pdo bcmath opcache ldap
RUN docker-php-ext-install mysqli pdo_mysql mbstring exif pcntl pdo bcmath opcache ldap zip

RUN docker-php-ext-enable zip
RUN docker-php-ext-configure gd --enable-gd --with-jpeg=/usr/include/ --with-freetype --with-jpeg
RUN docker-php-ext-install gd
RUN docker-php-ext-enable xdebug
Expand Down
3 changes: 2 additions & 1 deletion .phpactor.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "/Users/josephroberts/.local/share/nvim/mason/packages/phpactor/phpactor.schema.json",
"php_code_sniffer.enabled": false
"php_code_sniffer.enabled": false,
"language_server_phpstan.enabled": true
}
8 changes: 4 additions & 4 deletions app/Command/MigrateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ protected function configure(): void
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
define('BASE_URL', "");
define('CURRENT_URL', "");
! defined('BASE_URL') && define('BASE_URL', "");
! defined('CURRENT_URL') && define('CURRENT_URL', "");

$install = app()->make(Install::class);
$io = new SymfonyStyle($input, $output);
Expand Down Expand Up @@ -99,8 +99,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$io->text("Successfully Installed DB");
}
$success = $install->updateDB();
if (!$success) {
throw new Exception("Migration Failed; Please Check Logs");
if ($success !== true) {
throw new Exception("Migration Failed; See below" . PHP_EOL . implode(PHP_EOL, $success));
}
} catch (Exception $ex) {
$io->error($ex);
Expand Down
12 changes: 7 additions & 5 deletions app/Core/HtmxController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*
* @package leantime
* @subpackage core
* @method string|null run() The fallback method to be initialized.
*/
abstract class HtmxController
{
Expand All @@ -21,19 +22,20 @@ abstract class HtmxController
protected IncomingRequest $incomingRequest;
protected Template $tpl;
protected Response $response;
protected static string $view;

/**
* constructor - initialize private variables
*
* @access public
*
* @param IncomingRequest $incomingRequest The request to be initialized.
* @param template $tpl The template to be initialized.
* @param Template $tpl The template to be initialized.
* @throws BindingResolutionException
*/
public function __construct(
IncomingRequest $incomingRequest,
template $tpl
Template $tpl
) {
self::dispatch_event('begin');

Expand Down Expand Up @@ -69,11 +71,11 @@ private function executeActions(): void

$action = Str::camel($this->incomingRequest->query->get('id', 'run'));

if (! method_exists($this, $action)) {
throw new Error("Method $action doesn't exist.");
if (! method_exists($this, $action) && ! method_exists($this, 'run')) {
throw new Error("Method $action doesn't exist and no fallback method.");
}

$fragment = $this->$action();
$fragment = method_exists($this, $action) ? $this->$action() : $this->run();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll also need to check that the run method exists. Had this issue a few times.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do, right beforehand.


$this->response = $this->tpl->displayFragment($this::$view, $fragment ?? '');
}
Expand Down
13 changes: 12 additions & 1 deletion app/Core/HttpKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Pipeline\Pipeline;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;

class HttpKernel implements HttpKernelContract
{
Expand Down Expand Up @@ -46,7 +48,16 @@ public function handle($request)
return $e->getResponse();
} catch (\Throwable $e) {
if (! app()->make(Environment::class)->debug) {
return new RedirectResponse(BASE_URL . "/errors/error500", 500);
return new RedirectResponse(BASE_URL . "/errors/error500", 201);
}

if ($request instanceof HtmxRequest) {
/** @todo Replace with a proper error template for htmx requests */
return new Response(sprintf(
'<dialog style="%s" open>%s</dialog>',
'width: 90vw; height: 90vh; z-index: 9999999; position: fixed; top: 5vh; left: 5vh; overflow: scroll',
(new HtmlErrorRenderer(true))->render($e)->getAsString(),
));
}

throw $e;
Expand Down
82 changes: 43 additions & 39 deletions app/Core/Language.php
Original file line number Diff line number Diff line change
Expand Up @@ -359,51 +359,55 @@ public function getBrowserLanguage(): string
/**
* __ - returns a language specific string
*
* @access public
* @param string $index
* @param bool $convertValue If true then if a value has a conversion (i.e.: PHP -> JavaScript) then do it, otherwise return PHP value
* @return string
* @param string $index
* @param bool $convertValue If true then if a value has a conversion (i.e.: PHP -> JavaScript) then do it, otherwise return PHP value
* @param string $default If the index is not found, return this value
* @return string
*/
public function __(string $index, bool $convertValue = false): string
public function __(string $index, bool $convertValue = false, $default = ''): string
{
if (isset($this->ini_array[$index]) === true) {
$index = trim($index);

// @TODO: move the date/time format logic into here and have Api/Controllers/I18n call this for each type?
$dateTimeIniSettings = [
'language.dateformat',
'language.jsdateformat',
'language.timeformat',
'language.jstimeformat',
'language.momentJSDate',
];

$dateTimeFormat = $this->getCustomDateTimeFormat();

if (in_array($index, $dateTimeIniSettings) && $convertValue) {
$isMoment = stristr($index, 'momentjs') !== false;
$isJs = stristr($index, '.js') !== false;
$isDate = stristr($index, 'date') !== false;

if ($isJs || $isMoment) {
return $this->convertDateFormatToJS($isDate ? $dateTimeFormat['date'] : $dateTimeFormat['time'], $isMoment);
} else if ($isDate) {
return $this->convertDateFormatToJS($dateTimeFormat['date'], false);
}
} else if ($index === 'language.dateformat') {
return $dateTimeFormat['date'];
} else if ($index === 'language.timeformat') {
return $dateTimeFormat['time'];
if (! isset($this->ini_array[$index])) {
if (! empty($default)) {
return $default;
}

return (string) $this->ini_array[$index];
} else {
if ($this->alert === true) {
return '<span style="color: red; font-weight:bold;">' . $index . '</span>';
} else {
return $index;
if ($this->alert) {
return sprintf('<span style="color: red; font-weight:bold;">%s</span>', $index);
}

return $index;
}

$index = trim($index);

// @TODO: move the date/time format logic into here and have Api/Controllers/I18n call this for each type?
$dateTimeIniSettings = [
'language.dateformat',
'language.jsdateformat',
'language.timeformat',
'language.jstimeformat',
'language.momentJSDate',
];

$dateTimeFormat = $this->getCustomDateTimeFormat();

if (in_array($index, $dateTimeIniSettings) && $convertValue) {
$isMoment = stristr($index, 'momentjs') !== false;
$isJs = stristr($index, '.js') !== false;
$isDate = stristr($index, 'date') !== false;

if ($isJs || $isMoment) {
return $this->convertDateFormatToJS($isDate ? $dateTimeFormat['date'] : $dateTimeFormat['time'], $isMoment);
} else if ($isDate) {
return $this->convertDateFormatToJS($dateTimeFormat['date'], false);
}
} else if ($index === 'language.dateformat') {
return $dateTimeFormat['date'];
} else if ($index === 'language.timeformat') {
return $dateTimeFormat['time'];
}

return (string) $this->ini_array[$index];
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Core/Middleware/Updated.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function handle(IncomingRequest $request, Closure $next): Response
$dbVersion = app()->make(SettingRepository::class)->getSetting('db-version');
$settingsDbVersion = app()->make(AppSettings::class)->dbVersion;

$_SESSION['isUpdated'] = $dbVersion == $settingsDbVersion;
$_SESSION['isUpdated'] ??= $dbVersion == $settingsDbVersion;

self::dispatch_event('system_update', ['dbVersion' => $dbVersion, 'settingsDbVersion' => $settingsDbVersion]);

Expand Down
10 changes: 7 additions & 3 deletions app/Domain/Plugins/Controllers/Details.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,17 @@ public function get(): Response
}

/**
* @var \Leantime\Domain\Plugins\Models\MarketplacePlugin[] $versions
* @var \Leantime\Domain\Plugins\Models\MarketplacePlugin|false $plugin
*/
$versions = $this->pluginService->getMarketplacePlugin(
$plugin = $this->pluginService->getMarketplacePlugin(
$this->incomingRequest->query->get('id'),
);

$this->tpl->assign('versions', $versions);
if (! $plugin) {
return $this->tpl->display('error.error404', 'blank');
}

$this->tpl->assign('plugin', $plugin);

return $this->tpl->display('plugins.plugindetails', 'blank');
}
Expand Down
27 changes: 18 additions & 9 deletions app/Domain/Plugins/Hxcontrollers/Details.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Leantime\Domain\Plugins\Hxcontrollers;

use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\Exceptions\HttpResponseException;
use Leantime\Core\HtmxController;
use Leantime\Domain\Plugins\Models\MarketplacePlugin;
use Leantime\Domain\Plugins\Services\Plugins as PluginService;
Expand Down Expand Up @@ -39,22 +40,30 @@ public function init(
*/
public function install(): string
{
$pluginModel = app(MarketplacePlugin::class);
$pluginProps = $this->incomingRequest->request->all()['plugin'];
collect($pluginProps)->each(fn($value, $key) => $pluginModel->{$key} = $value ?? '');
$version = $pluginProps['version'];
unset($pluginProps['version']);
$builder = build(new MarketplacePlugin);

if (! empty($pluginModel->identifier)) {
$pluginModel->identifier = Str::studly($pluginModel->identifier);
foreach ($pluginProps as $key => $value) {
$newValue = json_decode(json: $value, flags: JSON_OBJECT_AS_ARRAY);

if (json_last_error() === JSON_ERROR_NONE) {
$value = $newValue;
}

$builder->set($key, $value);
}

$this->tpl->assign('versions', [$pluginModel->version => $pluginModel]);
$pluginModel = $builder->get();

$this->tpl->assign('plugin', $pluginModel);

try {
$this->pluginService->installMarketplacePlugin($pluginModel);
$this->pluginService->installMarketplacePlugin($pluginModel, $version);
} catch (\Throwable $e) {
Frontcontroller::setResponseCode(200);
$this->tpl->assign('formError', $e->getMessage());
return 'plugin-installation';
$this->tpl->assign('formError', $e->getMessage());
return 'plugin-installation';
}

if ($this->pluginService->isPluginEnabled($pluginModel->identifier)) {
Expand Down
1 change: 1 addition & 0 deletions app/Domain/Plugins/Hxcontrollers/Marketplaceplugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function init(
*/
public function getlist(): void
{
/** @var MarketplacePlugin[] $plugins */
$plugins = $this->pluginService->getMarketplacePlugins(
$this->incomingRequest->query->get('page', 1),
$this->incomingRequest->query->get('search', ''),
Expand Down
37 changes: 29 additions & 8 deletions app/Domain/Plugins/Models/MarketplacePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ class MarketplacePlugin implements PluginDisplayStrategy
public string $excerpt;
public string $description;
public string $imageUrl;
public array|string $authors;
public string $version;
public string $vendorDisplayName;
public int $vendorId;
public string $vendorEmail;
public string $marketplaceUrl;
public ?string $price;
public ?string $startingPrice;
public ?array $pricingTiers;
public ?string $license;
public ?string $rating;
public ?int $reviewCount;
public array $reviews;
public string $marketplaceId;
public array $compatibility;

public function getCardDesc(): string
{
Expand All @@ -31,12 +36,28 @@ public function getMetadataLinks(): array
{
$links = [];

if (! empty($plugin->authors)) {
$author = is_array($plugin->authors) ? $plugin->authors[0] : $plugin->authors;
$links[] = [
if (! empty($this->vendorDisplayName) && (! empty($this->vendorId) || ! empty($this->vendorEmail))) {
$vendor = [
'prefix' => __('text.by'),
'link' => "mailto:{$author->email}",
'text' => $author->name,
'display' => $this->vendorDisplayName,
];

$vendor['link'] = ! empty($this->vendorId) ? "/plugins/marketplace?" . http_build_query(['vendor_id' => $this->vendorId]) : "mailto:{$this->vendorEmail}";

$links[] = $vendor;
}

if (! empty($this->startingPrice)) {
$links[] = [
'prefix' => __('text.starting_at', 'Starting At'),
'display' => $this->startingPrice,
];
}

if (! empty($this->rating)) {
$links[] = [
'prefix' => __('text.rating', 'Rating: '),
'display' => $this->rating,
];
}

Expand Down
Loading