aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorvincent-peugnet <v.peugnet@free.fr>2020-03-26 17:20:31 +0100
committervincent-peugnet <v.peugnet@free.fr>2020-03-26 17:20:31 +0100
commit01795f56b61ab4e6becd959b7a41aad1276cdfca (patch)
tree0be85f2a811cc0fb7b44985ca3bc09015f706ebd /app
parentadb3d38893acb75b838fc6ccce5edb4321d08bf9 (diff)
parentd17713051ca2fef29de8025fe876d417838cea7f (diff)
downloadwcms-01795f56b61ab4e6becd959b7a41aad1276cdfca.tar.gz
wcms-01795f56b61ab4e6becd959b7a41aad1276cdfca.zip
Merge branch 'implement-cytoscape.js'
Diffstat (limited to 'app')
-rw-r--r--app/class/Config.php13
-rw-r--r--app/class/Controllerhome.php74
-rw-r--r--app/class/Model.php11
-rw-r--r--app/class/Modelhome.php218
-rw-r--r--app/class/Modelpage.php26
-rw-r--r--app/class/Modelrender.php7
-rw-r--r--app/fn/fn.php26
-rw-r--r--app/view/templates/home.php57
-rw-r--r--app/view/templates/homeopt.php2
9 files changed, 366 insertions, 68 deletions
diff --git a/app/class/Config.php b/app/class/Config.php
index fa9082f..2cfdcd3 100644
--- a/app/class/Config.php
+++ b/app/class/Config.php
@@ -3,6 +3,8 @@
namespace Wcms;
+use Http\Client\Common\Plugin\RetryPlugin;
+
abstract class Config
{
protected static $pagetable = 'mystore';
@@ -129,9 +131,16 @@ abstract class Config
return self::$fontsize;
}
- public static function basepath()
+ /**
+ * @param bool $trailingslash If not empty basepath, add a trailing slash after the basepath
+ */
+ public static function basepath(bool $trailingslash = false) : string
{
- return self::$basepath;
+ if($trailingslash && !empty(self::$basepath)) {
+ return self::$basepath . '/';
+ } else {
+ return self::$basepath;
+ }
}
public static function route404()
diff --git a/app/class/Controllerhome.php b/app/class/Controllerhome.php
index 5a9110a..71f110e 100644
--- a/app/class/Controllerhome.php
+++ b/app/class/Controllerhome.php
@@ -27,23 +27,15 @@ class Controllerhome extends Controllerpage
} else {
- $table = $this->modelhome->getlister();
- $this->opt = $this->modelhome->optinit($table);
+ $pagelist = $this->modelhome->getlister();
+ $this->opt = $this->modelhome->optinit($pagelist);
$vars['colors'] = new Colors($this->opt->taglist());
- if (!isset($_GET['search'])) {
- $searchopt = ['title' => 1, 'description' => 1, 'content' => 1, 'other' => 0, 'casesensitive' => 0];
- } else {
- $searchopt['title'] = $_GET['title'] ?? 0;
- $searchopt['description'] = $_GET['description'] ?? 0;
- $searchopt['content'] = $_GET['content'] ?? 0;
- $searchopt['other'] = $_GET['other'] ?? 0;
- $searchopt['casesensitive'] = $_GET['case'] ?? 0;
- }
- $regex = $_GET['search'] ?? '';
+ $deepsearch = $this->deepsearch();
+
+ $vars['pagelistopt'] = $this->modelhome->pagetable($pagelist, $this->opt, $deepsearch['regex'], $deepsearch['searchopt']);
- $vars['table2'] = $this->modelhome->table2($table, $this->opt, $regex , $searchopt);
$vars['columns'] = $this->modelhome->setcolumns($this->user->columns());
@@ -52,22 +44,58 @@ class Controllerhome extends Controllerpage
$vars['editorlist'] = $this->usermanager->getlisterbylevel(2, '>=');
$vars['user'] = $this->user;
$vars['opt'] = $this->opt;
- $vars['deepsearch'] = $regex;
- $vars['searchopt'] = $searchopt;
+ $vars['deepsearch'] = $deepsearch['regex'];
+ $vars['searchopt'] = $deepsearch['searchopt'];
+ $vars['display'] = $_GET['display'] ?? 'list';
+
+ if($vars['display'] === 'map') {
+ $vars['layout'] = $_GET['layout'] ?? 'cose-bilkent';
+ $vars['showorphans'] = boolval($_GET['showorphans'] ?? false);
+ $vars['showredirection'] = boolval($_GET['showredirection'] ?? false);
+ $datas = $this->modelhome->cytodata($vars['pagelistopt'], $vars['layout'], $vars['showorphans'], $vars['showredirection']);
+ $vars['json'] = json_encode($datas, JSON_PRETTY_PRINT);
+ }
- $vars['footer'] = ['version' => getversion(), 'total' => count($table), 'database' => Config::pagetable()];
+ $vars['footer'] = ['version' => getversion(), 'total' => count($pagelist), 'database' => Config::pagetable()];
- if (isset($_POST['query']) && $this->user->iseditor()) {
- $datas = array_merge($_POST, $_SESSION['opt']);
- $this->optlist = $this->modelhome->Optlistinit($table);
- $this->optlist->hydrate($datas);
- $vars['optlist'] = $this->optlist;
- }
+ $this->listquery($pagelist);
+
+ $vars['optlist'] = $this->optlist ?? null;
$this->showtemplate('home', $vars);
}
}
+ /**
+ * Look for GET deepsearch datas and transform it an array
+ *
+ * @return array containing `string $regex` and `array $searchopt`
+ */
+ public function deepsearch() : array
+ {
+ if (!isset($_GET['search'])) {
+ $searchopt = ['title' => 1, 'description' => 1, 'content' => 1, 'other' => 0, 'casesensitive' => 0];
+ } else {
+ $searchopt['title'] = $_GET['title'] ?? 0;
+ $searchopt['description'] = $_GET['description'] ?? 0;
+ $searchopt['content'] = $_GET['content'] ?? 0;
+ $searchopt['other'] = $_GET['other'] ?? 0;
+ $searchopt['casesensitive'] = $_GET['case'] ?? 0;
+ }
+ $regex = $_GET['search'] ?? '';
+ return ['regex' => $regex, 'searchopt' => $searchopt];
+ }
+
+ public function listquery(array $pagelist)
+ {
+ if (isset($_POST['query']) && $this->user->iseditor()) {
+ $datas = array_merge($_POST, $_SESSION['opt']);
+ $this->optlist = $this->modelhome->Optlistinit($pagelist);
+ $this->optlist->hydrate($datas);
+ $vars['optlist'] = $this->optlist;
+ }
+ }
+
public function columns()
{
if (isset($_POST['columns']) && $this->user->iseditor()) {
@@ -109,7 +137,7 @@ class Controllerhome extends Controllerpage
$this->routedirect('home');
}
}
-
+
/**
* Render every pages in the database
*/
diff --git a/app/class/Model.php b/app/class/Model.php
index 5847f74..9e66af4 100644
--- a/app/class/Model.php
+++ b/app/class/Model.php
@@ -20,6 +20,17 @@ abstract class Model
const GLOBAL_DIR = 'assets'. DIRECTORY_SEPARATOR . 'global' . DIRECTORY_SEPARATOR;
const DATABASE_DIR = '.' . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR;
const PAGES_DIR = self::DATABASE_DIR . 'pages' . DIRECTORY_SEPARATOR;
+
+ const MAP_LAYOUTS = [
+ 'cose' => 'cose',
+ 'cose-bilkent' => 'cose-bilkent',
+ 'circle' => 'circle',
+ 'breadthfirst' => 'breadthfirst',
+ 'concentric' => 'concentric',
+ 'grid' => 'grid',
+ 'random' => 'random',
+ ];
+
const MEDIA_EXT = [
'jpg' => 'image',
diff --git a/app/class/Modelhome.php b/app/class/Modelhome.php
index 6352282..949c4f7 100644
--- a/app/class/Modelhome.php
+++ b/app/class/Modelhome.php
@@ -39,64 +39,87 @@ class Modelhome extends Modelpage
}
+ /**
+ * @param array $pagelist of Pages objects as `id => Page`
+ * @param Opt $opt
+ *
+ * @param string $regex Regex to match.
+ * @param array $options Option search, could be `content` `title` `description`.
+ *
+ * @return array associative array of `Page` objects *
+ */
+ public function pagetable(array $pagelist, Opt $opt, $regex = '', $searchopt = []) : array
+ {
+ $pagelist = $this->filter($pagelist, $opt);
+ if(!empty($regex)) {
+ $pagelist = $this->deepsearch($pagelist, $regex , $searchopt);
+ }
+ $pagelist = $this->sort($pagelist, $opt);
+
+ return $pagelist;
+ }
+
/**
- * @param array $table
+ * Filter the pages list acording to the options and invert
+ *
+ * @param array $pagelist of `Page` objects
* @param Opt $opt
- * @param string $regex
+ *
+ * @return array of `string` pages id
*/
- public function table2(array $table, Opt $opt, string $regex = "", array $searchopt = [])
- {
+ public function filter(array $pagelist, Opt $opt) : array
+ {
- $filtertagfilter = $this->filtertagfilter($table, $opt->tagfilter(), $opt->tagcompare());
- $filterauthorfilter = $this->filterauthorfilter($table, $opt->authorfilter(), $opt->authorcompare());
- $filtersecure = $this->filtersecure($table, $opt->secure());
- $filterlinkto = $this->filterlinkto($table, $opt->linkto());
+ $filtertagfilter = $this->filtertagfilter($pagelist, $opt->tagfilter(), $opt->tagcompare());
+ $filterauthorfilter = $this->filterauthorfilter($pagelist, $opt->authorfilter(), $opt->authorcompare());
+ $filtersecure = $this->filtersecure($pagelist, $opt->secure());
+ $filterlinkto = $this->filterlinkto($pagelist, $opt->linkto());
$filter = array_intersect($filtertagfilter, $filtersecure, $filterauthorfilter, $filterlinkto);
- $table2 = [];
- $table2invert = [];
- foreach ($table as $page) {
- if (in_array($page->id(), $filter)) {
- $table2[] = $page;
- } else {
- $table2invert[] = $page;
- }
-
+ if($opt->invert()) {
+ $idlist = array_keys($pagelist);
+ $filter = array_diff($idlist, $filter);
}
- if (!empty($opt->invert())) {
- $table2 = $table2invert;
- }
+ return array_intersect_key($pagelist, array_flip($filter));
+ }
- if(!empty($regex)) {
- $table2 = $this->deepsearch($regex, $searchopt, $table2);
- }
- $this->pagelistsort($table2, $opt->sortby(), $opt->order());
+
+ /**
+ * Sort and limit an array of Pages
+ *
+ * @param array $pagelist of `Page` objects
+ * @param Opt $opt
+ *
+ * @return array associative array of `Page` objects
+ */
+ public function sort(array $pagelist, Opt $opt) : array
+ {
+ $this->pagelistsort($pagelist, $opt->sortby(), $opt->order());
if($opt->limit() !== 0) {
- $table2 = array_slice($table2, 0, $opt->limit());
+ $pagelist = array_slice($pagelist, 0, $opt->limit());
}
-
- return $table2;
+ return $pagelist;
}
/**
* Search for regex and count occurences
*
+ * @param array $page list Array of Pages.
* @param string $regex Regex to match.
* @param array $options Option search, could be `content` `title` `description`.
- * @param array $page list Array of Pages.
*
- * @return array associative array of Pages
+ * @return array associative array of `Page` objects
*/
- public function deepsearch(string $regex, array $options, array $pagelist) : array
+ public function deepsearch(array $pagelist, string $regex, array $options) : array
{
if($options['casesensitive']) {
$case = '';
@@ -126,11 +149,142 @@ class Modelhome extends Modelpage
$count += preg_match($regex, $page->description());
}
if ($count !== 0) {
- $pageselected[] = $page;
+ $pageselected[$page->id()] = $page;
}
}
return $pageselected;
- }
+ }
+
+
+
+ /**
+ * Transform list of page into list of nodes and edges
+ *
+ * @param array $pagelist associative array of pages as `id => Page`
+ * @param string $layout
+ * @param bool $showorphans if `false`, remove orphans pages
+ * @param bool $showredirection if `true`, add redirections
+ *
+ * @return array
+ */
+ public function cytodata(array $pagelist, string $layout = 'random', bool $showorphans = false, bool $showredirection = false) : array
+ {
+ $datas['elements'] = $this->mapdata($pagelist, $showorphans, $showredirection);
+
+ $datas['layout'] = [
+ 'name' => $layout,
+ 'quality' => 'proof',
+ 'fit' => true,
+ 'randomize' => true,
+ 'nodeDimensionsIncludeLabels' => true,
+ 'tile' => false,
+ 'edgeElasticity' => 0.45,
+ 'gravity' => 0.25,
+ 'idealEdgeLength' => 60,
+ 'numIter' => 10000
+ ];
+ $datas['style'] = [
+ [
+ 'selector' => 'node',
+ 'style' => [
+ 'label' => 'data(id)',
+ 'background-image' => 'data(favicon)',
+ 'background-fit' => 'contain',
+ 'border-width' => 3,
+ 'border-color' => '#80b97b'
+ ],
+ ],
+ [
+ 'selector' => 'node.not_published',
+ 'style' => [
+ 'shape' => 'round-hexagon',
+ 'border-color' => '#b97b7b'
+ ],
+ ],
+ [
+ 'selector' => 'node.private',
+ 'style' => [
+ 'shape' => 'round-triangle',
+ 'border-color' => '#b9b67b'
+ ],
+ ],
+ [
+ 'selector' => 'edge',
+ 'style' => [
+ 'curve-style' => 'bezier',
+ 'target-arrow-shape' => 'triangle',
+ 'arrow-scale' => 1.5
+ ],
+ ],
+ [
+ 'selector' => 'edge.redirect',
+ 'style' => [
+ 'line-style' => 'dashed',
+ 'label' => 'data(refresh)'
+ ],
+ ],
+ ];
+ return $datas;
+ }
+
+ /**
+ * Transform list of Pages into cytoscape nodes and edge datas
+ *
+ * @param array $pagelist associative array of pages as `id => Page`
+ * @param bool $showorphans if `false`, remove orphans pages
+ * @param bool $showredirection if `true`, add redirections
+ *
+ * @return array of cytoscape datas
+ */
+ public function mapdata(array $pagelist, bool $showorphans = true, bool $showredirection = false) : array
+ {
+ $idlist = array_keys($pagelist);
+
+ $edges = [];
+ $notorphans = [];
+ foreach ($pagelist as $page) {
+ foreach ($page->linkto() as $linkto) {
+ if(in_array($linkto, $idlist)) {
+ $edge['group'] = 'edges';
+ $edge['data']['id'] = $page->id() . '>' . $linkto;
+ $edge['data']['source'] = $page->id();
+ $edge['data']['target'] = $linkto;
+ $edges[] = $edge;
+ $notorphans[] = $linkto;
+ $notorphans[] = $page->id();
+ }
+ }
+ // add redirection edge
+ if($showredirection && key_exists($page->redirection(), $pagelist)) {
+ $edger['group'] = 'edges';
+ $edger['data']['id'] = $page->id() . '>' . $page->redirection();
+ $edger['data']['refresh'] = $page->refresh();
+ $edger['data']['source'] = $page->id();
+ $edger['data']['target'] = $page->redirection();
+ $edger['classes'] = 'redirect';
+ $edges[] = $edger;
+ $notorphans[] = $page->redirection();
+ $notorphans[] = $page->id();
+ }
+ }
+
+ $notorphans = array_unique($notorphans);
+
+ $nodes = [];
+ foreach ($pagelist as $id => $page) {
+ if($showorphans || (!$showorphans && in_array($id, $notorphans))) {
+ $node['group'] = 'nodes';
+ $node['data']['id'] = $page->id();
+ $node['data']['edit'] = $page->id() . DIRECTORY_SEPARATOR . 'edit';
+ $node['data']['favicon'] = Model::faviconpath() . $page->favicon();
+ $node['classes'] = [$page->secure('string')];
+ $nodes[] = $node;
+ }
+ }
+
+ return array_merge($nodes, $edges);
+
+ }
/**
diff --git a/app/class/Modelpage.php b/app/class/Modelpage.php
index 9344089..43ba117 100644
--- a/app/class/Modelpage.php
+++ b/app/class/Modelpage.php
@@ -27,7 +27,7 @@ class Modelpage extends Modeldb
/**
* Scan library for all pages as objects
*
- * @return array of Pages objects
+ * @return array of Pages objects as `id => Page`
*/
public function getlister()
{
@@ -232,7 +232,7 @@ class Modelpage extends Modeldb
public function pagelistsort(&$pagelist, $sortby, $order = 1)
{
- return usort($pagelist, $this->buildsorter($sortby, $order));
+ return uasort($pagelist, $this->buildsorter($sortby, $order));
}
@@ -241,7 +241,7 @@ class Modelpage extends Modeldb
* @param array $tagchecked list of tags
* @param string $tagcompare string, can be 'OR' or 'AND', set the tag filter method
*
- * @return array $array
+ * @return array $array of `string` page id
*/
public function filtertagfilter(array $pagelist, array $tagchecked, $tagcompare = 'OR')
@@ -267,6 +267,16 @@ class Modelpage extends Modeldb
return $filteredlist;
}
+
+
+ /**
+ * @param array $pagelist List of Page
+ * @param array $authorchecked list of authors
+ * @param string $authorcompare, can be 'OR' or 'AND', set the author filter method
+ *
+ * @return array $array of `string` page id
+ */
+
public function filterauthorfilter(array $pagelist, array $authorchecked, $authorcompare = 'OR')
{
@@ -290,7 +300,15 @@ class Modelpage extends Modeldb
return $filteredlist;
}
- public function filtersecure(array $pagelist, $secure) : array
+ /**
+ * @param array $pagelist List of Page
+ * @param int $secure secure level
+ * @param string $authorcompare, can be 'OR' or 'AND', set the author filter method
+ *
+ * @return array $array of `string` page id
+ */
+
+ public function filtersecure(array $pagelist, int $secure) : array
{
$filteredlist = [];
foreach ($pagelist as $page) {
diff --git a/app/class/Modelrender.php b/app/class/Modelrender.php
index 2338eb4..638f3e3 100644
--- a/app/class/Modelrender.php
+++ b/app/class/Modelrender.php
@@ -7,6 +7,7 @@ use Michelf\MarkdownExtra;
class Modelrender extends Modelpage
{
+ /** @var \AltoRouter */
protected $router;
/** @var Page */
protected $page;
@@ -21,7 +22,7 @@ class Modelrender extends Modelpage
const RENDER_VERBOSE = 1;
- public function __construct($router)
+ public function __construct(\AltoRouter $router)
{
parent::__construct();
@@ -640,10 +641,10 @@ class Modelrender extends Modelpage
foreach ($matches as $match) {
$optlist = $modelhome->Optlistinit($pagelist);
$optlist->parsehydrate($match['options']);
- $table2 = $modelhome->table2($pagelist, $optlist, '', []);
+ $pagetable = $modelhome->pagetable($pagelist, $optlist, '', []);
$content = '<ul class="pagelist">' . PHP_EOL ;
- foreach ($table2 as $page ) {
+ foreach ($pagetable as $page ) {
$content .= '<li>' . PHP_EOL;
$content .= '<a href="' . $this->upage($page->id()) . '">' . $page->title() . '</a>' . PHP_EOL;
if($optlist->description()) {
diff --git a/app/fn/fn.php b/app/fn/fn.php
index 6dfeb2c..60e4722 100644
--- a/app/fn/fn.php
+++ b/app/fn/fn.php
@@ -2,6 +2,8 @@
use Wcms\Medialist;
+use function Clue\StreamFilter\fun;
+
function readablesize($bytes)
{
$format = ' %d %s';
@@ -292,7 +294,29 @@ function recurse_copy($src,$dst) {
}
}
closedir($dir);
-}
+}
+
+/**
+ * Generate a list of <options> html drop down list
+ *
+ * @param array $options as `value => title`
+ * @param string|int $selected value of actualy selected option
+ *
+ * @return string HTML list of options
+ */
+function options(array $options, $selected = null) : string
+{
+ $html = '';
+ foreach ($options as $value => $title) {
+ if($value == $selected) {
+ $attribute = 'selected';
+ } else {
+ $attribute = '';
+ }
+ $html .= '<option value="' . $value . '" ' . $attribute . '>' . $title . '</option>' . PHP_EOL;
+ }
+ return $html;
+}
diff --git a/app/view/templates/home.php b/app/view/templates/home.php
index 8078d4a..4784b08 100644
--- a/app/view/templates/home.php
+++ b/app/view/templates/home.php
@@ -24,13 +24,62 @@
<main class="home">
- <?php $this->insert('homeopt', ['opt' => $opt, 'user' => $user]) ?>
+ <?php $this->insert('homeopt', ['opt' => $opt, 'user' => $user, 'display' => $display]) ?>
<section class="pages">
<div class="block">
- <h2 class="hidephone">Pages (<?= count($table2) ?>)</h2>
+ <h2 class="hidephone">Pages (<?= count($pagelistopt) ?>) <span class="right"><a href="?display=list" <?= $display === 'list' ? 'style="color: white"' : '' ?> >list</a> / <a href="?display=map" <?= $display === 'map' ? 'style="color: white"' : '' ?> >map</a></span> </h2>
+
+ <?php if($display === 'map') { ?>
+
+ <!-- ___________________ M A P _________________________ -->
+
+ <div id="deepsearchbar">
+ <form action="" method="get">
+ <input type="hidden" name="display" value="map">
+ <input type="checkbox" name="showorphans" value="1" id="showorphans" <?= $showorphans ? 'checked' : '' ?>>
+ <label for="showorphans">show orphans pages</label>
+ <input type="checkbox" name="showredirection" value="1" id="showredirection" <?= $showredirection ? 'checked' : '' ?>>
+ <label for="showredirection">show redirections</label>
+ <select name="layout" id="layout">
+ <?= options(Wcms\Model::MAP_LAYOUTS, $layout) ?>
+ </select>
+ <label for="layout">graph layout</label>
+ <input type="submit" value="update">
+ </form>
+ </div>
+
+ <div id="graph"></div>
+
+ <script>
+ var data = <?= $json ?>;
+ console.log(data);
+ </script>
+
+ <script src="<?= Wcms\Model::jspath() ?>map.bundle.js"></script>
+
+ <?php } else { ?>
+
+ <!-- ___________________ L I S T _________________________ -->
+
+ <div id="deepsearchbar" class="hidephone">
+ <form action="<?= $this->url('home') ?>" method="get">
+ <input type="text" name="search" value="<?= $deepsearch ?>" id="deepsearch" placeholder="deep search">
+ <input type="checkbox" name="title" id="deeptitle" value="1" <?= $searchopt['title'] ? 'checked' : '' ?>>
+ <label for="deeptitle">title</label>
+ <input type="checkbox" name="description" id="deepdescription" value="1" <?= $searchopt['description'] ? 'checked' : '' ?>>
+ <label for="deepdescription">description</label>
+ <input type="checkbox" name="content" id="deepcontent" value="1" <?= $searchopt['content'] ? 'checked' : '' ?>>
+ <label for="deepcontent" title="Markdown content : MAIN, HEADER, NAV, ASIDE, FOOTER">content</label>
+ <input type="checkbox" name="other" id="deepother" value="1" <?= $searchopt['other'] ? 'checked' : '' ?>>
+ <label for="deepother" title="Structure content : BODY, CSS, Javascript">other</label>
+ <input type="checkbox" name="case" id="deepcase" value="1" <?= $searchopt['casesensitive'] ? 'checked' : '' ?>>
+ <label for="deepcase" title="Case sensitive or not">case sensitive</label>
+ <input type="submit" value="search">
+ </form>
+ </div>
<div id="deepsearchbar" class="hidephone">
@@ -105,7 +154,7 @@
</tr>
</thead>
<tbody>
- <?php foreach ($table2 as $item) { ?>
+ <?php foreach ($pagelistopt as $item) { ?>
<tr>
<?php if($user->issupereditor()) { ?><td class="hidephone"><input type="checkbox" name="pagesid[]" value="<?= $item->id() ?>" id="id_<?= $item->id() ?>" form="multi"></td><?php } ?>
<?php if($columns['favicon']) { ?>
@@ -161,6 +210,8 @@
</table>
</div>
+ <?php } ?>
+
</div>
</section>
diff --git a/app/view/templates/homeopt.php b/app/view/templates/homeopt.php
index d1f70da..ed7315d 100644
--- a/app/view/templates/homeopt.php
+++ b/app/view/templates/homeopt.php
@@ -159,6 +159,8 @@
</div>
+ <input type="hidden" name="display" value="<?= $display ?>">
+
<input type="submit" name="submit" value="filter">
⬅<input type="submit" name="submit" value="reset">