Parcourir la source

template rapport_dactivitees avec pdf à télécharger

ouidade il y a 4 ans
Parent
commit
f8ecbbe597
39 fichiers modifiés avec 524 ajouts et 203 suppressions
  1. 21 0
      CHANGELOG.md
  2. 1 1
      SECURITY.md
  3. 18 18
      composer.lock
  4. 1 1
      system/defines.php
  5. 14 9
      system/src/Grav/Common/Data/Validation.php
  6. 2 1
      system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
  7. 23 0
      system/src/Grav/Common/Flex/Types/Pages/PageObject.php
  8. 2 0
      system/src/Grav/Common/Flex/Types/Pages/Traits/PageRoutableTrait.php
  9. 1 1
      system/src/Grav/Common/Grav.php
  10. 11 0
      system/src/Grav/Common/Page/Medium/AbstractMedia.php
  11. 12 10
      system/src/Grav/Common/Page/Page.php
  12. 114 71
      system/src/Grav/Common/Page/Pages.php
  13. 1 1
      system/src/Grav/Common/Uri.php
  14. 112 33
      system/src/Grav/Common/Utils.php
  15. 14 12
      system/src/Grav/Framework/Flex/Pages/Traits/PageRoutableTrait.php
  16. 69 13
      system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php
  17. 0 1
      user/blueprints/pages/modular/personnes.yaml
  18. 0 1
      user/blueprints/pages/modular/programmes.yaml
  19. 48 0
      user/blueprints/pages/modular/rapport_dactivitees.yaml
  20. 1 1
      user/config/site.yaml
  21. 1 1
      user/config/system.yaml
  22. BIN
      user/pages/01.home/02._programmes/logo_coubertin.png
  23. 2 2
      user/pages/01.home/02._programmes/programmes.md
  24. BIN
      user/pages/01.home/03._rapports-dactivitees/190410_events2event.pdf
  25. 0 0
      user/pages/01.home/03._rapports-dactivitees/images.jpeg
  26. 19 0
      user/pages/01.home/03._rapports-dactivitees/rapport_dactivitees.md
  27. 0 13
      user/pages/01.home/03._rapports-dactivites/showcase.md
  28. 1 0
      user/pages/01.home/04._gouvernance/01._conseil-dadministration/02._membres-du-conseil-dadministration/personnes.md
  29. 0 0
      user/pages/01.home/04._gouvernance/01._conseil-dadministration/02._membres-du-conseil-dadministration/photo_butlen.jpeg
  30. 0 0
      user/pages/01.home/04._gouvernance/02._equipe-epau/Alain Maugard_TROUVE.jpg
  31. 1 0
      user/pages/01.home/04._gouvernance/02._equipe-epau/personnes.md
  32. BIN
      user/pages/01.home/04._gouvernance/1612260288596.jpg
  33. BIN
      user/pages/01.home/04._gouvernance/aurelie-cousi-nommee-aux-fonctions-de-directrice.jpg
  34. BIN
      user/pages/01.home/04._gouvernance/catherine-chevillot-nouvelle-presidente-de-cite.JPG
  35. 0 1
      user/pages/01.home/04._gouvernance/gouvernance.md
  36. 1 1
      user/pages/01.home/modular.md
  37. 5 8
      user/themes/epau-antimatter/templates/modular/personnes.html.twig
  38. 4 3
      user/themes/epau-antimatter/templates/modular/programmes.html.twig
  39. 25 0
      user/themes/epau-antimatter/templates/modular/rapport_dactivitees.html.twig

+ 21 - 0
CHANGELOG.md

