Skip to content
This repository has been archived by the owner on Oct 6, 2021. It is now read-only.

Commit

Permalink
Merge pull request #130 from paragonie/issue-129
Browse files Browse the repository at this point in the history
Allow Motifs to be Configured (See #129)
  • Loading branch information
paragonie-scott committed Aug 28, 2016
2 parents efffb34 + aa28228 commit 74dbbe0
Show file tree
Hide file tree
Showing 25 changed files with 414 additions and 104 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -16,6 +16,7 @@
/src/config/keyring/*.skey
/src/config/keyring/*.keypair
/src/config/universal.json
/src/config/motifs/*.json
/src/tmp/cache/*.json
/src/tmp/installing.json
/src/tmp/last_update_check.txt
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,8 @@
* Update [Halite](https://github.com/paragonie/halite) to 2.2.0.
* Added a `WhiteList` filter, which is a strict typed alternative to
switch-case whitelisting.
* Extension developers can now make their motifs configurable by
end users.

## Version 1.2.8 - 2016-07-26

Expand Down
147 changes: 146 additions & 1 deletion src/Cabin/Bridge/Landing/Motifs.php
Expand Up @@ -3,6 +3,11 @@
namespace Airship\Cabin\Bridge\Landing;

use Airship\Cabin\Bridge\Filter\MotifsFilter;
use Airship\Engine\Gadgets;
use Airship\Engine\Security\Filter\{
GeneralFilterContainer,
InputFilterContainer
};
use Airship\Engine\Security\Util;

require_once __DIR__.'/init_gear.php';
Expand All @@ -24,6 +29,81 @@ public function airshipLand()
$this->storeLensVar('active_link', 'bridge-link-admin-ext-motifs');
}

/**
* @route motif-config/{string}
*
* @param string $motifName
*/
public function configure(string $motifName)
{
$motifs = $this->getAllMotifs();
if (!\array_key_exists($motifName, $motifs)) {
\Airship\redirect($this->airship_cabin_prefix . '/motifs');
}
if (!$this->can('update')) {
\Airship\redirect($this->airship_cabin_prefix . '/motifs');
}
$selected = $motifs[$motifName];
$path = ROOT . '/Motifs/' . $selected['supplier'] . '/' . $selected['name'];

// Should we load overload the configuration lens?
if (\file_exists($path . '/lens/config.twig')) {
Gadgets::loadCargo(
'bridge_motifs_config_overloaded',
'motif/' . $motifName . '/config.twig'
);
}
$inputFilter = null;
if (\file_exists($path . '/config_filter.php')) {
$inputFilter = $this->getConfigFilter($path . '/config_filter.php');
}
try {
$motifConfig = \Airship\loadJSON(
ROOT . '/config/motifs/' . $motifName . '.json'
);
} catch (\Throwable $ex) {
$motifConfig = [];
}

// Handle POST data
if ($inputFilter instanceof InputFilterContainer) {
$post = $this->post($inputFilter);
} else {
$post = $this->post();
if (\is_string($post['motif_config'])) {
$post['motif_config'] = \Airship\parseJSON(
$post['motif_config'],
true
);
}
}
if ($post) {
if (empty($post['motif_config'])) {
$post['motif_config'] = [];
}
if ($this->saveMotifConfig(
$motifName,
$post['motif_config']
)) {
\Airship\redirect(
$this->airship_cabin_prefix . '/motif_config/' . $motifName
);
}
}
$this->lens(
'motif_configure',
[
'cabin_name' => $motifName,
'motifs' => $motifs,
'motif_config' => $motifConfig,
'title' => \__('Configuring %s/%s', 'default',
Util::noHTML($selected['supplier']),
Util::noHTML($selected['name'])
)
]
);
}

/**
* @route motifs
*/
Expand All @@ -32,7 +112,8 @@ public function index()
$this->lens(
'motifs',
[
'cabins' => $this->getCabinNames()
'cabins' => $this->getCabinNames(),
'motifs' => $this->getAllMotifs(true)
]
);
}
Expand Down Expand Up @@ -75,6 +156,70 @@ public function manage(string $cabinName = '')
);
}

/**
* @param bool $deepSort
* @return array
*/
protected function getAllMotifs(bool $deepSort = false): array
{
$allMotifs = [];
$cabins = $this->getCabinNamespaces();
foreach ($cabins as $cabinName) {
$motifs = \Airship\loadJSON(ROOT . '/Cabin/' . $cabinName . '/config/motifs.json');
foreach ($motifs as $motif => $config) {
if (!\array_key_exists($motif, $allMotifs)) {
$allMotifs[$motif] = $config;
$allMotifs[$motif]['link'] = $motif;
}
}
}
if ($deepSort) {
\usort(
$allMotifs,
function (array $a, array $b): int {
if ($a['supplier'] === $b['supplier']) {
return (int)($a['name'] <=> $b['name']);
}
return (int)($a['supplier'] <=> $b['supplier']);
}
);
} else {
\ksort($allMotifs);
}
return $allMotifs;
}

/**
* Get an input filter container
*
* @param string $path
* @return InputFilterContainer
*/
protected function getConfigFilter(string $path): InputFilterContainer
{
include $path;

if (!isset($motifInputFilter)) {
return new GeneralFilterContainer();
}
return $motifInputFilter;
}

/**
* @param string $motifName
* @param array $config
* @return bool
*/
protected function saveMotifConfig(
string $motifName,
array $config = []
): bool {
return \Airship\saveJSON(
ROOT . '/config/motifs/' . $motifName . '.json',
$config
);
}

