Comment: | List of pages and categories |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | dev |
Files: | files | file ages | folders |
SHA1: |
76f8f4650ad551f8b43b193ae07cc24f |
User & Date: | bohwaz on 2020-12-13 18:21:59 |
Other Links: | branch diff | manifest | tags |
2020-12-14
| ||
02:21 | More work on the rich text editor, but Trix sucks a lot :( check-in: e1d70bedb9 user: bohwaz tags: dev | |
2020-12-13
| ||
18:21 | List of pages and categories check-in: 76f8f4650a user: bohwaz tags: dev | |
16:11 | Reflect changes in config check-in: c83f057f54 user: bohwaz tags: dev | |
Modified src/include/lib/Garradin/Entities/Files/File.php from [f144751b28] to [06d115add3].
︙ | ︙ | |||
37 38 39 40 41 42 43 | protected $content_id; protected $_types = [ 'id' => 'int', 'folder_id' => '?int', 'name' => 'string', 'type' => '?string', | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | protected $content_id; protected $_types = [ 'id' => 'int', 'folder_id' => '?int', 'name' => 'string', 'type' => '?string', 'public' => 'int', 'image' => 'int', 'size' => 'int', 'hash' => 'string', 'storage' => '?string', 'storage_path' => '?string', 'created' => 'DateTime', 'author_id' => '?int', |
︙ | ︙ | |||
104 105 106 107 108 109 110 | $db->preparedQuery('INSERT OR REPLACE INTO files_search (id, content) VALUES (?, ?);', $this->id(), $content); } return $return; } | | < < < < < < | | < | | < < < | | | > > > | > | | > | > | > > > | | > < | | | < > | > | > > < | | | 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | $db->preparedQuery('INSERT OR REPLACE INTO files_search (id, content) VALUES (?, ?);', $this->id(), $content); } return $return; } public function store(string $source_path = null, $source_content = null): self { if ($source_path && !$source_content) { $this->hash = sha1_file($source_path); $this->size = filesize($source_path); } else { $this->hash = sha1($source_content); $this->size = strlen($source_content); } // Check that it's a real image if ($this->image) { try { if ($source_path && !$source_content) { $i = new Image($source_path); } else { $i = Image::createFromBlob($source_content); } // Recompress PNG files from base64, assuming they are coming // from JS canvas which doesn't know how to gzip (d'oh!) if ($i->format() == 'png' && null !== $source_content) { $source_content = $i->output('png', true); $this->hash = sha1($source_content); $this->size = strlen($source_content); } unset($i); } catch (\RuntimeException $e) { if (strstr($e->getMessage(), 'No suitable image library found')) { throw new \RuntimeException('Le serveur n\'a aucune bibliothèque de gestion d\'image installée, et ne peut donc pas accepter les images. Installez Imagick ou GD.'); } throw new UserException('Fichier image invalide'); } } if (!Files::callStorage('store', $file, $source_path, $source_content)) { throw new UserException('Le fichier n\'a pas pu être enregistré.'); } return $this; } static protected function create(string $name, string $source_path = null, $source_content = null): self { assert($source_path || $source_content); $finfo = \finfo_open(\FILEINFO_MIME_TYPE); $file = new self; $file->name = $name; if ($source_path && !$source_content) { $file->type = finfo_file($finfo, $source_path); } else { $file->type = finfo_buffer($finfo, $source_content); } $file->image = preg_match('/^image\/(?:png|jpe?g|gif)$/', $file->type); $db = DB::getInstance(); $db->begin(); $file->store($source_path, $source_content); $file->save(); $db->commit(); return $file; } /** * Upload de fichier à partir d'une chaîne en base64 * @param string $name * @param string $content * @return File */ static public function storeFromBase64(string $name, string $encoded_content): self { $content = base64_decode($encoded_content); return self::store($name, null, $content); } /** * Upload du fichier par POST * @param array $file Caractéristiques du fichier envoyé * @return File */ |
︙ | ︙ |
Modified src/include/lib/Garradin/Entities/Web/Page.php from [ab2d08746c] to [652702f2a8].
1 2 3 4 5 6 7 | <?php namespace Garradin\Entities\Web; use Garradin\Entity; use Garradin\UserException; | > > | > > > | | > > < > > > > > > > > > > > > > > > > > > > | > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | <?php namespace Garradin\Entities\Web; use Garradin\Entity; use Garradin\UserException; use const Garradin\WWW_URL; use KD2\DB\EntityManager as EM; class Page extends Entity { const TABLE = 'web_pages'; protected $id; protected $parent_id; protected $type; protected $status; protected $title; protected $uri; protected $modified; protected $_types = [ 'id' => 'int', 'parent_id' => '?int', 'type' => 'int', 'status' => 'int', 'uri' => 'string', 'title' => 'string', 'modified' => 'DateTime', ]; protected $_file; const STATUS_ONLINE = 1; const STATUS_DRAFT = 0; const TYPE_CATEGORY = 1; const TYPE_PAGE = 2; public function url(): string { $url = WWW_URL . $this->uri; if ($this->type == self::TYPE_CATEGORY) { $url .= '/'; } return $url; } public function file(): File { if (null === $this->_file) { $this->_file = EM::findOneById(File::class, $this->id); } return $this->_file; } public function setFile(File $file) { $this->_file = $file; } public function save(): bool { $this->modified = new \DateTime; $file = $this->file(); $file->save(); $this->id($file->id()); return parent::save(); } public function selfCheck(): void { $this->assert($this->type === self::TYPE_CATEGORY || $this->type === self::TYPE_PAGE, 'Unknown page type'); $this->assert($this->status === self::STATUS_DRAFT || $this->status === self::STATUS_ONLINE, 'Unknown page status'); $this->assert(trim($this->title) !== '', 'Le titre ne peut rester vide'); $this->assert(trim($this->uri) !== '', 'L\'URI ne peut rester vide'); $this->assert(!$this->_file, 'Fichier manquant'); } public function importForm(array $source = null) { if (null === $source) { $source = $_POST; } $file = $this->file(); if (isset($source['content']) && sha1($source['content']) != $file->hash) { $file->store(null, $content); } return $this->import($source); } static public function create(int $type, string $title, string $content, int $status = self::STATUS_ONLINE): Page { static $types = [self::TYPE_PAGE, self::TYPE_CATEGORY]; if (!in_array($type, $types)) { throw new \InvalidArgumentException('Unknown type'); } $page = new self; $page->type = $type; $page->title = $title; $page->uri = Utils::transformTitleToURI($title); $page->status = $status; $file = new File; $file->type = 'text/html'; $file->image = 0; if ($type == self::TYPE_PAGE) { $file->name = $page->uri . '.html'; } else { $file->name = 'index.html'; } $file->store(null, $content); $page->save(); return $page; } } |
Modified src/include/lib/Garradin/Files/Render/Skriv.php from [048c543789] to [0dba465910].
︙ | ︙ | |||
41 42 43 44 45 46 47 | $str = preg_replace_callback('!<a href="([^/.:@]+)">!i', function ($matches) use ($prefix) { return '<a href="' . $prefix . self::transformTitleToURI($matches[1]) . '">'; }, $str); return $str; } | < < < < < < < < < < < | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | $str = preg_replace_callback('!<a href="([^/.:@]+)">!i', function ($matches) use ($prefix) { return '<a href="' . $prefix . self::transformTitleToURI($matches[1]) . '">'; }, $str); return $str; } /** * Callback utilisé pour l'extension <<fichier>> dans le wiki-texte * @param array $args Arguments passés à l'extension * @param string $content Contenu éventuel (en mode bloc) * @param object $skriv Objet SkrivLite */ static public function SkrivFichier($args, $content, $skriv) |
︙ | ︙ |
Modified src/include/lib/Garradin/Template.php from [f94b6d765a] to [b8eafe3cb8].
︙ | ︙ | |||
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | $shape = $params['shape']; $label = $params['label']; // href can be prefixed with '!' to make the URL relative to ADMIN_URL if (substr($href, 0, 1) == '!') { $href = ADMIN_URL . substr($params['href'], 1); } unset($params['href'], $params['shape'], $params['label']); array_walk($params, function (&$v, $k) { $v = sprintf('%s="%s"', $k, $this->escape($v)); }); $params = implode(' ', $params); | > > > > > > | | 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | $shape = $params['shape']; $label = $params['label']; // href can be prefixed with '!' to make the URL relative to ADMIN_URL if (substr($href, 0, 1) == '!') { $href = ADMIN_URL . substr($params['href'], 1); } if (!isset($params['class'])) { $params['class'] = ''; } $params['class'] .= ' icn-btn'; unset($params['href'], $params['shape'], $params['label']); array_walk($params, function (&$v, $k) { $v = sprintf('%s="%s"', $k, $this->escape($v)); }); $params = implode(' ', $params); return sprintf('<a data-icon="%s" href="%s" %s>%s</a>', Utils::iconUnicode($shape), $this->escape($href), $params, $this->escape($label)); } protected function formInput(array $params) { static $params_list = ['value', 'default', 'type', 'help', 'label', 'name', 'options', 'source']; // Extract params and keep attributes separated |
︙ | ︙ |
Modified src/include/lib/Garradin/Utils.php from [f7c395e1cb] to [936bc987d3].
︙ | ︙ | |||
837 838 839 840 841 842 843 | } $config->set('last_version_check', json_encode($last)); $config->save(); return $last->version; } | | > > > > > > > > > > > | 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 | } $config->set('last_version_check', json_encode($last)); $config->save(); return $last->version; } static public function transformTitleToURI($str) { $str = Utils::transliterateToAscii($str); $str = preg_replace('![^\w\d_-]!i', '-', $str); $str = preg_replace('!-{2,}!', '-', $str); $str = trim($str, '-'); return $str; } } |
Modified src/include/lib/Garradin/Web.php from [eebca41282] to [8d933d1e83].
1 2 3 4 5 6 7 8 9 10 11 | <?php namespace Garradin; class Web { static public function search(string $search, bool $online_only = true): array { if (strlen($search) > 100) { throw new UserException('Recherche trop longue : maximum 100 caractères'); } | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php namespace Garradin; use Garradin\Entities\Web\Page; use KD2\DB\EntityManager as EM; class Web { static public function search(string $search, bool $online_only = true): array { if (strlen($search) > 100) { throw new UserException('Recherche trop longue : maximum 100 caractères'); } |
︙ | ︙ | |||
24 25 26 27 28 29 30 | INNER JOIN web_pages AS p USING (id) WHERE %s files_search MATCH ? ORDER BY points DESC LIMIT 0,50;', $where); return DB::getInstance()->get($query, $search); } | | > > > > > > > > > > > > > > > > > > > > | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | INNER JOIN web_pages AS p USING (id) WHERE %s files_search MATCH ? ORDER BY points DESC LIMIT 0,50;', $where); return DB::getInstance()->get($query, $search); } static public function listCategories(?int $parent): array { $where = $parent ? sprintf('parent_id = %d', $parent) : 'parent_id IS NULL'; $sql = sprintf('SELECT * FROM @TABLE WHERE %s AND type = %d ORDER BY title COLLATE NOCASE;', $where, Page::TYPE_CATEGORY); return EM::getInstance(Page::class)->all($sql); } static public function listPages(?int $parent, bool $order_by_date = true): array { $where = $parent ? sprintf('parent_id = %d', $parent) : 'parent_id IS NULL'; $order = $order_by_date ? 'modified DESC' : 'title COLLATE NOCASE'; $sql = sprintf('SELECT * FROM @TABLE WHERE %s AND type = %d ORDER BY %s;', $where, Page::TYPE_PAGE, $order); return EM::getInstance(Page::class)->all($sql); } static public function get(int $id): Page { return EM::findOneById(Page::class, $id); } } |
Added src/templates/web/edit.tpl version [5664c13bce].
> > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | {include file="admin/_head.tpl" title="Édition : %s"|args:$page.title current="web"} {form_errors} <form method="post" action="{$self_url}" class="web-edit"> <fieldset> <legend>Édition</legend> <dl> {input type="text" label="Titre" required=true name="title" source=$page} {input type="text" label="Adresse unique" required=true name="uri" source=$page} {input type="checkbox" label="Brouillon" name="status" value=$page::STATUS_DRAFT source=$page help="Si coché, ne sera pas visible sur le site public"} </dl> </fieldset> </form> {include file="admin/_foot.tpl"} |
Added src/templates/web/index.tpl version [3bd955603c].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | {include file="admin/_head.tpl" title=$title current="web"} <nav class="tabs"> <aside> {linkbutton shape="plus" label="Nouvelle page" href="new.php?type=%d&parent=%d"|args:$type_page,$parent} {linkbutton shape="plus" label="Nouvelle catégorie" href="new.php?type=%d&parent=%d"|args:$type_category,$parent} </aside> <ul> <li class="current"><a href="./">Gestion du site web</a></li> {if $session->canAccess($session::SECTION_WEB, Membres::DROIT_ADMIN)} <li><a href="theme.php">Thèmes</a></li> <li><a href="config.php">Configuration</a></li> {/if} </ul> </nav> {if count($categories)} <h2 class="ruler">Catégories</h2> <table class="list"> <tbody> {foreach from=$categories item="p"} <tr> <th><a href="?parent={$p.id}">{$p.title}</a></th> <td>{if $p.status == $p::STATUS_ONLINE}En ligne{else}<em>Brouillon</em>{/if}</td> <td class="actions"> {if $p.status == $p::STATUS_ONLINE} {linkbutton shape="eye" label="Voir sur le site" href=$p->url() target="_blank"} {/if} {linkbutton shape="menu" label="Sous-catégories et pages" href="?parent=%d"|args:$p.id} {linkbutton shape="image" label="Prévisualiser" href="page.php?id=%d"|args:$p.id} {linkbutton shape="edit" label="Modifier" href="edit.php?id=%d"|args:$p.id} {linkbutton shape="delete" label="Supprimer" href="delete.php?id=%d"|args:$p.id} </td> </tr> {/foreach} </tbody> </table> {else} <p class="submit"> {linkbutton class="main" shape="plus" label="Nouvelle catégorie" href="new.php?type=%d&parent=%d"|args:$type_category,$parent} </p> {/if} {if count($pages)} <h2 class="ruler">Pages</h2> <p> {if !$order_date} {linkbutton shape="down" label="Trier par date" href="?parent=%d"|args:$parent} {else} {linkbutton shape="up" label="Trier par titre" href="?parent=%d&order_title"|args:$parent} {/if} </p> <table class="list"> <tbody> {foreach from=$pages item="p"} <tr> <th>{$p.title}</th> <td>{if $p.status == $p::STATUS_ONLINE}En ligne{else}<em>Brouillon</em>{/if}</td> <td>Modifié le {$p.modified|date_long}</td> <td class="actions"> {if $p.status == $p::STATUS_ONLINE} {linkbutton shape="eye" label="Voir sur le site" href=$p->url() target="_blank"} {/if} {linkbutton shape="image" label="Prévisualiser" href="page.php?id=%d"|args:$p.id} {linkbutton shape="edit" label="Modifier" href="edit.php?id=%d"|args:$p.id} {linkbutton shape="delete" label="Supprimer" href="delete.php?id=%d"|args:$p.id} </td> </tr> {/foreach} </tbody> </table> {else} <p class="submit"> {linkbutton shape="plus" class="main" label="Nouvelle page" href="new.php?type=%d&parent=%d"|args:$type_page,$parent} </p> {/if} {include file="admin/_foot.tpl"} |
Added src/www/admin/web/_inc.php version [c90c669bb9].
> > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 | <?php namespace Garradin; use Garradin\Membres\Session; require_once __DIR__ . '/../_inc.php'; $session->requireAccess(Session::SECTION_WEB, Membres::DROIT_ACCES); $tpl->assign('custom_css', ['wiki.css']); |
Added src/www/admin/web/edit.php version [8062efc532].
> > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php namespace Garradin; use Garradin\Web; use Garradin\Entities\Web\Page; require_once __DIR__ . '/_inc.php'; $id = (int) qg('id'); $page = Web::get($id); if (!$page) { throw new UserException('Page inconnue'); } $csrf_key = 'edit_' . $page->id(); $form->runIf('save', function ($page) { $page->importForm(); $page->save(); }, $csrf_key, Utils::getSelfURI()); $tpl->assign(compact('page', 'csrf_key')); $tpl->display('web/edit.tpl'); |
Added src/www/admin/web/index.php version [70815962e3].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php namespace Garradin; use Garradin\Web; use Garradin\Entities\Web\Page; require_once __DIR__ . '/_inc.php'; $parent = (int) qg('parent') ?: null; if ($parent) { $cat = Web::get($parent); if (!$cat) { throw new UserException('Catégorie inconnue'); } } $order_date = qg('order_title') === null; $categories = Web::listCategories($parent); $pages = Web::listPages($parent, $order_date); $title = $parent ? sprintf('Gestion du site web : %s', $cat->title) : 'Gestion du site web'; $type_page = Page::TYPE_PAGE; $type_category = Page::TYPE_CATEGORY; $tpl->assign(compact('categories', 'pages', 'title', 'parent', 'type_page', 'type_category', 'order_date')); $tpl->display('web/index.tpl'); |