@@ -1,3 +1,24 @@
+# v1.7.9
+## 03/19/2021
+
+1. [](#new)
+    * Added `Media::hide()` method to hide files from media
+    * Added `Utils::getPathFromToken()` method which works also with `Flex Objects`
+    * Added `FlexMediaTrait::getMediaField()`, which can be used to access custom media set in the blueprint fields
+    * Added `FlexMediaTrait::getFieldSettings()`, which can be used to get media field settings
+1. [](#improved)
+    * Method `Utils::getPagePathFromToken()` now calls the more generic `Utils::getPathFromToken()`
+    * Updated `SECURITY.md` to use security@getgrav.org
+1. [](#bugfix)
+    * Fixed broken media upload in `Flex` with `@self/path`, `@page` and `@theme` destinations [#3275](https://github.com/getgrav/grav/issues/3275)
+    * Fixed media fields excluding newly deleted files before saving the object
+    * Fixed method `$pages->find()` should never redirect [#3266](https://github.com/getgrav/grav/pull/3266)
+    * Fixed `Page::activeChild()` throwing an error [#3276](https://github.com/getgrav/grav/issues/3276)
+    * Fixed `Flex Page` CRUD ACL when creating a new page (needs Flex Objects plugin update) [grav-plugin-flex-objects#115](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/115)
+    * Fixed the list of pages not showing up in admin [#3280](https://github.com/getgrav/grav/issues/3280)
+    * Fixed text field min/max validation for UTF8 characters [#3281](https://github.com/getgrav/grav/issues/3281)
+    * Fixed redirects using wrong redirect code
+
 # v1.7.8
 ## 03/17/2021
 

+ 1 - 1
SECURITY.md

@@ -18,4 +18,4 @@ If you cannot update to the latest stable version available because, for example
 
 ## Reporting a Vulnerability
 
-Please contact contact@getgrav.org with a detailed explaination of the security issue found and we will work with you to get it resolved as fast as possible.
+Please contact security@getgrav.org with a detailed explaination of the security issue found and we will work with you to get it resolved as fast as possible.

+ 18 - 18
composer.lock

@@ -381,16 +381,16 @@
         },
         {
             "name": "donatj/phpuseragentparser",
-            "version": "v1.3.0",
+            "version": "v1.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/donatj/PhpUserAgent.git",
-                "reference": "f9a521726b2ce4c5173281ceaab5a02c05b691ef"
+                "reference": "246c1cf0a44f07168c702203bf30d5f48f17bab0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/f9a521726b2ce4c5173281ceaab5a02c05b691ef",
-                "reference": "f9a521726b2ce4c5173281ceaab5a02c05b691ef",
+                "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/246c1cf0a44f07168c702203bf30d5f48f17bab0",
+                "reference": "246c1cf0a44f07168c702203bf30d5f48f17bab0",
                 "shasum": ""
             },
             "require": {
@@ -433,7 +433,7 @@
             ],
             "support": {
                 "issues": "https://github.com/donatj/PhpUserAgent/issues",
-                "source": "https://github.com/donatj/PhpUserAgent/tree/v1.3.0"
+                "source": "https://github.com/donatj/PhpUserAgent/tree/v1.4.0"
             },
             "funding": [
                 {
@@ -445,7 +445,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2021-02-18T04:30:49+00:00"
+            "time": "2021-03-16T16:25:14+00:00"
         },
         {
             "name": "dragonmantank/cron-expression",
@@ -642,16 +642,16 @@
         },
         {
             "name": "filp/whoops",
-            "version": "2.9.2",
+            "version": "2.10.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/filp/whoops.git",
-                "reference": "df7933820090489623ce0be5e85c7e693638e536"
+                "reference": "6ecda5217bf048088b891f7403b262906be5a957"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/filp/whoops/zipball/df7933820090489623ce0be5e85c7e693638e536",
-                "reference": "df7933820090489623ce0be5e85c7e693638e536",
+                "url": "https://api.github.com/repos/filp/whoops/zipball/6ecda5217bf048088b891f7403b262906be5a957",
+                "reference": "6ecda5217bf048088b891f7403b262906be5a957",
                 "shasum": ""
             },
             "require": {
@@ -701,7 +701,7 @@
             ],
             "support": {
                 "issues": "https://github.com/filp/whoops/issues",
-                "source": "https://github.com/filp/whoops/tree/2.9.2"
+                "source": "https://github.com/filp/whoops/tree/2.10.0"
             },
             "funding": [
                 {
@@ -709,7 +709,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2021-01-24T12:00:00+00:00"
+            "time": "2021-03-16T12:00:00+00:00"
         },
         {
             "name": "gregwar/cache",
@@ -4929,16 +4929,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "9.5.2",
+            "version": "9.5.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4"
+                "reference": "27241ac75fc37ecf862b6e002bf713b6566cbe41"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f661659747f2f87f9e72095bb207bceb0f151cb4",
-                "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/27241ac75fc37ecf862b6e002bf713b6566cbe41",
+                "reference": "27241ac75fc37ecf862b6e002bf713b6566cbe41",
                 "shasum": ""
             },
             "require": {
@@ -5016,7 +5016,7 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.2"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.3"
             },
             "funding": [
                 {
@@ -5028,7 +5028,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2021-02-02T14:45:58+00:00"
+            "time": "2021-03-17T07:30:34+00:00"
         },
         {
             "name": "psr/http-client",

+ 1 - 1
system/defines.php

@@ -8,7 +8,7 @@
 
 // Some standard defines
 define('GRAV', true);
-define('GRAV_VERSION', '1.7.8');
+define('GRAV_VERSION', '1.7.9');
 define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
 define('GRAV_TESTING', false);
 

+ 14 - 9
system/src/Grav/Common/Data/Validation.php

@@ -27,7 +27,6 @@ use function is_bool;
 use function is_float;
 use function is_int;
 use function is_string;
-use function strlen;
 
 /**
  * Class Validation
@@ -239,16 +238,20 @@ class Validation
             $value = trim($value);
         }
 
-        if (isset($params['min']) && strlen($value) < $params['min']) {
+        $len = mb_strlen($value);
+
+        $min = (int)($params['min'] ?? 0);
+        if ($min && $len < $min) {
             return false;
         }
 
-        if (isset($params['max']) && strlen($value) > $params['max']) {
+        $max = (int)($params['max'] ?? 0);
+        if ($max && $len > $max) {
             return false;
         }
 
-        $min = $params['min'] ?? 0;
-        if (isset($params['step']) && (strlen($value) - $min) % $params['step'] === 0) {
+        $step = (int)($params['step'] ?? 0);
+        if ($step && ($len - $min) % $step === 0) {
             return false;
         }
 
@@ -271,11 +274,13 @@ class Validation
             return '';
         }
 
+        $value = (string)$value;
+
         if (!empty($params['trim'])) {
             $value = trim($value);
         }
 
-        return (string) $value;
+        return $value;
     }
 
     /**
@@ -332,7 +337,7 @@ class Validation
      */
     protected static function filterLower($value, array $params)
     {
-        return strtolower($value);
+        return mb_strtolower($value);
     }
 
     /**
@@ -342,7 +347,7 @@ class Validation
      */
     protected static function filterUpper($value, array $params)
     {
-        return strtoupper($value);
+        return mb_strtoupper($value);
     }
 
 
@@ -534,7 +539,7 @@ class Validation
      */
     protected static function filterNumber($value, array $params, array $field)
     {
-        return (string)(int)$value !== (string)(float)$value ? (float) $value : (int) $value;
+        return (string)(int)$value !== (string)(float)$value ? (float)$value : (int)$value;
     }
 
     /**

+ 2 - 1
system/src/Grav/Common/Flex/Types/Pages/PageIndex.php

@@ -522,12 +522,13 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
                     $tmp = $child->children()->getIndex();
                     $child_count = $tmp->count();
                     $count = $filters ? $tmp->filterBy($filters, true)->count() : null;
+                    $route = $child->getRoute();
                     $payload = [
                         'item-key' => basename($child->rawRoute() ?? $child->getKey()),
                         'icon' => $icon,
                         'title' => htmlspecialchars($child->menu()),
                         'route' => [
-                            'display' => $child->getRoute()->toString(false) ?: '/',
+                            'display' => ($route ? ($route->toString(false) ?: '/') : null) ?? '',
                             'raw' => $child->rawRoute(),
                         ],
                         'modified' => $this->jsDate($child->modified()),

+ 23 - 0
system/src/Grav/Common/Flex/Types/Pages/PageObject.php

@@ -22,6 +22,7 @@ use Grav\Common\Flex\Types\Pages\Traits\PageTranslateTrait;
 use Grav\Common\Language\Language;
 use Grav\Common\Page\Interfaces\PageInterface;
 use Grav\Common\Page\Pages;
+use Grav\Common\User\Interfaces\UserInterface;
 use Grav\Common\Utils;
 use Grav\Framework\Filesystem\Filesystem;
 use Grav\Framework\Flex\FlexObject;
@@ -321,6 +322,28 @@ class PageObject extends FlexPageObject
         return $this;
     }
 
+    /**
+     * @param UserInterface $user
+     * @param string $action
+     * @param string $scope
+     * @param bool $isMe
+     * @return bool|null
+     */
+    protected function isAuthorizedOverride(UserInterface $user, string $action, string $scope, bool $isMe): ?bool
+    {
+        // Special case: creating a new page means checking parent for its permissions.
+        if ($action === 'create' && !$this->exists()) {
+            $parent = $this->parent();
+            if ($parent && method_exists($parent, 'isAuthorized')) {
+                return $parent->isAuthorized($action, $scope, $user);
+            }
+
+            return false;
+        }
+
+        return parent::isAuthorizedOverride($user, $action, $scope, $isMe);
+    }
+
     /**
      * @param array $ordering
      * @return PageCollection|null

+ 2 - 0
system/src/Grav/Common/Flex/Types/Pages/Traits/PageRoutableTrait.php

@@ -15,6 +15,7 @@ use Grav\Common\Grav;
 use Grav\Common\Page\Interfaces\PageCollectionInterface;
 use Grav\Common\Page\Interfaces\PageInterface;
 use Grav\Common\Page\Pages;
+use Grav\Common\Uri;
 use Grav\Common\Utils;
 use Grav\Framework\Filesystem\Filesystem;
 use RuntimeException;
@@ -97,6 +98,7 @@ trait PageRoutableTrait
     public function activeChild(): bool
     {
         $grav = Grav::instance();
+        /** @var Uri $uri */
         $uri = $grav['uri'];
         /** @var Pages $pages */
         $pages = $grav['pages'];

+ 1 - 1
system/src/Grav/Common/Grav.php

@@ -426,7 +426,7 @@ class Grav extends Container
         // Clean route for redirect
         $route = preg_replace("#^\/[\\\/]+\/#", '/', $route);
 
-        if (null !== $code || $code < 300 || $code > 399) {
+        if ($code < 300 || $code > 399) {
             $code = null;
         }
 

+ 11 - 0
system/src/Grav/Common/Page/Medium/AbstractMedia.php

@@ -198,6 +198,17 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
         }
     }
 
+    /**
+     * @param string $name
+     * @return void
+     */
+    public function hide($name)
+    {
+        $this->offsetUnset($name);
+
+        unset($this->images[$name], $this->videos[$name], $this->audios[$name], $this->files[$name]);
+    }
+
     /**
      * Create Medium from a file.
      *

+ 12 - 10
system/src/Grav/Common/Page/Page.php

@@ -2496,21 +2496,23 @@ class Page implements PageInterface
      */
     public function activeChild()
     {
-        $uri = Grav::instance()['uri'];
-        $pages = Grav::instance()['pages'];
+        $grav = Grav::instance();
+        /** @var Uri $uri */
+        $uri = $grav['uri'];
+        /** @var Pages $pages */
+        $pages = $grav['pages'];
         $uri_path = rtrim(urldecode($uri->path()), '/');
-        $routes = Grav::instance()['pages']->routes();
+        $routes = $pages->routes();
 
         if (isset($routes[$uri_path])) {
+            $page = $pages->find($uri->route());
             /** @var PageInterface|null $child_page */
-            $child_page = $pages->find($uri->route())->parent();
-            if ($child_page) {
-                while (!$child_page->root()) {
-                    if ($this->path() === $child_page->path()) {
-                        return true;
-                    }
-                    $child_page = $child_page->parent();
+            $child_page = $page ? $page->parent() : null;
+            while ($child_page && !$child_page->root()) {
+                if ($this->path() === $child_page->path()) {
+                    return true;
                 }
+                $child_page = $child_page->parent();
             }
         }
 

+ 114 - 71
system/src/Grav/Common/Page/Pages.php

@@ -880,103 +880,146 @@ class Pages
     }
 
     /**
-     * alias method to return find a page.
+     * Find a page based on route.
      *
-     * @param string $route The relative URL of the page
-     * @param bool   $all
+     * @param string $route The route of the page
+     * @param bool   $all   If true, return also non-routable pages, otherwise return null if page isn't routable
      * @return PageInterface|null
      */
     public function find($route, $all = false)
     {
-        return $this->dispatch($route, $all, false);
+        $route = urldecode((string)$route);
+
+        // Fetch page if there's a defined route to it.
+        $path = $this->routes[$route] ?? null;
+        $page = null !== $path ? $this->get($path) : null;
+
+        // Try without trailing slash
+        if (null === $page && Utils::endsWith($route, '/')) {
+            $path = $this->routes[rtrim($route, '/')] ?? null;
+            $page = null !== $path ? $this->get($path) : null;
+        }
+
+        if (!$all && !isset($this->grav['admin'])) {
+            if (null === $page || !$page->routable()) {
+                // If the page cannot be accessed, look for the site wide routes and wildcards.
+                $page = $this->findSiteBasedRoute($route) ?? $page;
+            }
+        }
+
+        return $page;
+    }
+
+    /**
+     * Check site based routes.
+     *
+     * @param string $route
+     * @return PageInterface|null
+     */
+    protected function findSiteBasedRoute($route)
+    {
+        /** @var Config $config */
+        $config = $this->grav['config'];
+
+        $site_routes = $config->get('site.routes');
+        if (!is_array($site_routes)) {
+            return null;
+        }
+
+        $page = null;
+
+        // See if route matches one in the site configuration
+        $site_route = $site_routes[$route] ?? null;
+        if ($site_route) {
+            $page = $this->find($site_route);
+        } else {
+            // Use reverse order because of B/C (previously matched multiple and returned the last match).
+            foreach (array_reverse($site_routes, true) as $pattern => $replace) {
+                $pattern = '#^' . str_replace('/', '\/', ltrim($pattern, '^')) . '#';
+                try {
+                    $found = preg_replace($pattern, $replace, $route);
+                    if ($found && $found !== $route) {
+                        $page = $this->find($found);
+                        if ($page) {
+                            return $page;
+                        }
+                    }
+                } catch (ErrorException $e) {
+                    $this->grav['log']->error('site.routes: ' . $pattern . '-> ' . $e->getMessage());
+                }
+            }
+        }
+
+        return $page;
     }
 
     /**
      * Dispatch URI to a page.
      *
      * @param string $route The relative URL of the page
-     * @param bool $all
-     * @param bool $redirect
+     * @param bool $all If true, return also non-routable pages, otherwise return null if page isn't routable
+     * @param bool $redirect If true, allow redirects
      * @return PageInterface|null
      * @throws Exception
      */
     public function dispatch($route, $all = false, $redirect = true)
     {
-        $route = urldecode($route);
+        $page = $this->find($route, true);
 
-        // Fetch page if there's a defined route to it.
-        $path = $this->routes[$route] ?? null;
-        $page = null !== $path ? $this->get($path) : null;
-        // Try without trailing slash
-        if (!$page && Utils::endsWith($route, '/')) {
-            $path = $this->routes[rtrim($route, '/')] ?? null;
-            $page = null !== $path ? $this->get($path) : null;
+        // If we want all pages or are in admin, return what we already have.
+        if ($all || isset($this->grav['admin'])) {
+            return $page;
         }
 
-        // Are we in the admin? this is important!
-        $not_admin = !isset($this->grav['admin']);
+        if ($page) {
+            $routable = $page->routable();
+            if ($redirect) {
+                if ($page->redirect()) {
+                    // Follow a redirect page.
+                    $this->grav->redirectLangSafe($page->redirect());
+                }
+
+                if (!$routable && ($child = $page->children()->visible()->routable()->published()->first()) !== null) {
+                    // Redirect to the first visible child as current page isn't routable.
+                    $this->grav->redirectLangSafe($child->route());
+                }
+            }
 
-        // If the page cannot be reached, look into site wide redirects, routes + wildcards
-        if (!$all && $not_admin) {
-            // If the page is a simple redirect, just do it.
-            if ($redirect && $page && $page->redirect()) {
-                $this->grav->redirectLangSafe($page->redirect());
+            if ($routable) {
+                return $page;
             }
+        }
 
-            // fall back and check site based redirects
-            if (!$page || !$page->routable()) {
-                // Redirect to the first child (placeholder page)
-                if ($redirect && $page && count($children = $page->children()->visible()->routable()->published()) > 0) {
-                    $this->grav->redirectLangSafe($children->first()->route());
-                }
+        $route = urldecode((string)$route);
 
-                /** @var Config $config */
-                $config = $this->grav['config'];
+        // The page cannot be reached, look into site wide redirects, routes and wildcards.
+        $redirectedPage = $this->findSiteBasedRoute($route);
+        if ($redirectedPage) {
+            $page = $this->dispatch($redirectedPage->route(), false, $redirect);
+        }
 
-                // See if route matches one in the site configuration
-                $site_route = $config->get("site.routes.{$route}");
-                if ($site_route) {
-                    $page = $this->dispatch($site_route, $all, $redirect);
-                } else {
-                    /** @var Uri $uri */
-                    $uri = $this->grav['uri'];
-                    /** @var \Grav\Framework\Uri\Uri $source_url */
-                    $source_url = $uri->uri(false);
-
-                    // Try Regex style redirects
-                    $site_redirects = $config->get('site.redirects');
-                    if (is_array($site_redirects)) {
-                        foreach ((array)$site_redirects as $pattern => $replace) {
-                            $pattern = ltrim($pattern, '^');
-                            $pattern = '#^' . str_replace('/', '\/', $pattern) . '#';
-                            try {
-                                /** @var string $found */
-                                $found = preg_replace($pattern, $replace, $source_url);
-                                if ($found && $found !== $source_url) {
-                                    $this->grav->redirectLangSafe($found);
-                                }
-                            } catch (ErrorException $e) {
-                                $this->grav['log']->error('site.redirects: ' . $pattern . '-> ' . $e->getMessage());
-                            }
-                        }
-                    }
+        /** @var Config $config */
+        $config = $this->grav['config'];
 
-                    // Try Regex style routes
-                    $site_routes = $config->get('site.routes');
-                    if (is_array($site_routes)) {
-                        foreach ((array)$site_routes as $pattern => $replace) {
-                            $pattern = '#^' . str_replace('/', '\/', ltrim($pattern, '^')) . '#';
-                            try {
-                                /** @var string $found */
-                                $found = preg_replace($pattern, $replace, $source_url);
-                                if ($found && $found !== $source_url) {
-                                    $page = $this->dispatch($found, $all, $redirect);
-                                }
-                            } catch (ErrorException $e) {
-                                $this->grav['log']->error('site.routes: ' . $pattern . '-> ' . $e->getMessage());
-                            }
-                        }
+        /** @var Uri $uri */
+        $uri = $this->grav['uri'];
+        /** @var \Grav\Framework\Uri\Uri $source_url */
+        $source_url = $uri->uri(false);
+
+        // Try Regex style redirects
+        $site_redirects = $config->get('site.redirects');
+        if (is_array($site_redirects)) {
+            foreach ((array)$site_redirects as $pattern => $replace) {
+                $pattern = ltrim($pattern, '^');
+                $pattern = '#^' . str_replace('/', '\/', $pattern) . '#';
+                try {
+                    /** @var string $found */
+                    $found = preg_replace($pattern, $replace, $source_url);
+                    if ($found && $found !== $source_url) {
+                        $this->grav->redirectLangSafe($found);
                     }
+                } catch (ErrorException $e) {
+                    $this->grav['log']->error('site.redirects: ' . $pattern . '-> ' . $e->getMessage());
                 }
             }
         }

+ 1 - 1
system/src/Grav/Common/Uri.php

@@ -519,7 +519,7 @@ class Uri
      * Return the full uri
      *
      * @param bool $include_root
-     * @return mixed
+     * @return string
      */
     public function uri($include_root = true)
     {

+ 112 - 33
system/src/Grav/Common/Utils.php

@@ -12,11 +12,15 @@ namespace Grav\Common;
 use DateTime;
 use DateTimeZone;
 use Exception;
+use Grav\Common\Flex\Types\Pages\PageObject;
 use Grav\Common\Helpers\Truncator;
 use Grav\Common\Page\Interfaces\PageInterface;
 use Grav\Common\Markdown\Parsedown;
 use Grav\Common\Markdown\ParsedownExtra;
 use Grav\Common\Page\Markdown\Excerpts;
+use Grav\Common\Page\Pages;
+use Grav\Framework\Flex\Flex;
+use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
 use InvalidArgumentException;
 use Negotiation\Accept;
 use Negotiation\Negotiator;
@@ -1520,7 +1524,7 @@ abstract class Utils
     }
 
     /**
-     * Get path based on a token
+     * Get relative page path based on a token.
      *
      * @param string $path
      * @param PageInterface|null $page
@@ -1529,47 +1533,122 @@ abstract class Utils
      */
     public static function getPagePathFromToken($path, PageInterface $page = null)
     {
-        $path_parts = pathinfo($path);
-        $grav       = Grav::instance();
+        return static::getPathFromToken($path, $page);
+    }
 
-        $basename = '';
-        if (isset($path_parts['extension'])) {
-            $basename = '/' . $path_parts['basename'];
-            $path     = rtrim($path_parts['dirname'], ':');
+    /**
+     * Get relative path based on a token.
+     *
+     * Path supports following syntaxes:
+     *
+     * 'self@', 'self@/path'
+     * 'page@:/route', 'page@:/route/filename.ext'
+     * 'theme@:', 'theme@:/path'
+     *
+     * @param string $path
+     * @param FlexObjectInterface|PageInterface|null $object
+     * @return string
+     * @throws RuntimeException
+     */
+    public static function getPathFromToken($path, $object = null)
+    {
+        $matches = static::resolveTokenPath($path);
+        if (null === $matches) {
+            return $path;
         }
 
-        $regex = '/(@self|self@)|((?:@page|page@):(?:.*))|((?:@theme|theme@):(?:.*))/';
-        preg_match($regex, $path, $matches);
+        $grav = Grav::instance();
 
-        if ($matches) {
-            if ($matches[1]) {
-                if (null === $page) {
-                    throw new RuntimeException('Page not available for this self@ reference');
+        switch ($matches[0]) {
+            case 'self':
+                if (null === $object) {
+                    throw new RuntimeException(sprintf('Page not available for self@ reference: %s', $path));
                 }
-            } elseif ($matches[2]) {
-                // page@
-                $parts = explode(':', $path);
-                $route = $parts[1];
-                $page  = $grav['page']->find($route);
-            } elseif ($matches[3]) {
-                // theme@
-                $parts = explode(':', $path);
-                $route = $parts[1];
-                $theme = str_replace(ROOT_DIR, '', $grav['locator']->findResource('theme://'));
-
-                return $theme . $route . $basename;
-            }
-        } else {
-            return $path . $basename;
-        }
 
-        if (!$page) {
-            throw new RuntimeException('Page route not found: ' . $path);
+                if ($matches[2] === '') {
+                    if ($object->exists()) {
+                        $route = '/' . $matches[1];
+
+                        if ($object instanceof PageInterface) {
+                            return trim($object->relativePagePath() . $route, '/');
+                        }
+
+                        $folder = $object->getMediaFolder();
+                        if ($folder) {
+                            return trim($folder . $route, '/');
+                        }
+                    } else {
+                        return '';
+                    }
+                }
+
+                break;
+            case 'page':
+                if ($matches[1] === '') {
+                    $route = '/' . $matches[2];
+
+                    // Exclude filename from the page lookup.
+                    if (pathinfo($route, PATHINFO_EXTENSION)) {
+                        $basename = '/' . basename($route);
+                        $route = \dirname($route);
+                    } else {
+                        $basename = '';
+                    }
+
+                    $key = trim($route === '/' ? $grav['config']->get('system.home.alias') : $route, '/');
+                    if ($object instanceof PageObject) {
+                        $object = $object->getFlexDirectory()->getObject($key);
+                    } elseif (static::isAdminPlugin()) {
+                        /** @var Flex|null $flex */
+                        $flex = $grav['flex'] ?? null;
+                        $object = $flex ? $flex->getObject($key, 'pages') : null;
+                    } else {
+                        /** @var Pages $pages */
+                        $pages = $grav['pages'];
+                        $object = $pages->find($route);
+                    }
+
+                    if ($object instanceof PageInterface) {
+                        return trim($object->relativePagePath() . $basename, '/');
+                    }
+                }
+
+                break;
+            case 'theme':
+                if ($matches[1] === '') {
+                    $route = '/' . $matches[2];
+                    $theme = $grav['locator']->findResource('theme://', false);
+                    if (false !== $theme) {
+                        return trim($theme . $route, '/');
+                    }
+                }
+
+                break;
         }
 
-        $path = str_replace($matches[0], rtrim($page->relativePagePath(), '/'), $path);
+        throw new RuntimeException(sprintf('Token path not found: %s', $path));
+    }
+
+    /**
+     * Returns [token, route, path] from '@token/route:/path'. Route and path are optional. If pattern does not match, return null.
+     *
+     * @param string $path
+     * @return string[]|null
+     */
+    private static function resolveTokenPath(string $path): ?array
+    {
+        if (strpos($path, '@') !== false) {
+            $regex = '/^(@\w+|\w+@|@\w+@)([^:]*)(.*)$/u';
+            if (preg_match($regex, $path, $matches)) {
+                return [
+                    trim($matches[1], '@'),
+                    trim($matches[2], '/'),
+                    trim($matches[3], ':/')
+                ];
+            }
+        }
 
-        return $path . $basename;
+        return null;
     }
 
     /**

+ 14 - 12
system/src/Grav/Framework/Flex/Pages/Traits/PageRoutableTrait.php

@@ -420,9 +420,11 @@ trait PageRoutableTrait
             return $this->_parentCache;
         }
 
+        // Use filesystem as \dirname() does not work in Windows because of '/foo' becomes '\'.
+        $filesystem = Filesystem::getInstance(false);
         $directory = $this->getFlexDirectory();
-        $parentKey = ltrim(dirname("/{$this->getKey()}"), '/');
-        if ($parentKey) {
+        $parentKey = ltrim($filesystem->dirname("/{$this->getKey()}"), '/');
+        if ('' !== $parentKey) {
             $parent = $directory->getObject($parentKey);
             $language = $this->getLanguage();
             if ($language && $parent && method_exists($parent, 'getTranslation')) {
@@ -433,7 +435,7 @@ trait PageRoutableTrait
         } else {
             $index = $directory->getIndex();
 
-            $this->_parentCache = method_exists($index, 'getRoot') ? $index->getRoot() : null;
+            $this->_parentCache = \is_callable([$index, 'getRoot']) ? $index->getRoot() : null;
         }
 
         return $this->_parentCache;
@@ -497,22 +499,22 @@ trait PageRoutableTrait
     public function activeChild(): bool
     {
         $grav = Grav::instance();
+        /** @var Uri $uri */
         $uri = $grav['uri'];
+        /** @var Pages $pages */
         $pages = $grav['pages'];
         $uri_path = rtrim(urldecode($uri->path()), '/');
         $routes = $pages->routes();
 
         if (isset($routes[$uri_path])) {
-            /** @var PageInterface $child_page|null */
-            $child_page = $pages->find($uri->route())->parent();
-            if (null !== $child_page) {
-                while (!$child_page->root()) {
-                    if ($this->path() === $child_page->path()) {
-                        return true;
-                    }
-                    /** @var PageInterface $child_page|null */
-                    $child_page = $child_page->parent();
+            $page = $pages->find($uri->route());
+            /** @var PageInterface|null $child_page */
+            $child_page = $page ? $page->parent() : null;
+            while ($child_page && !$child_page->root()) {
+                if ($this->path() === $child_page->path()) {
+                    return true;
                 }
+                $child_page = $child_page->parent();
             }
         }
 

+ 69 - 13
system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php

@@ -14,8 +14,10 @@ use Grav\Common\Grav;
 use Grav\Common\Media\Interfaces\MediaCollectionInterface;
 use Grav\Common\Media\Interfaces\MediaUploadInterface;
 use Grav\Common\Media\Traits\MediaTrait;
+use Grav\Common\Page\Media;
 use Grav\Common\Page\Medium\Medium;
 use Grav\Common\Page\Medium\MediumFactory;
+use Grav\Common\Utils;
 use Grav\Framework\Cache\CacheInterface;
 use Grav\Framework\Filesystem\Filesystem;
 use Grav\Framework\Flex\FlexDirectory;
@@ -23,6 +25,7 @@ use Grav\Framework\Form\FormFlashFile;
 use Psr\Http\Message\UploadedFileInterface;
 use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
 use RuntimeException;
+use function array_key_exists;
 use function in_array;
 use function is_array;
 use function is_callable;
@@ -75,11 +78,40 @@ trait FlexMediaTrait
         return $media;
     }
 
+    /**
+     * @param string $field
+     * @return MediaCollectionInterface|null
+     */
+    public function getMediaField(string $field): ?MediaCollectionInterface
+    {
+        // Field specific media.
+        $settings = $this->getFieldSettings($field);
+        if (!empty($settings['media_field'])) {
+            $var = 'destination';
+        } elseif (!empty($settings['media_picker_field'])) {
+            $var = 'folder';
+        }
+
+        if (empty($var)) {
+            // Not a media field.
+            $media = null;
+        } elseif ($settings['self']) {
+            // Uses main media.
+            $media = $this->getMedia();
+        } else {
+            // Uses custom media.
+            $media = new Media($settings[$var]);
+            $this->addUpdatedMedia($media);
+        }
+
+        return $media;
+    }
+
     /**
      * @param string $field
      * @return array|null
      */
-    protected function getFieldSettings(string $field): ?array
+    public function getFieldSettings(string $field): ?array
     {
         if ($field === '') {
             return null;
@@ -88,14 +120,32 @@ trait FlexMediaTrait
         // Load settings for the field.
         $schema = $this->getBlueprint()->schema();
         $settings = $field && is_object($schema) ? (array)$schema->getProperty($field) : null;
+        if (!isset($settings) || !is_array($settings)) {
+            return null;
+        }
 
-        if (isset($settings['type']) && (in_array($settings['type'], ['avatar', 'file', 'pagemedia']) || !empty($settings['destination']))) {
-            // Set destination folder.
+        $type = $settings['type'] ?? '';
+
+        // Media field.
+        if (!empty($settings['media_field']) || array_key_exists('destination', $settings) || in_array($type, ['avatar', 'file', 'pagemedia'], true)) {
             $settings['media_field'] = true;
-            if (empty($settings['destination']) || in_array($settings['destination'], ['@self', 'self@', '@self@'], true)) {
-                $settings['destination'] = $this->getMediaFolder();
+            $var = 'destination';
+        }
+
+        // Media picker field.
+        if (!empty($settings['media_picker_field']) || in_array($type, ['filepicker', 'pagemediaselect'], true)) {
+            $settings['media_picker_field'] = true;
+            $var = 'folder';
+        }
+
+        // Set media folder for media fields.
+        if (isset($var)) {
+            $folder = $settings[$var] ?? '';
+            if (in_array(rtrim($folder, '/'), ['', '@self', 'self@', '@self@'], true)) {
+                $settings[$var] = $this->getMediaFolder();
                 $settings['self'] = true;
             } else {
+                $settings[$var] = Utils::getPathFromToken($folder, $this);
                 $settings['self'] = false;
             }
         }
@@ -115,7 +165,6 @@ trait FlexMediaTrait
         return $settings + ['accept' => '*', 'limit' => 1000, 'self' => true];
     }
 
-
     protected function getMediaFields(): array
     {
         // Load settings for the field.
@@ -206,12 +255,13 @@ trait FlexMediaTrait
      */
     public function uploadMediaFile(UploadedFileInterface $uploadedFile, string $filename = null, string $field = null): void
     {
-        $media = $this->getMedia();
+        $settings = $this->getMediaFieldSettings($field ?? '');
+
+        $media = $field ? $this->getMediaField($field) : $this->getMedia();
         if (!$media instanceof MediaUploadInterface) {
             throw new RuntimeException("Media for {$this->getFlexDirectory()->getFlexType()} doesn't support file uploads.");
         }
 
-        $settings = $this->getMediaFieldSettings($field ?? '');
         $filename = $media->checkUploadedFile($uploadedFile, $filename, $settings);
         $media->copyUploadedFile($uploadedFile, $filename, $settings);
         $this->clearMediaCache();
@@ -322,13 +372,20 @@ trait FlexMediaTrait
         foreach ($this->getUpdatedMedia() as $filename => $upload) {
             if (is_array($upload)) {
                 // Uses new format with [UploadedFileInterface, array].
-                $upload = $upload[0];
+                $settings = $upload[1];
+                if ($settings['destination'] === $media->getPath()) {
+                    $upload = $upload[0];
+                } else {
+                    $upload = false;
+                }
             }
-            if ($upload) {
-                $medium = MediumFactory::fromUploadedFile($upload);
+            if (false !== $upload) {
+                $medium = $upload ? MediumFactory::fromUploadedFile($upload) : null;
+                $updated = true;
                 if ($medium) {
-                    $updated = true;
                     $media->add($filename, $medium);
+                } else {
+                    $media->hide($filename);
                 }
             }
         }
@@ -356,7 +413,6 @@ trait FlexMediaTrait
             return;
         }
 
-
         // Upload/delete altered files.
         /**
          * @var string $filename

+ 0 - 1
user/blueprints/pages/modular/personnes.yaml

@@ -42,4 +42,3 @@ form:
                 .portrait:
                   type: filepicker
                   label: Portrait
-                  folder: 'user/pages/01.home/04._gouvernance'

+ 0 - 1
user/blueprints/pages/modular/programmes.yaml

@@ -41,4 +41,3 @@ form:
                   name: logo_du_programme
                   type: filepicker
                   label: Logo
-                  folder: 'user/pages/01.home/02._programmes'

+ 48 - 0
user/blueprints/pages/modular/rapport_dactivitees.yaml

@@ -0,0 +1,48 @@
+title: Rapport d'activitées
+'@extends': default
+
+form:
+  fields:
+    tabs:
+      fields:
+        advanced:
+          fields:
+            columns:
+              fields:
+                column1:
+                  fields:
+                    name:
+                      default: modular/rapport_dactivitees
+                      '@data-options': '\Grav\Common\Page\Pages::modularTypes'
+            overrides:
+              fields:
+                header.template:
+                  default: modular/rapport_dactivitees
+                  '@data-options': '\Grav\Common\Page\Pages::modularTypes'
+        rapports:
+          type: tab
+          title: Rapports d'activitées
+          fields:
+            header.rapports:
+              name: rapports
+              type: list
+              label: Rapports d'activitées
+
+              fields:
+                .titre_du_rapport:
+                  type: text
+                  label: Titre du rapport
+                .texte_de_presentation:
+                  type: markdown
+                  label: Texte de présentation du rapport
+                  validate:
+                    type: textarea
+                .couverture:
+                  name: couv_du_rapport
+                  type: filepicker
+                  label: Image de couverture
+                .pdf:
+                  type: filepicker
+                  label: pdf à télécharger
+                  accept:
+                    - .pdf

+ 1 - 1
user/config/site.yaml

@@ -1,7 +1,7 @@
 title: 'GIP EPAU'
 default_lang: fr
 author:
-  name: 'Ouidade Soussi'
+  name: 'Ouidade Soussi Chiadmi'
   email: ouidade@figureslibres.io
 taxonomies:
   - category

+ 1 - 1
user/config/system.yaml

@@ -96,7 +96,7 @@ pages:
 cache:
   enabled: true
   check:
-    method: file
+    method: hash
   driver: auto
   prefix: g
   purge_at: '0 4 * * *'

BIN
user/pages/01.home/02._programmes/logo_coubertin.png


+ 2 - 2
user/pages/01.home/02._programmes/programmes.md

@@ -17,10 +17,10 @@ programmes:
     -
         nom_du_programme: Coubertin
         texte_de_presentation: 'Le programme de recherche-action Coubertin construit le récit, au fil de l’eau, de la conception des ouvrages et des opérations d’aménagement des Jeux Olympiques et Paralympiques de Paris 2024. À travers une observation embarquée au sein de la SOLIDEO, l’équipe de chercheurs analyse la production architecturale et urbaine et sa capacité à transformer les pratiques d’aménagement.'
-        logo: null
+        logo: logo_coubertin.png
 visible: true
 debugger: true
-media_order: 'popsu.png,forum_solution.png,europan_france.jpg'
+media_order: 'popsu.png,forum_solution.png,europan_france.jpg,logo_coubertin.png'
 process:
     markdown: true
     twig: false

BIN
user/pages/01.home/03._rapports-dactivitees/190410_events2event.pdf


+ 0 - 0
user/pages/01.home/03._rapports-dactivites/images.jpeg → user/pages/01.home/03._rapports-dactivitees/images.jpeg


+ 19 - 0
user/pages/01.home/03._rapports-dactivitees/rapport_dactivitees.md

@@ -0,0 +1,19 @@
+---
+title: 'Rapports d''activitées'
+body_classes: modular
+media_order: 'images.jpeg,190410_events2event.pdf'
+visible: true
+debugger: true
+rapports:
+    -
+        titre_du_rapport: 'rapport d''activitées POPSU 2019'
+        texte_de_presentation: "#### # ozMIHEFMJBZNDKCJbnW NX;, \r\noipouepuoaiejhrglnk\r\n_loremnksjdqs _\r\n"
+        couverture: images.jpeg
+        pdf: 190410_events2event.pdf
+template: modular/rapport_dactivitees
+process:
+    markdown: true
+    twig: false
+---
+
+###  Rapports d’activitées

+ 0 - 13
user/pages/01.home/03._rapports-dactivites/showcase.md

@@ -1,13 +0,0 @@
----
-title: 'Rapports d''activités'
-body_classes: modular
-media_order: images.jpeg
-visible: true
-debugger: true
----
-
-###  Rapports d’activités de l’Europe des projets architecturaux et urbains 
-
-
-[Lien pour télécharger]() 
-![]images.jpeg(http://)+ images couv

+ 1 - 0
user/pages/01.home/04._gouvernance/01._conseil-dadministration/02._membres-du-conseil-dadministration/personnes.md

@@ -10,6 +10,7 @@ personnes:
         portrait: photo_butlen.jpeg
 debugger: true
 template: modular/personnes
+media_order: photo_butlen.jpeg
 ---
 
 ### Membres du conseil d'administration

+ 0 - 0
user/pages/01.home/04._gouvernance/photo_butlen.jpeg → user/pages/01.home/04._gouvernance/01._conseil-dadministration/02._membres-du-conseil-dadministration/photo_butlen.jpeg


+ 0 - 0
user/pages/01.home/04._gouvernance/Alain Maugard_TROUVE.jpg → user/pages/01.home/04._gouvernance/02._equipe-epau/Alain Maugard_TROUVE.jpg


+ 1 - 0
user/pages/01.home/04._gouvernance/02._equipe-epau/personnes.md

@@ -12,6 +12,7 @@ personnes:
         fonction: 'Président de l’Association Europan France'
         biographie: "Né le 23 avril 1943 à Nérac (Lot-et-Garonne), Alain Maugard est un ancien élève de l’École polytechnique et Ingénieur général des Ponts et Chaussées. Il débute sa carrière au ministère de l’Équipement au service des Affaires économiques et internationales et dans les services urbanisme construction des Directions Départementales. Chef du service de la politique technique à la direction de la Construction et secrétaire permanent du Plan Construction, de 1978 à 1981, il devient successivement directeur adjoint de cabinet des ministres de l’Urbanisme et du Logement Roger Quilliot et Paul Quilès puis directeur de la Construction au ministère de l’Équipement, du Logement, des Transports et de la Mer. En 1990, il est nommé directeur général de l’Établissement public d’aménagement de la Défense (epad), jusqu’en 1993 où il prend la présidence du cstb. Il quitte ces fonctions en 2008 pour rejoindre le Conseil général de l’environnement et du développement durable où il assure la présidence de la section « risques, sécurité, sûreté ». En septembre 2009, il accède à la présidence de Qualibat. \r\nEn outre, Alain Maugard est membre du conseil d’administration de l’Ademe. Il a également piloté le Comité opérationnel (comop) du chantier n°1 « Bâtiments neufs publics et privés » du Grenelle de l’Environnement. \r\n\r\nBibliographie\_:\r\n- Regards sur le bâtiment — Le futur en construction, éditions Le Moniteur, 2006\r\n - Regard sur la ville durable, Alain Maugard et Jean-Pierre Cuisinier, CSTB, mars 2010\r\n - Le BEPOS pour tous (livre électronique), http://outils.xpair.com/livre/bepos-pourtous/7.htm (25 juin 2015) \r\n"
         portrait: 'Alain Maugard_TROUVE.jpg'
+media_order: 'Alain Maugard_TROUVE.jpg'
 ---
 
 ## Équipe EPAU

BIN
user/pages/01.home/04._gouvernance/1612260288596.jpg


BIN
user/pages/01.home/04._gouvernance/aurelie-cousi-nommee-aux-fonctions-de-directrice.jpg


BIN
user/pages/01.home/04._gouvernance/catherine-chevillot-nouvelle-presidente-de-cite.JPG


+ 0 - 1
user/pages/01.home/04._gouvernance/gouvernance.md

@@ -15,7 +15,6 @@ content:
 admin:
     children_display_order: collection
 debugger: true
-media_order: '1612260288596.jpg,Alain Maugard_TROUVE.jpg,aurelie-cousi-nommee-aux-fonctions-de-directrice.jpg,catherine-chevillot-nouvelle-presidente-de-cite.JPG,photo_butlen.jpeg'
 ---
 
 # Gouvernance

+ 1 - 1
user/pages/01.home/modular.md

@@ -11,7 +11,7 @@ content:
         custom:
             - _accueil
             - _programmes
-            - _rapports-dactivites
+            - _rapports-dactivitees
             - _gouvernance
             - _contact
 media_order: 'jeux-olypiques-paris-2024-village-athle-lot-e2.jpg,Logotype_GIP_EPAU.svg.png'

+ 5 - 8
user/themes/epau-antimatter/templates/modular/personnes.html.twig

@@ -3,12 +3,7 @@
     <div class="feature-items">
     {% for personne in page.header.personnes %}
            <div class="feature">
-            {% if personne.icon %}
-            <i class="fa fa-fw fa-{{ personne.icon }}"></i>
-            <div class="feature-content icon-offset">
-            {% else %}
-            <div class="feature-content">
-            {% endif %}
+
             {% if personne.nom %}
             <h5>{{ personne.nom }}</h5>
             {% endif %}
@@ -16,11 +11,13 @@
             <h6>{{ personne.fonction }}</h6>
             {% endif %}
             {% if personne.biographie %}
-            <p>{{ personne.biographie }}</p>
+            <p>{{ personne.biographie|markdown }}</p>
             {% endif %}
             {% if personne.portrait %}
-            <img src="/user/pages/01.home/04._gouvernance/{{personne.portrait}}" alt="portrait de {{personne.nom}}" />
+            <img src="{{page.media[personne.portrait].url|e }}" alt="portrait de {{personne.nom}}" />
+
             {% endif %}
+
             </div>
         </div>
     {% endfor %}

+ 4 - 3
user/themes/epau-antimatter/templates/modular/programmes.html.twig

@@ -3,17 +3,18 @@
 
     <div class="">
     {% for programme in page.header.programmes %}
+
         <div class="">
             <div class="">
             {% if programme.nom_du_programme %}
             <h4>{{ programme.nom_du_programme }}</h4>
             {% endif %}
             {% if programme.texte_de_presentation %}
-            <p>{{ programme.texte_de_presentation }}</p>
+            <p>{{ programme.texte_de_presentation|markdown }}</p>
             {% endif %}
             {% if programme.logo %}
-            <img src="/user/pages/01.home/02._programmes/{{programme.logo}} " alt="logo du programme {{programme.nom_du_programme}}" />
-            {{ dump(programme.logo) }}
+            <img src="{{page.media[programme.logo].url|e }}" alt="logo du programme {{programme.nom_du_programme}}" />
+
 
             {% endif %}
             </div>

+ 25 - 0
user/themes/epau-antimatter/templates/modular/rapport_dactivitees.html.twig

@@ -0,0 +1,25 @@
+<div class="modular-row {{ page.header.class}}">
+    {{ content|raw }}
+
+    <div class="">
+    {% for rapport in page.header.rapports %}
+
+        <div class="">
+            <div class="">
+            {% if rapport.titre_du_rapport %}
+            <h4>{{ rapport.titre_du_rapport }}</h4>
+            {% endif %}
+            {% if rapport.texte_de_presentation %}
+            <p>{{ rapport.texte_de_presentation|markdown }}</p>
+            {% endif %}
+            {% if rapport.couverture and rapport.pdf %}
+            <a href="{{page.media[rapport.pdf].url|e }}" {{rapport.titre_du_rapport}}>
+              <img src="{{page.media[rapport.couverture].url|e }}" alt="couverture du {{rapport.titre_du_rapport}}" />
+            </a>
+            {% endif %}
+            {{ dump(rapport)}}
+            </div>
+        </div>
+    {% endfor %}
+    </div>
+</div>