/**
* @param array $motifs
* @param array $post
Expand Down
16 changes: 16 additions & 0 deletions src/Cabin/Bridge/Lens/cargo/bridge_motifs_config.twig
@@ -0,0 +1,16 @@
<h2>{{ __(title) }}</h2>

<form method="post">{{ form_token() }}
{% include cargo("bridge_motifs_config_overloaded") ignore missing %}

<div class="text-right form-button-group no-border">
<a href="{{ cabin_url() }}motifs" class="pure-button pure-button-tertiary">
{{ __("Cancel") }}
</a>

<button type="submit" class="pure-button pure-button-primary">
<i class="fa fa-paint-brush"></i>
{{ __("Save Motif Configuration") }}
</button>
</div>
</form>
@@ -0,0 +1,6 @@
{# For motifs that fail to override this template. #}
<textarea name="motif_config">{{
motif_config|json_encode(
constant("JSON_PRETTY_PRINT")
)
}}</textarea>
18 changes: 16 additions & 2 deletions src/Cabin/Bridge/Lens/cargo/bridge_motifs_home.twig
Expand Up @@ -7,12 +7,26 @@
</p>

<h3>{{ __("Manage Installed Motifs") }}</h3>
<ul>
<ul id="motif-cabins-list">
{% for cabin in cabins %}
<li>
<a href="{{ cabin_url() }}motifs/{{ cabin|e('url') }}">
{{ __("Motifs for <b>%s</b>", "default", cabin|e('html')) }}
</a>
</li>
{% endfor %}
</ul>
</ul>

<h3 id="configure-motifs">{{ __("Configure Motifs") }}</h3>
<ul id="motif-configure-list">
{% for motif in motifs %}
<li>
<a href="{{ cabin_url() }}motif_config/{{ motif.link|e('url') }}">
{{ __("%s/%s", "default",
motif.supplier|e('html'),
motif.name|e('html')
) }}
</a>
</li>
{% endfor %}
</ul>
5 changes: 5 additions & 0 deletions src/Cabin/Bridge/Lens/motif_configure.twig
@@ -0,0 +1,5 @@
{% extends base_template() %}

{% block main %}
{% include cargo("bridge_motifs_config") %}
{% endblock %}
2 changes: 2 additions & 0 deletions src/Cabin/Bridge/manifest.json
Expand Up @@ -190,6 +190,8 @@

"motifs/{string}":
["Motifs", "manage"],
"motif_config/{string}":
["Motifs", "configure"],
"motifs":
["Motifs"],

Expand Down
4 changes: 2 additions & 2 deletions src/Engine/Gadgets.php
Expand Up @@ -56,7 +56,7 @@ public static function loadCargo(string $name, string $source)
if (!\array_key_exists($name, $cargo)) {
$cargo[$name] = [];
}

\array_unshift($cargo[$name], $source);
$cargo[$name] = \array_values($cargo[$name]);

Expand All @@ -79,8 +79,8 @@ public static function unloadNextCargo(string $name)
$iterate = $state->cargoIterator;

if (isset($iterate[$name])) {
$cargo = self::unloadCargo($name, $iterate[$name]);
++$iterate[$name];
$cargo = self::unloadCargo($name, $iterate[$name]);
$state->cargoIterator = $iterate;
return $cargo;
}
Expand Down
55 changes: 53 additions & 2 deletions src/Engine/Lens.php
Expand Up @@ -275,6 +275,56 @@ public function listFilters(): array
return \array_keys($filters);
}

/**
* Load the cargo for these motifs
*
* @param string $name
* @return self
*/
public function loadMotifCargo(string $name): self
{
$state = State::instance();
if (
isset($state->motifs[$name])
&&
isset($state->motifs[$name]['config']['cargo'])
) {
foreach ($state->motifs[$name]['config']['cargo'] as $key => $subPath) {
Gadgets::loadCargo(
$key,
'motif/' . $name . '/cargo/' . $subPath
);
}
}
return $this;
}


/**
* Load the config for this motif
*
* @param string $name
* @return self
*/
public function loadMotifConfig(string $name): self
{
$state = State::instance();
if (isset($state->motifs[$name])) {
try {
if (\file_exists(ROOT . '/config/motifs/' . $name . '.json')) {
$state->motif_config = \Airship\loadJSON(
ROOT . '/config/motifs/' . $name . '.json'
);
} else {
$state->motif_config = [];
}
} catch (\Throwable $ex) {
$state->motif_config = [];
}
}
return $this;
}

/**
* Set the active motif
*
Expand Down Expand Up @@ -308,7 +358,7 @@ public function resetBaseTemplate(): self
* Override the base template
*
* @param string $name
* @return Lens
* @return self
*/
public function setBaseTemplate(string $name): self
{
Expand All @@ -320,9 +370,10 @@ public function setBaseTemplate(string $name): self
) {
$state->base_template = 'motif/' .
$name .
'/lens/' .
'/' .
$state->motifs[$name]['config']['base_template'] .
'.twig';
\var_dump($state->base_template);
}
return $this;
}
Expand Down
7 changes: 1 addition & 6 deletions src/Engine/Security/Filter/BoolFilter.php
Expand Up @@ -48,11 +48,6 @@ public function setType(string $typeIndicator): FilterInterface
*/
public function setDefault($value): FilterInterface
{
if ($value !== false) {
throw new UnsupportedOperation(
'Default must always be set to FALSE.'
);
}
return parent::setDefault(false);
return parent::setDefault($value);
}
}
17 changes: 17 additions & 0 deletions src/Motifs/paragonie/airship-classic/config_filter.php
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);

use Airship\Engine\Security\Filter\{
BoolFilter,
GeneralFilterContainer
};

/**
* @return $motifInputFilter
*/
$motifInputFilter = (new GeneralFilterContainer())
->addFilter(
'bridge.gradient',
new BoolFilter()
)
;

0 comments on commit 74dbbe0

Please sign in to comment.