Kévin Tessier 7 years ago
parent
commit
f9d3d02e74
100 changed files with 3104 additions and 712 deletions
  1. 61 1
      CHANGELOG.md
  2. 1 1
      README.md
  3. 8 8
      composer.json
  4. 220 179
      composer.lock
  5. 2 2
      index.php
  6. 36 0
      system/blueprints/config/system.yaml
  7. 0 2
      system/blueprints/pages/default.yaml
  8. 1 0
      system/blueprints/user/account.yaml
  9. 5 1
      system/config/system.yaml
  10. 2 2
      system/defines.php
  11. 15 11
      system/src/Grav/Common/Config/Setup.php
  12. 10 12
      system/src/Grav/Common/Data/Validation.php
  13. 8 1
      system/src/Grav/Common/Debugger.php
  14. 0 3
      system/src/Grav/Common/File/CompiledFile.php
  15. 8 3
      system/src/Grav/Common/GPM/GPM.php
  16. 1 1
      system/src/Grav/Common/GPM/Licenses.php
  17. 3 7
      system/src/Grav/Common/Grav.php
  18. 4 3
      system/src/Grav/Common/Inflector.php
  19. 9 0
      system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php
  20. 29 0
      system/src/Grav/Common/Media/Interfaces/MediaInterface.php
  21. 9 0
      system/src/Grav/Common/Media/Interfaces/MediaObjectInterface.php
  22. 112 0
      system/src/Grav/Common/Media/Traits/MediaTrait.php
  23. 9 0
      system/src/Grav/Common/Page/Interfaces/PageInterface.php
  24. 5 3
      system/src/Grav/Common/Page/Media.php
  25. 20 11
      system/src/Grav/Common/Page/Medium/AbstractMedia.php
  26. 9 2
      system/src/Grav/Common/Page/Medium/ImageMedium.php
  27. 8 3
      system/src/Grav/Common/Page/Medium/Medium.php
  28. 34 0
      system/src/Grav/Common/Page/Medium/VideoMedium.php
  29. 62 52
      system/src/Grav/Common/Page/Page.php
  30. 46 24
      system/src/Grav/Common/Page/Pages.php
  31. 20 9
      system/src/Grav/Common/Processors/InitializeProcessor.php
  32. 13 1
      system/src/Grav/Common/Service/ConfigServiceProvider.php
  33. 17 20
      system/src/Grav/Common/Service/PageServiceProvider.php
  34. 27 20
      system/src/Grav/Common/Service/SessionServiceProvider.php
  35. 17 38
      system/src/Grav/Common/Session.php
  36. 1 1
      system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php
  37. 3 3
      system/src/Grav/Common/Twig/Node/TwigNodeScript.php
  38. 3 3
      system/src/Grav/Common/Twig/Node/TwigNodeStyle.php
  39. 12 10
      system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php
  40. 6 1
      system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php
  41. 1 1
      system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php
  42. 1 1
      system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php
  43. 30 43
      system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php
  44. 1 1
      system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php
  45. 10 7
      system/src/Grav/Common/Twig/Twig.php
  46. 81 28
      system/src/Grav/Common/Twig/TwigExtension.php
  47. 41 30
      system/src/Grav/Common/Uri.php
  48. 28 62
      system/src/Grav/Common/Utils.php
  49. 47 0
      system/src/Grav/Common/Yaml.php
  50. 25 13
      system/src/Grav/Console/Cli/InstallCommand.php
  51. 4 2
      system/src/Grav/Console/ConsoleTrait.php
  52. 9 6
      system/src/Grav/Console/Gpm/InstallCommand.php
  53. 5 2
      system/src/Grav/Console/Gpm/VersionCommand.php
  54. 18 9
      system/src/Grav/Framework/Cache/CacheTrait.php
  55. 0 10
      system/src/Grav/Framework/Collection/ArrayCollection.php
  56. 30 4
      system/src/Grav/Framework/ContentBlock/ContentBlock.php
  57. 11 0
      system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php
  58. 1 0
      system/src/Grav/Framework/ContentBlock/HtmlBlock.php
  59. 44 0
      system/src/Grav/Framework/File/Formatter/FormatterInterface.php
  60. 83 0
      system/src/Grav/Framework/File/Formatter/IniFormatter.php
  61. 78 0
      system/src/Grav/Framework/File/Formatter/JsonFormatter.php
  62. 116 0
      system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php
  63. 96 0
      system/src/Grav/Framework/File/Formatter/SerializeFormatter.php
  64. 103 0
      system/src/Grav/Framework/File/Formatter/YamlFormatter.php
  65. 1 11
      system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php
  66. 25 4
      system/src/Grav/Framework/Object/Base/ObjectTrait.php
  67. 198 0
      system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php
  68. 2 1
      system/src/Grav/Framework/Object/Interfaces/ObjectCollectionInterface.php
  69. 35 0
      system/src/Grav/Framework/Object/ObjectCollection.php
  70. 4 4
      system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php
  71. 4 5
      system/src/Grav/Framework/Route/Route.php
  72. 17 0
      system/src/Grav/Framework/Route/RouteFactory.php
  73. 340 0
      system/src/Grav/Framework/Session/Session.php
  74. 147 0
      system/src/Grav/Framework/Session/SessionInterface.php
  75. 18 0
      user/pages/01._recits/_12-juillet-2017/text.md
  76. 13 0
      user/pages/01._recits/_12-septembre-2017/text.md
  77. 10 0
      user/pages/01._recits/_13-septembre-2017/text.md
  78. 24 0
      user/pages/01._recits/_19-juin-2017/text.md
  79. 24 0
      user/pages/01._recits/_25-juillet-2017/text.md
  80. 12 0
      user/pages/01._recits/_26-juillet-2017/text.md
  81. 15 0
      user/pages/01._recits/_3-mai-2017/text.md
  82. 8 0
      user/pages/01._recits/_3-octobre-2017/text.md
  83. 15 0
      user/pages/01._recits/_4-octobre-2017/text.md
  84. 15 0
      user/pages/01._recits/_5-octobre-2017/text.md
  85. 11 0
      user/pages/01._recits/_5-septembre-2017/text.md
  86. 13 0
      user/pages/01._recits/_6-juillet-2017/text.md
  87. 12 0
      user/pages/01._recits/_6-juin-2017/text.md
  88. 46 24
      user/pages/02._interviews/_andrea/text.md
  89. 8 6
      user/pages/02._interviews/_manon-dumond/text.md
  90. 66 0
      user/pages/02._interviews/_marie-w/text.md
  91. 50 0
      user/pages/02._interviews/_martine/text.md
  92. 22 0
      user/pages/02._interviews/_nadia/text.md
  93. 60 0
      user/pages/02._interviews/_oceane-et-laurent/text.md
  94. 42 0
      user/pages/02._interviews/_olivier/text.md
  95. 43 0
      user/pages/02._interviews/_pascale/text.md
  96. 38 0
      user/pages/02._interviews/_solen/text.md
  97. 39 0
      user/pages/02._interviews/_victoria/text.md
  98. BIN
      user/pages/03._images/_rue-marie-w/DSC04024.JPG
  99. 8 0
      user/pages/03._images/_rue-marie-w/image.md
  100. BIN
      user/pages/03._images/_taxiphone/DSC04028.JPG

+ 61 - 1
CHANGELOG.md

@@ -1,3 +1,63 @@
+# v1.5.1
+## 08/23/2018
+
+1. [](#new)
+    * Added static `Grav\Common\Yaml` class which should be used instead of `Symfony\Component\Yaml\Yaml`
+1. [](#improved)
+    * Updated deprecated Twig code so it works in both in Twig 1.34+ and Twig 2.4+
+    * Switched to new Grav Yaml class to support Native + Fallback YAML libraries
+1. [](#bugfix)
+    * Broken handling of user folder in Grav URI object [#2151](https://github.com/getgrav/grav/issues/2151)
+
+# v1.5.0
+## 08/17/2018
+
+1. [](#new)
+    * Set minimum requirements to [PHP 5.6.4](https://getgrav.org/blog/raising-php-requirements-2018) 
+    * Updated Doctrine Collections to 1.4
+    * Updated Symfony Components to 3.4 (with compatibility mode to fall back to Symfony YAML 2.8)
+    * Added `Uri::method()` to get current HTTP method (GET/POST etc)
+    * `FormatterInterface`: Added `getSupportedFileExtensions()` and `getDefaultFileExtension()` methods
+    * Added option to disable `SimpleCache` key validation   
+    * Added support for multiple repo locations for `bin/grav install` command 
+    * Added twig filters for casting values: `|string`, `|int`, `|bool`, `|float`, `|array`
+    * Made `ObjectCollection::matching()` criteria expressions to behave more like in Twig
+    * Criteria: Added support for `LENGTH()`, `LOWER()`, `UPPER()`, `LTRIM()`, `RTRIM()` and `TRIM()`
+    * Added `Grav\Framework\File\Formatter` classes for encoding/decoding YAML, Markdown, JSON, INI and PHP serialized strings
+    * Added `Grav\Framework\Session` class to replace `RocketTheme\Toolbox\Session\Session`
+    * Added `Grav\Common\Media` interfaces and trait; use those in `Page` and `Media` classes 
+    * Added `Grav\Common\Page` interface to allow custom page types in the future
+    * Added setting to disable sessions from the site [#2013](https://github.com/getgrav/grav/issues/2013)
+    * Added new `strict_mode` settings in `system.yaml` for compatibility
+1. [](#improved)
+    * Improved `Utils::url()` to support query strings
+    * Display better exception message if Grav fails to initialize
+    * Added `muted` and `playsinline` support to videos [#2124](https://github.com/getgrav/grav/pull/2124)
+    * Added `MediaTrait::clearMediaCache()` to allow cache to be cleared
+    * Added `MediaTrait::getMediaCache()` to allow custom caching
+    * Improved session handling, allow all session configuration options in `system.session.options`
+1. [](#bugfix)
+    * Fix broken form nonce logic [#2121](https://github.com/getgrav/grav/pull/2121)
+    * Fixed issue with uppercase extensions and fallback media URLs [#2133](https://github.com/getgrav/grav/issues/2133)   
+    * Fixed theme inheritance issue with `camel-case` that includes numbers [#2134](https://github.com/getgrav/grav/issues/2134)
+    * Typo in demo typography page [#2136](https://github.com/getgrav/grav/pull/2136)
+    * Fix for incorrect plugin order in debugger panel
+    * Made `|markdown` filter HTML safe
+    * Fixed bug in `ContentBlock` serialization
+    * Fixed `Route::withQueryParam()` to accept array values
+    * Fixed typo in truncate function [#1943](https://github.com/getgrav/grav/issues/1943)
+    * Fixed blueprint field validation: Allow numeric inputs in text fields
+
+# v1.4.8
+## 07/31/2018
+
+1. [](#improved)
+    * Add Grav version to debug bar messages tab [#2106](https://github.com/getgrav/grav/pull/2106)
+    * Add Nginx config for ddev project to `webserver-configs` [#2117](https://github.com/getgrav/grav/pull/2117)
+    * Vendor library updates
+1. [](#bugfix)
+    * Don't allow `null` to be set as Page content
+
 # v1.4.7
 ## 07/13/2018
 
@@ -6,7 +66,7 @@
 1. [](#bugfix)
     * Fix for modular page preview [#2066](https://github.com/getgrav/grav/issues/2066)
     * `Page::routeCanonical()` should be string not array [#2069](https://github.com/getgrav/grav/issues/2069)
-
+    
 # v1.4.6
 ## 06/20/2018
 

+ 1 - 1
README.md

@@ -18,7 +18,7 @@ The underlying architecture of Grav is designed to use well-established and _bes
 
 # Requirements
 
-- PHP 5.5.9 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
+- PHP 5.6.4 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
 - Check the [Apache](https://learn.getgrav.org/basics/requirements#apache-requirements) or [IIS](https://learn.getgrav.org/basics/requirements#iis-requirements) requirements
 
 # QuickStart

+ 8 - 8
composer.json

@@ -6,17 +6,17 @@
     "homepage": "http://getgrav.org",
     "license": "MIT",
     "require": {
-        "php": ">=5.5.9",
+        "php": ">=5.6.4",
         "twig/twig": "~1.24",
         "erusev/parsedown": "1.6.4",
         "erusev/parsedown-extra": "~0.7",
-        "symfony/yaml": "~2.8",
-        "symfony/console": "~2.8",
-        "symfony/event-dispatcher": "~2.8",
-        "symfony/var-dumper": "~2.8",
+        "symfony/yaml": "~3.4",
+        "symfony/console": "~3.4",
+        "symfony/event-dispatcher": "~3.4",
+        "symfony/var-dumper": "~3.4",
         "symfony/polyfill-iconv": "~1.0",
         "doctrine/cache": "^1.6",
-        "doctrine/collections": "1.3",
+        "doctrine/collections": "^1.4",
         "psr/simple-cache": "^1.0",
         "psr/http-message": "^1.0",
         "guzzlehttp/psr7": "^1.4",
@@ -26,7 +26,7 @@
         "gregwar/image": "2.*",
         "donatj/phpuseragentparser": "~0.3",
         "pimple/pimple": "~3.2",
-        "rockettheme/toolbox": "~1.3.9",
+        "rockettheme/toolbox": "~1.4",
         "maximebf/debugbar": "~1.10",
         "ext-mbstring": "*",
         "ext-openssl": "*",
@@ -45,7 +45,7 @@
     },
     "config": {
         "platform": {
-            "php": "5.5.9"
+            "php": "5.6.4"
         }
     },
     "repositories": [

File diff suppressed because it is too large
+ 220 - 179
composer.lock


+ 2 - 2
index.php

@@ -7,7 +7,7 @@
  */
 
 namespace Grav;
-define('GRAV_PHP_MIN', '5.5.9');
+define('GRAV_PHP_MIN', '5.6.4');
 
 // Ensure vendor libraries exist
 $autoload = __DIR__ . '/vendor/autoload.php';
@@ -15,7 +15,7 @@ if (!is_file($autoload)) {
     die("Please run: <i>bin/grav install</i>");
 }
 
-if (PHP_SAPI == 'cli-server') {
+if (PHP_SAPI === 'cli-server') {
     if (!isset($_SERVER['PHP_CLI_ROUTER'])) {
        die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
     }

+ 36 - 0
system/blueprints/config/system.yaml

@@ -996,6 +996,18 @@ form:
                     validate:
                         type: bool
 
+                session.initialize:
+                    type: toggle
+                    label: PLUGIN_ADMIN.SESSION_INITIALIZE
+                    help: PLUGIN_ADMIN.SESSION_INITIALIZE_HELP
+                    highlight: 1
+                    options:
+                        1: PLUGIN_ADMIN.YES
+                        0: PLUGIN_ADMIN.NO
+                    default: true
+                    validate:
+                        type: bool
+
                 session.timeout:
                     type: text
                     size: small
@@ -1206,3 +1218,27 @@ form:
                     placeholder: "e.g. http://yoursite.com/yourpath"
                     label: PLUGIN_ADMIN.CUSTOM_BASE_URL
                     help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
+
+                strict_mode.yaml_compat:
+                    type: toggle
+                    label: PLUGIN_ADMIN.STRICT_YAML_COMPAT
+                    highlight: 1
+                    default: 1
+                    help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP
+                    options:
+                        1: PLUGIN_ADMIN.YES
+                        0: PLUGIN_ADMIN.NO
+                    validate:
+                        type: bool
+
+                strict_mode.twig_compat:
+                    type: toggle
+                    label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT
+                    highlight: 1
+                    default: 1
+                    help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP
+                    options:
+                        1: PLUGIN_ADMIN.YES
+                        0: PLUGIN_ADMIN.NO
+                    validate:
+                        type: bool

+ 0 - 2
system/blueprints/pages/default.yaml

@@ -39,7 +39,6 @@ form:
         options:
           type: tab
           title: PLUGIN_ADMIN.OPTIONS
-          multiple: true
 
           fields:
 
@@ -49,7 +48,6 @@ form:
               underline: true
 
               fields:
-    
                 header.published:
                   type: toggle
                   toggleable: true

+ 1 - 0
system/blueprints/user/account.yaml

@@ -94,6 +94,7 @@ form:
                     twofa_secret:
                         type: 2fa_secret
                         outerclasses: 'twofa-secret'
+                        markdown: true
                         label: PLUGIN_ADMIN.2FA_SECRET
                         sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP
 

+ 5 - 1
system/config/system.yaml

@@ -88,7 +88,7 @@ twig:
   cache: true                                    # Set to true to enable Twig caching
   debug: true                                    # Enable Twig debug
   auto_reload: true                              # Refresh cache on changes
-  autoescape: false                              # Autoescape Twig vars
+  autoescape: false                              # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
   undefined_functions: true                      # Allow undefined functions
   undefined_filters: true                        # Allow undefined filters
   umask_fix: false                               # By default Twig creates cached files as 755, fix switches this to 775
@@ -146,3 +146,7 @@ gpm:
   method: 'auto'                                 # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
   verify_peer: true                              # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
   official_gpm_only: true                        # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
+
+strict_mode:
+  yaml_compat: true                              # Grav 1.5+: Enables YAML backwards compatibility
+  twig_compat: true                              # Grav 1.5+: Enables deprecated Twig autoescape setting (autoescape: false)

+ 2 - 2
system/defines.php

@@ -8,12 +8,12 @@
 
 // Some standard defines
 define('GRAV', true);
-define('GRAV_VERSION', '1.4.7');
+define('GRAV_VERSION', '1.5.1');
 define('GRAV_TESTING', false);
 define('DS', '/');
 
 if (!defined('GRAV_PHP_MIN')) {
-    define('GRAV_PHP_MIN', '5.5.9');
+    define('GRAV_PHP_MIN', '5.6.4');
 }
 
 // Directories and Paths

+ 15 - 11
system/src/Grav/Common/Config/Setup.php

@@ -262,18 +262,22 @@ class Setup extends Data
             );
         }
 
-        if (!$locator->findResource('environment://config', true)) {
-            // If environment does not have its own directory, remove it from the lookup.
-            $this->set('streams.schemes.environment.prefixes', ['config' => []]);
-            $this->initializeLocator($locator);
-        }
+        try {
+            if (!$locator->findResource('environment://config', true)) {
+                // If environment does not have its own directory, remove it from the lookup.
+                $this->set('streams.schemes.environment.prefixes', ['config' => []]);
+                $this->initializeLocator($locator);
+            }
 
-        // Create security.yaml if it doesn't exist.
-        $filename = $locator->findResource('config://security.yaml', true, true);
-        $file = YamlFile::instance($filename);
-        if (!$file->exists()) {
-            $file->save(['salt' => Utils::generateRandomString(14)]);
-            $file->free();
+            // Create security.yaml if it doesn't exist.
+            $filename = $locator->findResource('config://security.yaml', true, true);
+            $file = YamlFile::instance($filename);
+            if (!$file->exists()) {
+                $file->save(['salt' => Utils::generateRandomString(14)]);
+                $file->free();
+            }
+        } catch (\RuntimeException $e) {
+            throw new \RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
         }
     }
 }

+ 10 - 12
system/src/Grav/Common/Data/Validation.php

@@ -10,9 +10,8 @@ namespace Grav\Common\Data;
 
 use Grav\Common\Grav;
 use Grav\Common\Utils;
-use Symfony\Component\Yaml\Exception\ParseException;
-use Symfony\Component\Yaml\Parser;
-use Symfony\Component\Yaml\Yaml;
+use Grav\Common\Yaml;
+use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYaml;
 
 class Validation
 {
@@ -107,7 +106,7 @@ class Validation
         $method = 'filter' . ucfirst(strtr($type, '-', '_'));
 
         // If this is a YAML field validate/filter as such
-        if ($type != 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
+        if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
             $method = 'filterYaml';
         }
 
@@ -128,10 +127,12 @@ class Validation
      */
     public static function typeText($value, array $params, array $field)
     {
-        if (!is_string($value)) {
+        if (!is_string($value) && !is_numeric($value)) {
             return false;
         }
 
+        $value = (string)$value;
+
         if (isset($params['min']) && strlen($value) < $params['min']) {
             return false;
         }
@@ -643,15 +644,12 @@ class Validation
 
     public static function filterYaml($value, $params)
     {
-        try {
-            if (is_string($value)) {
-                return (array) Yaml::parse($value);
-            } else {
-                return $value;
-            }
-        } catch (ParseException $e) {
+        if (!is_string($value)) {
             return $value;
         }
+
+        return (array) Yaml::parse($value);
+
     }
 
     /**

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

@@ -58,8 +58,15 @@ class Debugger
         $this->enabled = $this->config->get('system.debugger.enabled');
 
         if ($this->enabled()) {
+
+            $plugins_config = (array)$this->config->get('plugins');
+
+            ksort($plugins_config);
+
+
             $this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
-            $this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('plugins'), 'Plugins'));
+            $this->debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
+            $this->addMessage('Grav v' . GRAV_VERSION);
         }
 
         return $this;

+ 0 - 3
system/src/Grav/Common/File/CompiledFile.php

@@ -20,9 +20,6 @@ trait CompiledFile
      */
     public function content($var = null)
     {
-        // Set some options
-        $this->settings(['native' => true, 'compat' => true]);
-
         try {
             // If nothing has been loaded, attempt to get pre-compiled version of the file first.
             if ($var === null && $this->raw === null && $this->content === null) {

+ 8 - 3
system/src/Grav/Common/GPM/GPM.php

@@ -13,7 +13,7 @@ use Grav\Common\Filesystem\Folder;
 use Grav\Common\Inflector;
 use Grav\Common\Iterator;
 use Grav\Common\Utils;
-use Symfony\Component\Yaml\Yaml;
+use RocketTheme\Toolbox\File\YamlFile;
 
 class GPM extends Iterator
 {
@@ -624,7 +624,10 @@ class GPM extends Iterator
             return false;
         }
 
-        $blueprint = (array)Yaml::parse(file_get_contents($blueprint_file));
+        $file = YamlFile::instance($blueprint_file);
+        $blueprint = (array)$file->content();
+        $file->free();
+
         return $blueprint;
     }
 
@@ -873,7 +876,9 @@ class GPM extends Iterator
                 // get currently installed version
                 $locator = Grav::instance()['locator'];
                 $blueprints_path = $locator->findResource('plugins://' . $dependency_slug . DS . 'blueprints.yaml');
-                $package_yaml = Yaml::parse(file_get_contents($blueprints_path));
+                $file = YamlFile::instance($blueprints_path);
+                $package_yaml = $file->content();
+                $file->free();
                 $currentlyInstalledVersion = $package_yaml['version'];
 
                 // if requirement is next significant release, check is compatible with currently installed version, might not be

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

@@ -114,7 +114,7 @@ class Licenses
 
     {
         if (!isset(self::$file)) {
-            $path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';;
+            $path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';
             if (!file_exists($path)) {
                 touch($path);
             }

+ 3 - 7
system/src/Grav/Common/Grav.php

@@ -9,7 +9,6 @@
 namespace Grav\Common;
 
 use Grav\Common\Config\Config;
-use Grav\Common\Language\Language;
 use Grav\Common\Page\Medium\ImageMedium;
 use Grav\Common\Page\Medium\Medium;
 use Grav\Common\Page\Page;
@@ -205,11 +204,8 @@ class Grav extends Container
      */
     public function redirectLangSafe($route, $code = null)
     {
-        /** @var Language $language */
-        $language = $this['language'];
-
-        if (!$this['uri']->isExternal($route) && $language->enabled() && $language->isIncludeDefaultLanguage()) {
-            $this->redirect($language->getLanguage() . $route, $code);
+        if (!$this['uri']->isExternal($route)) {
+            $this->redirect($this['pages']->route($route), $code);
         } else {
             $this->redirect($route, $code);
         }
@@ -443,7 +439,7 @@ class Grav extends Container
         /** @var Config $config */
         $config = $this['config'];
 
-        $uri_extension = $uri->extension();
+        $uri_extension = strtolower($uri->extension());
         $fallback_types = $config->get('system.media.allowed_fallback_types', null);
         $supported_types = $config->get('media.types');
 

+ 4 - 3
system/src/Grav/Common/Inflector.php

@@ -190,10 +190,11 @@ class Inflector
     public function hyphenize($word)
     {
         $regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word);
-        $regex2 = preg_replace('/([a-zd])([A-Z])/', '\1-\2', $regex1);
-        $regex3 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex2);
+        $regex2 = preg_replace('/([a-z])([A-Z])/', '\1-\2', $regex1);
+        $regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2);
+        $regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3);
 
-        return strtolower($regex3);
+        return strtolower($regex4);
     }
 
     /**

+ 9 - 0
system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php

@@ -0,0 +1,9 @@
+<?php
+namespace Grav\Common\Media\Interfaces;
+
+/**
+ * Class implements media collection interface.
+ */
+interface MediaCollectionInterface
+{
+}

+ 29 - 0
system/src/Grav/Common/Media/Interfaces/MediaInterface.php

@@ -0,0 +1,29 @@
+<?php
+namespace Grav\Common\Media\Interfaces;
+
+/**
+ * Class implements media interface.
+ */
+interface MediaInterface
+{
+    /**
+     * Gets the associated media collection.
+     *
+     * @return MediaCollectionInterface  Collection of associated media.
+     */
+    public function getMedia();
+
+    /**
+     * Get filesystem path to the associated media.
+     *
+     * @return string|null  Media path or null if the object doesn't have media folder.
+     */
+    public function getMediaFolder();
+
+    /**
+     * Get display order for the associated media.
+     *
+     * @return array Empty array means default ordering.
+     */
+    public function getMediaOrder();
+}

+ 9 - 0
system/src/Grav/Common/Media/Interfaces/MediaObjectInterface.php

@@ -0,0 +1,9 @@
+<?php
+namespace Grav\Common\Media\Interfaces;
+
+/**
+ * Class implements media object interface.
+ */
+interface MediaObjectInterface
+{
+}

+ 112 - 0
system/src/Grav/Common/Media/Traits/MediaTrait.php

@@ -0,0 +1,112 @@
+<?php
+namespace Grav\Common\Media\Traits;
+
+use Grav\Common\Cache;
+use Grav\Common\Grav;
+use Grav\Common\Media\Interfaces\MediaCollectionInterface;
+use Grav\Common\Page\Media;
+use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
+
+trait MediaTrait
+{
+    protected $media;
+
+    /**
+     * Get filesystem path to the associated media.
+     *
+     * @return string|null
+     */
+    abstract public function getMediaFolder();
+
+    /**
+     * Get display order for the associated media.
+     *
+     * @return array Empty array means default ordering.
+     */
+    abstract public function getMediaOrder();
+
+    /**
+     * Get URI ot the associated media. Method will return null if path isn't URI.
+     *
+     * @return null|string
+     */
+    public function getMediaUri()
+    {
+       $folder = $this->getMediaFolder();
+
+       if (strpos($folder, '://')) {
+           return $folder;
+       }
+
+       /** @var UniformResourceLocator $locator */
+       $locator = Grav::instance()['locator'];
+       $user = $locator->findResource('user://');
+       if (strpos($folder, $user) === 0) {
+           return 'user://' . substr($folder, strlen($user)+1);
+       }
+
+       return null;
+    }
+
+    /**
+     * Gets the associated media collection.
+     *
+     * @return MediaCollectionInterface  Representation of associated media.
+     */
+    public function getMedia()
+    {
+        $cache = $this->getMediaCache();
+
+        if ($this->media === null) {
+            // Use cached media if possible.
+            $cacheKey = md5('media' . $this->getCacheKey());
+            if (!$media = $cache->fetch($cacheKey)) {
+                $media = new Media($this->getMediaFolder(), $this->getMediaOrder());
+                $cache->save($cacheKey, $media);
+            }
+            $this->media = $media;
+        }
+
+        return $this->media;
+    }
+
+    /**
+     * Sets the associated media collection.
+     *
+     * @param  MediaCollectionInterface  $media Representation of associated media.
+     * @return $this
+     */
+    protected function setMedia(MediaCollectionInterface $media)
+    {
+        $cache = $this->getMediaCache();
+        $cacheKey = md5('media' . $this->getCacheKey());
+        $cache->save($cacheKey, $media);
+
+        $this->media = $media;
+
+        return $this;
+    }
+
+    /**
+     * Clear media cache.
+     */
+    protected function clearMediaCache()
+    {
+        $cache = $this->getMediaCache();
+        $cacheKey = md5('media' . $this->getCacheKey());
+        $cache->delete($cacheKey);
+    }
+
+    /**
+     * @return Cache
+     */
+    protected function getMediaCache()
+    {
+        return Grav::instance()['cache'];
+    }
+
+    /**
+     * @return string
+     */
+    abstract protected function getCacheKey();
+}

+ 9 - 0
system/src/Grav/Common/Page/Interfaces/PageInterface.php

@@ -0,0 +1,9 @@
+<?php
+namespace Grav\Common\Page\Interfaces;
+
+/**
+ * Class implements page interface.
+ */
+interface PageInterface
+{
+}

+ 5 - 3
system/src/Grav/Common/Page/Media.php

@@ -9,11 +9,11 @@
 namespace Grav\Common\Page;
 
 use Grav\Common\Grav;
+use Grav\Common\Yaml;
 use Grav\Common\Page\Medium\AbstractMedia;
 use Grav\Common\Page\Medium\GlobalMedia;
 use Grav\Common\Page\Medium\MediumFactory;
 use RocketTheme\Toolbox\File\File;
-use Symfony\Component\Yaml\Yaml;
 
 class Media extends AbstractMedia
 {
@@ -24,11 +24,13 @@ class Media extends AbstractMedia
     protected $standard_exif = ['FileSize', 'MimeType', 'height', 'width'];
 
     /**
-     * @param $path
+     * @param string $path
+     * @param array  $media_order
      */
-    public function __construct($path)
+    public function __construct($path, array $media_order = null)
     {
         $this->path = $path;
+        $this->media_order = $media_order;
 
         $this->__wakeup();
         $this->init();

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

@@ -10,9 +10,11 @@ namespace Grav\Common\Page\Medium;
 
 use Grav\Common\Getters;
 use Grav\Common\Grav;
+use Grav\Common\Media\Interfaces\MediaCollectionInterface;
+use Grav\Common\Media\Interfaces\MediaObjectInterface;
 use Grav\Common\Utils;
 
-abstract class AbstractMedia extends Getters
+abstract class AbstractMedia extends Getters implements MediaCollectionInterface
 {
     protected $gettersVariable = 'instances';
 
@@ -21,6 +23,7 @@ abstract class AbstractMedia extends Getters
     protected $videos = [];
     protected $audios = [];
     protected $files = [];
+    protected $media_order;
 
     /**
      * Get medium by filename.
@@ -62,7 +65,7 @@ abstract class AbstractMedia extends Getters
     /**
      * Get a list of all media.
      *
-     * @return array|Medium[]
+     * @return array|MediaObjectInterface[]
      */
     public function all()
     {
@@ -74,7 +77,7 @@ abstract class AbstractMedia extends Getters
     /**
      * Get a list of all image media.
      *
-     * @return array|Medium[]
+     * @return array|MediaObjectInterface[]
      */
     public function images()
     {
@@ -85,7 +88,7 @@ abstract class AbstractMedia extends Getters
     /**
      * Get a list of all video media.
      *
-     * @return array|Medium[]
+     * @return array|MediaObjectInterface[]
      */
     public function videos()
     {
@@ -96,7 +99,7 @@ abstract class AbstractMedia extends Getters
     /**
      * Get a list of all audio media.
      *
-     * @return array|Medium[]
+     * @return array|MediaObjectInterface[]
      */
     public function audios()
     {
@@ -107,7 +110,7 @@ abstract class AbstractMedia extends Getters
     /**
      * Get a list of all file media.
      *
-     * @return array|Medium[]
+     * @return array|MediaObjectInterface[]
      */
     public function files()
     {
@@ -117,7 +120,7 @@ abstract class AbstractMedia extends Getters
 
     /**
      * @param string $name
-     * @param Medium $file
+     * @param MediaObjectInterface $file
      */
     protected function add($name, $file)
     {
@@ -145,14 +148,20 @@ abstract class AbstractMedia extends Getters
      */
     protected function orderMedia($media)
     {
-        $page = Grav::instance()['pages']->get($this->path);
+        if (null === $this->media_order) {
+            $page = Grav::instance()['pages']->get($this->path);
 
-        if ($page && isset($page->header()->media_order)) {
-            $media_order = array_map('trim', explode(',', $page->header()->media_order));
-            $media = Utils::sortArrayByArray($media, $media_order);
+            if ($page && isset($page->header()->media_order)) {
+                $this->media_order = array_map('trim', explode(',', $page->header()->media_order));
+            }
+        }
+
+        if (!empty($this->media_order) && is_array($this->media_order)) {
+            $media = Utils::sortArrayByArray($media, $this->media_order);
         } else {
             ksort($media, SORT_NATURAL | SORT_FLAG_CASE);
         }
+
         return $media;
     }
 

+ 9 - 2
system/src/Grav/Common/Page/Medium/ImageMedium.php

@@ -11,6 +11,7 @@ namespace Grav\Common\Page\Medium;
 use Grav\Common\Data\Blueprint;
 use Grav\Common\Grav;
 use Grav\Common\Utils;
+use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
 
 class ImageMedium extends Medium
 {
@@ -164,12 +165,18 @@ class ImageMedium extends Medium
      */
     public function url($reset = true)
     {
-        $image_path = Grav::instance()['locator']->findResource('cache://images', true);
-        $image_dir = Grav::instance()['locator']->findResource('cache://images', false);
+        /** @var UniformResourceLocator $locator */
+        $locator = Grav::instance()['locator'];
+        $image_path = $locator->findResource('cache://images', true);
+        $image_dir = $locator->findResource('cache://images', false);
         $saved_image_path = $this->saveImage();
 
         $output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
 
+        if ($locator->isStream($output)) {
+            $output = $locator->findResource($output, false);
+        }
+
         if (Utils::startsWith($output, $image_path)) {
             $output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
         }

+ 8 - 3
system/src/Grav/Common/Page/Medium/Medium.php

@@ -12,9 +12,9 @@ use Grav\Common\File\CompiledYamlFile;
 use Grav\Common\Grav;
 use Grav\Common\Data\Data;
 use Grav\Common\Data\Blueprint;
-use Grav\Common\Utils;
+use Grav\Common\Media\Interfaces\MediaObjectInterface;
 
-class Medium extends Data implements RenderableInterface
+class Medium extends Data implements RenderableInterface, MediaObjectInterface
 {
     use ParsedownHtmlTrait;
 
@@ -199,7 +199,12 @@ class Medium extends Data implements RenderableInterface
      */
     public function url($reset = true)
     {
-        $output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $this->get('filepath'));
+        $output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $this->get('filepath'));
+
+        $locator = Grav::instance()['locator'];
+        if ($locator->isStream($output)) {
+            $output = $locator->findResource($output, false);
+        }
 
         if ($reset) {
             $this->reset();

+ 34 - 0
system/src/Grav/Common/Page/Medium/VideoMedium.php

@@ -94,6 +94,40 @@ class VideoMedium extends Medium
         return $this;
     }
 
+    /**
+     * Allows to set the playsinline attribute
+     *
+     * @param bool $status
+     * @return $this
+     */
+    public function playsinline($status = false)
+    {
+        if($status) {
+            $this->attributes['playsinline'] = true;
+        } else {
+            unset($this->attributes['playsinline']);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Allows to set the muted attribute
+     *
+     * @param bool $status
+     * @return $this
+     */
+    public function muted($status = false)
+    {
+        if($status) {
+            $this->attributes['muted'] = true;
+        } else {
+            unset($this->attributes['muted']);
+        }
+
+        return $this;
+    }
+
     /**
      * Reset medium.
      *

+ 62 - 52
system/src/Grav/Common/Page/Page.php

@@ -12,23 +12,26 @@ use Exception;
 use Grav\Common\Cache;
 use Grav\Common\Config\Config;
 use Grav\Common\Data\Blueprint;
+use Grav\Common\File\CompiledYamlFile;
 use Grav\Common\Filesystem\Folder;
 use Grav\Common\Grav;
-use Grav\Common\Language\Language;
 use Grav\Common\Markdown\Parsedown;
 use Grav\Common\Markdown\ParsedownExtra;
+use Grav\Common\Page\Interfaces\PageInterface;
+use Grav\Common\Media\Traits\MediaTrait;
 use Grav\Common\Taxonomy;
 use Grav\Common\Uri;
 use Grav\Common\Utils;
+use Grav\Common\Yaml;
 use RocketTheme\Toolbox\Event\Event;
 use RocketTheme\Toolbox\File\MarkdownFile;
-use Symfony\Component\Yaml\Exception\ParseException;
-use Symfony\Component\Yaml\Yaml;
 
 define('PAGE_ORDER_PREFIX_REGEX', '/^[0-9]+\./u');
 
-class Page
+class Page implements PageInterface
 {
+    use MediaTrait;
+
     /**
      * @var string Filename. Leave as null if page is folder.
      */
@@ -65,7 +68,6 @@ class Page
     protected $summary;
     protected $raw_content;
     protected $pagination;
-    protected $media;
     protected $metadata;
     protected $title;
     protected $max_count;
@@ -318,8 +320,6 @@ class Page
         if (!$this->header) {
             $file = $this->file();
             if ($file) {
-                // Set some options
-                $file->settings(['native' => true, 'compat' => true]);
                 try {
                     $this->raw_content = $file->markdown();
                     $this->frontmatter = $file->frontmatter();
@@ -328,11 +328,12 @@ class Page
                     if (!Utils::isAdminPlugin()) {
                         // If there's a `frontmatter.yaml` file merge that in with the page header
                         // note page's own frontmatter has precedence and will overwrite any defaults
-                        $frontmatter_file = $this->path . '/' . $this->folder . '/frontmatter.yaml';
-                        if (file_exists($frontmatter_file)) {
-                            $frontmatter_data = (array)Yaml::parse(file_get_contents($frontmatter_file));
+                        $frontmatterFile = CompiledYamlFile::instance($this->path . '/' . $this->folder . '/frontmatter.yaml');
+                        if ($frontmatterFile->exists()) {
+                            $frontmatter_data = (array)$frontmatterFile->content();
                             $this->header = (object)array_replace_recursive($frontmatter_data,
                                 (array)$this->header);
+                            $frontmatterFile->free();
                         }
                         // Process frontmatter with Twig if enabled
                         if (Grav::instance()['config']->get('system.pages.frontmatter.process_twig') === true) {
@@ -813,6 +814,8 @@ class Page
      */
     public function setRawContent($content)
     {
+        $content = $content === null ? '': $content;
+
         $this->content = $content;
     }
 
@@ -1122,6 +1125,14 @@ class Page
         return json_encode($this->toArray());
     }
 
+    /**
+     * @return string
+     */
+    protected function getCacheKey()
+    {
+        return $this->id();
+    }
+
     /**
      * Gets and sets the associated media as found in the page folder.
      *
@@ -1131,23 +1142,33 @@ class Page
      */
     public function media($var = null)
     {
-        /** @var Cache $cache */
-        $cache = Grav::instance()['cache'];
-
         if ($var) {
-            $this->media = $var;
-        }
-        if ($this->media === null) {
-            // Use cached media if possible.
-            $media_cache_id = md5('media' . $this->id());
-            if (!$media = $cache->fetch($media_cache_id)) {
-                $media = new Media($this->path());
-                $cache->save($media_cache_id, $media);
-            }
-            $this->media = $media;
+            $this->setMedia($var);
         }
 
-        return $this->media;
+        return $this->getMedia();
+    }
+
+    /**
+     * Get filesystem path to the associated media.
+     *
+     * @return string|null
+     */
+    public function getMediaFolder()
+    {
+        return $this->path();
+    }
+
+    /**
+     * Get display order for the associated media.
+     *
+     * @return array Empty array means default ordering.
+     */
+    public function getMediaOrder()
+    {
+        $header = $this->header();
+
+        return isset($header->media_order) ? array_map('trim', explode(',', (string)$header->media_order)) : [];
     }
 
     /**
@@ -1626,14 +1647,19 @@ class Page
      * Gets the url for the Page.
      *
      * @param bool $include_host Defaults false, but true would include http://yourhost.com
-     * @param bool $canonical true to return the canonical URL
-     * @param bool $include_lang
+     * @param bool $canonical    True to return the canonical URL
+     * @param bool $include_base Include base url on multisite as well as language code
      * @param bool $raw_route
      *
      * @return string The url.
      */
-    public function url($include_host = false, $canonical = false, $include_lang = true, $raw_route = false)
+    public function url($include_host = false, $canonical = false, $include_base = true, $raw_route = false)
     {
+        // Override any URL when external_url is set
+        if (isset($this->external_url)) {
+            return $this->external_url;
+        }
+
         $grav = Grav::instance();
 
         /** @var Pages $pages */
@@ -1642,41 +1668,25 @@ class Page
         /** @var Config $config */
         $config = $grav['config'];
 
-        /** @var Language $language */
-        $language = $grav['language'];
-
-        /** @var Uri $uri */
-        $uri = $grav['uri'];
-
-        // Override any URL when external_url is set
-        if (isset($this->external_url)) {
-            return $this->external_url;
-        }
-
-        // get pre-route
-        if ($include_lang && $language->enabled()) {
-            $pre_route = $language->getLanguageURLPrefix();
-        } else {
-            $pre_route = '';
-        }
+        // get base route (multisite base and language)
+        $route = $include_base ? $pages->baseRoute() : '';
 
         // add full route if configured to do so
-        if ($config->get('system.absolute_urls', false)) {
+        if (!$include_host && $config->get('system.absolute_urls', false)) {
             $include_host = true;
         }
 
-        // get canonical route if requested
         if ($canonical) {
-            $route = $pre_route . $this->routeCanonical();
+            $route .= $this->routeCanonical();
         } elseif ($raw_route) {
-            $route = $pre_route . $this->rawRoute();
+            $route .= $this->rawRoute();
         } else {
-            $route = $pre_route . $this->route();
+            $route .= $this->route();
         }
 
-        $rootUrl = $uri->rootUrl($include_host) . $pages->base();
-
-        $url = $rootUrl . '/' . trim($route, '/') . $this->urlExtension();
+        /** @var Uri $uri */
+        $uri = $grav['uri'];
+        $url = $uri->rootUrl($include_host) . '/' . trim($route, '/') . $this->urlExtension();
 
         // trim trailing / if not root
         if ($url !== '/') {

+ 46 - 24
system/src/Grav/Common/Page/Pages.php

@@ -49,7 +49,7 @@ class Pages
     /**
      * @var array|string[]
      */
-    protected $baseUrl = [];
+    protected $baseRoute = [];
 
     /**
      * @var array|string[]
@@ -120,7 +120,7 @@ class Pages
         if ($path !== null) {
             $path = trim($path, '/');
             $this->base = $path ? '/' . $path : null;
-            $this->baseUrl = [];
+            $this->baseRoute = [];
         }
 
         return $this->base;
@@ -128,39 +128,61 @@ class Pages
 
     /**
      *
-     * Get base URL for Grav pages.
+     * Get base route for Grav pages.
      *
-     * @param  string $lang     Optional language code for multilingual links.
-     * @param  bool   $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
+     * @param  string $lang     Optional language code for multilingual routes.
      *
      * @return string
      */
-    public function baseUrl($lang = null, $absolute = null)
+    public function baseRoute($lang = null)
     {
-        $lang = (string) $lang;
-        $type = $absolute === null ? 'base_url' : ($absolute ? 'base_url_absolute' : 'base_url_relative');
-        $key = "{$lang} {$type}";
-
-        if (!isset($this->baseUrl[$key])) {
-            /** @var Config $config */
-            $config = $this->grav['config'];
+        $key = $lang ?: 'default';
 
+        if (!isset($this->baseRoute[$key])) {
             /** @var Language $language */
             $language = $this->grav['language'];
 
-            if (!$lang) {
-                $lang = $language->getActive();
-            }
+            $path_base = rtrim($this->base(), '/');
+            $path_lang = $language->enabled() ? $language->getLanguageURLPrefix($lang) : '';
 
-            $path_append = rtrim($this->grav['pages']->base(), '/');
-            if ($language->getDefault() !== $lang || $config->get('system.languages.include_default_lang') === true) {
-                $path_append .= $lang ? '/' . $lang : '';
-            }
+            $this->baseRoute[$key] = $path_base . $path_lang;
+        }
+
+        return $this->baseRoute[$key];
+    }
 
-            $this->baseUrl[$key] = $this->grav[$type] . $path_append;
+    /**
+     *
+     * Get route for Grav site.
+     *
+     * @param  string $route    Optional route to the page.
+     * @param  string $lang     Optional language code for multilingual links.
+     *
+     * @return string
+     */
+    public function route($route = '/', $lang = null)
+    {
+        if (!$route || $route === '/') {
+            return $this->baseRoute($lang) ?: '/';
         }
 
-        return $this->baseUrl[$key];
+        return $this->baseRoute($lang) . $route;
+    }
+
+    /**
+     *
+     * Get base URL for Grav pages.
+     *
+     * @param  string     $lang     Optional language code for multilingual links.
+     * @param  bool|null  $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
+     *
+     * @return string
+     */
+    public function baseUrl($lang = null, $absolute = null)
+    {
+        $type = $absolute === null ? 'base_url' : ($absolute ? 'base_url_absolute' : 'base_url_relative');
+
+        return $this->grav[$type] . $this->baseRoute($lang);
     }
 
     /**
@@ -179,7 +201,7 @@ class Pages
 
     /**
      *
-     * Get home URL for Grav site.
+     * Get URL for Grav site.
      *
      * @param  string $route    Optional route to the page.
      * @param  string $lang     Optional language code for multilingual links.
@@ -189,7 +211,7 @@ class Pages
      */
     public function url($route = '/', $lang = null, $absolute = null)
     {
-        if ($route === '/') {
+        if (!$route || $route === '/') {
             return $this->homeUrl($lang, $absolute);
         }
 

+ 20 - 9
system/src/Grav/Common/Processors/InitializeProcessor.php

@@ -8,6 +8,10 @@
 
 namespace Grav\Common\Processors;
 
+use Grav\Common\Config\Config;
+use Grav\Common\Uri;
+use Grav\Common\Utils;
+
 class InitializeProcessor extends ProcessorBase implements ProcessorInterface
 {
     public $id = 'init';
@@ -15,29 +19,36 @@ class InitializeProcessor extends ProcessorBase implements ProcessorInterface
 
     public function process()
     {
-        $this->container['config']->debug();
+        /** @var Config $config */
+        $config = $this->container['config'];
+        $config->debug();
 
         // Use output buffering to prevent headers from being sent too early.
         ob_start();
-        if ($this->container['config']->get('system.cache.gzip')) {
+        if ($config->get('system.cache.gzip') && !@ob_start('ob_gzhandler')) {
             // Enable zip/deflate with a fallback in case of if browser does not support compressing.
-            if (!@ob_start("ob_gzhandler")) {
-                ob_start();
-            }
+            ob_start();
         }
 
         // Initialize the timezone.
-        if ($this->container['config']->get('system.timezone')) {
+        if ($config->get('system.timezone')) {
             date_default_timezone_set($this->container['config']->get('system.timezone'));
         }
 
         // FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
-        if ($this->container['config']->get('system.session.initialize', 1) && isset($this->container['session'])) {
+        if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
             $this->container['session']->init();
         }
 
-        // Initialize uri.
-        $this->container['uri']->init();
+        /** @var Uri $uri */
+        $uri = $this->container['uri'];
+        $uri->init();
+
+        // Redirect pages with trailing slash if configured to do so.
+        $path = $uri->path() ?: '/';
+        if ($path !== '/' && $config->get('system.pages.redirect_trailing_slash', false) && Utils::endsWith($path, '/')) {
+            $this->container->redirect(rtrim($path, '/'), 302);
+        }
 
         $this->container->setLocale();
     }

+ 13 - 1
system/src/Grav/Common/Service/ConfigServiceProvider.php

@@ -16,6 +16,7 @@ use Grav\Common\Config\ConfigFileFinder;
 use Grav\Common\Config\Setup;
 use Pimple\Container;
 use Pimple\ServiceProviderInterface;
+use RocketTheme\Toolbox\File\YamlFile;
 use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
 
 class ConfigServiceProvider implements ServiceProviderInterface
@@ -31,7 +32,14 @@ class ConfigServiceProvider implements ServiceProviderInterface
         };
 
         $container['config'] = function ($c) {
-            return static::load($c);
+            $config = static::load($c);
+
+            // After configuration has been loaded, we can disable YAML compatibility if strict mode has been enabled.
+            if (!$config->get('system.strict_mode.yaml_compat', true)) {
+                YamlFile::globalSettings(['compat' => false, 'native' => true]);
+            }
+
+            return $config;
         };
 
         $container['languages'] = function ($c) {
@@ -65,6 +73,10 @@ class ConfigServiceProvider implements ServiceProviderInterface
         return $blueprints->name("master-{$setup->environment}")->load();
     }
 
+    /**
+     * @param Container $container
+     * @return Config
+     */
     public static function load(Container $container)
     {
         /** Setup $setup */

+ 17 - 20
system/src/Grav/Common/Service/PageServiceProvider.php

@@ -8,6 +8,7 @@
 
 namespace Grav\Common\Service;
 
+use Grav\Common\Config\Config;
 use Grav\Common\Grav;
 use Grav\Common\Language\Language;
 use Grav\Common\Page\Page;
@@ -26,35 +27,33 @@ class PageServiceProvider implements ServiceProviderInterface
             /** @var Pages $pages */
             $pages = $c['pages'];
 
+            /** @var Config $config */
+            $config = $c['config'];
+
             /** @var Uri $uri */
             $uri = $c['uri'];
 
-            $path = $uri->path(); // Don't trim to support trailing slash default routes
-            $path = $path ?: '/';
-
+            $path = $uri->path() ?: '/'; // Don't trim to support trailing slash default routes
             $page = $pages->dispatch($path);
 
             // Redirection tests
             if ($page) {
-                /** @var Language $language */
-                $language = $c['language'];
-
                 // some debugger override logic
                 if ($page->debugger() === false) {
                     $c['debugger']->enabled(false);
                 }
 
-                if ($c['config']->get('system.force_ssl')) {
-                    if (!isset($_SERVER['HTTPS']) || $_SERVER["HTTPS"] != "on") {
-                        $url = "https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
+                if ($config->get('system.force_ssl')) {
+                    if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
+                        $url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
                         $c->redirect($url);
                     }
                 }
 
-                $url = $page->route();
+                $url = $pages->route($page->route());
 
                 if ($uri->params()) {
-                    if ($url == '/') { //Avoid double slash
+                    if ($url === '/') { //Avoid double slash
                         $url = $uri->params();
                     } else {
                         $url .= $uri->params();
@@ -67,18 +66,16 @@ class PageServiceProvider implements ServiceProviderInterface
                     $url .= '#' . $uri->fragment();
                 }
 
+                /** @var Language $language */
+                $language = $c['language'];
+
                 // Language-specific redirection scenarios
-                if ($language->enabled()) {
-                    if ($language->isLanguageInUrl() && !$language->isIncludeDefaultLanguage()) {
-                        $c->redirect($url);
-                    }
-                    if (!$language->isLanguageInUrl() && $language->isIncludeDefaultLanguage()) {
-                        $c->redirectLangSafe($url);
-                    }
+                if ($language->enabled() && ($language->isLanguageInUrl() xor $language->isIncludeDefaultLanguage())) {
+                    $c->redirect($url);
                 }
                 // Default route test and redirect
-                if ($c['config']->get('system.pages.redirect_default_route') && $page->route() != $path) {
-                    $c->redirectLangSafe($url);
+                if ($config->get('system.pages.redirect_default_route') && $page->route() !== $path) {
+                    $c->redirect($url);
                 }
             }
 

+ 27 - 20
system/src/Grav/Common/Service/SessionServiceProvider.php

@@ -29,21 +29,22 @@ class SessionServiceProvider implements ServiceProviderInterface
             /** @var Uri $uri */
             $uri = $c['uri'];
 
-            // Get session parameters.
-            $session_timeout = (int)$config->get('system.session.timeout', 1800);
-            $session_path = $config->get('system.session.path');
-            if (null === $session_path) {
-                $session_path = '/' . ltrim(Uri::filterPath($uri->rootUrl(false)), '/');
-            }
-            $domain = $uri->host();
-            if ($domain === 'localhost') {
-                $domain = '';
-            }
-
             // Get session options.
-            $secure = (bool)$config->get('system.session.secure', false);
-            $httponly = (bool)$config->get('system.session.httponly', true);
             $enabled = (bool)$config->get('system.session.enabled', false);
+            $cookie_secure = (bool)$config->get('system.session.secure', false);
+            $cookie_httponly = (bool)$config->get('system.session.httponly', true);
+            $cookie_lifetime = (int)$config->get('system.session.timeout', 1800);
+            $cookie_path = $config->get('system.session.path');
+            if (null === $cookie_path) {
+                $cookie_path = '/' . trim(Uri::filterPath($uri->rootUrl(false)), '/');
+            }
+            // Session cookie path requires trailing slash.
+            $cookie_path = rtrim($cookie_path, '/') . '/';
+
+            $cookie_domain = $uri->host();
+            if ($cookie_domain === 'localhost') {
+                $cookie_domain = '';
+            }
 
             // Activate admin if we're inside the admin path.
             $is_admin = false;
@@ -56,14 +57,14 @@ class SessionServiceProvider implements ServiceProviderInterface
                 // Check no language, simple language prefix (en) and region specific language prefix (en-US).
                 $pos = strpos($current_route, $base);
                 if ($pos === 0 || $pos === 3 || $pos === 6) {
-                    $session_timeout = $config->get('plugins.admin.session.timeout', 1800);
+                    $cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800);
                     $enabled = $is_admin = true;
                 }
             }
 
             // Fix for HUGE session timeouts.
-            if ($session_timeout > 99999999999) {
-                $session_timeout = 9999999999;
+            if ($cookie_lifetime > 99999999999) {
+                $cookie_lifetime = 9999999999;
             }
 
             $inflector = new Inflector();
@@ -73,10 +74,16 @@ class SessionServiceProvider implements ServiceProviderInterface
             }
 
             // Define session service.
-            $session = new Session($session_timeout, $session_path, $domain);
-            $session->setName($session_name);
-            $session->setSecure($secure);
-            $session->setHttpOnly($httponly);
+            $options = [
+                'name' => $session_name,
+                'cookie_lifetime' => $cookie_lifetime,
+                'cookie_path' => $cookie_path,
+                'cookie_domain' => $cookie_domain,
+                'cookie_secure' => $cookie_secure,
+                'cookie_httponly' => $cookie_httponly
+            ] + (array) $config->get('system.session.options');
+
+            $session = new Session($options);
             $session->setAutoStart($enabled);
 
             return $session;

+ 17 - 38
system/src/Grav/Common/Session.php

@@ -8,34 +8,18 @@
 
 namespace Grav\Common;
 
-use RocketTheme\Toolbox\Session\Session as BaseSession;
-
-class Session extends BaseSession
+class Session extends \Grav\Framework\Session\Session
 {
     /** @var bool */
     protected $autoStart = false;
 
-    protected $lifetime;
-    protected $path;
-    protected $domain;
-    protected $secure;
-    protected $httpOnly;
-
     /**
-     * @param int    $lifetime Defaults to 1800 seconds.
-     * @param string $path     Cookie path.
-     * @param string $domain   Optional, domain for the session
-     * @throws \RuntimeException
+     * @return \Grav\Framework\Session\Session
+     * @deprecated 1.5
      */
-    public function __construct($lifetime, $path, $domain = null)
+    public static function instance()
     {
-        $this->lifetime = $lifetime;
-        $this->path = $path;
-        $this->domain = $domain;
-
-        if (php_sapi_name() !== 'cli') {
-            parent::__construct($lifetime, $path, $domain);
-        }
+        return static::getInstance();
     }
 
     /**
@@ -48,9 +32,6 @@ class Session extends BaseSession
         if ($this->autoStart) {
             $this->start();
 
-            // TODO: This setcookie shouldn't be here, session should by itself be able to update its cookie.
-            setcookie(session_name(), session_id(), $this->lifetime ? time() + $this->lifetime : 0, $this->path, $this->domain, $this->secure, $this->httpOnly);
-
             $this->autoStart = false;
         }
     }
@@ -67,27 +48,25 @@ class Session extends BaseSession
     }
 
     /**
-     * @param bool $secure
-     * @return $this
+     * Returns attributes.
+     *
+     * @return array Attributes
+     * @deprecated 1.5
      */
-    public function setSecure($secure)
+    public function all()
     {
-        $this->secure = $secure;
-        ini_set('session.cookie_secure', (bool)$secure);
-
-        return $this;
+        return $this->getAll();
     }
 
     /**
-     * @param bool $httpOnly
-     * @return $this
+     * Checks if the session was started.
+     *
+     * @return Boolean
+     * @deprecated 1.5
      */
-    public function setHttpOnly($httpOnly)
+    public function started()
     {
-        $this->httpOnly = $httpOnly;
-        ini_set('session.cookie_httponly', (bool)$httpOnly);
-
-        return $this;
+        return $this->isStarted();
     }
 
     /**

+ 1 - 1
system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php

@@ -12,7 +12,7 @@ class TwigNodeMarkdown extends \Twig_Node implements \Twig_NodeOutputInterface
 {
     public function __construct(\Twig_Node $body, $lineno, $tag = 'markdown')
     {
-        parent::__construct(array('body' => $body), array(), $lineno, $tag);
+        parent::__construct(['body' => $body], [], $lineno, $tag);
     }
     /**
      * Compiles the node to PHP.

+ 3 - 3
system/src/Grav/Common/Twig/Node/TwigNodeScript.php

@@ -14,7 +14,7 @@ class TwigNodeScript extends \Twig_Node implements \Twig_NodeOutputInterface
 
     /**
      * TwigNodeScript constructor.
-     * @param \Twig_NodeInterface|null $body
+     * @param \Twig_Node|null $body
      * @param \Twig_Node_Expression|null $file
      * @param \Twig_Node_Expression|null $group
      * @param \Twig_Node_Expression|null $priority
@@ -23,12 +23,12 @@ class TwigNodeScript extends \Twig_Node implements \Twig_NodeOutputInterface
      * @param string|null $tag
      */
     public function __construct(
-        \Twig_NodeInterface $body = null,
+        \Twig_Node $body = null,
         \Twig_Node_Expression $file = null,
         \Twig_Node_Expression $group = null,
         \Twig_Node_Expression $priority = null,
         \Twig_Node_Expression $attributes = null,
-        $lineno,
+        $lineno = 0,
         $tag = null
     )
     {

+ 3 - 3
system/src/Grav/Common/Twig/Node/TwigNodeStyle.php

@@ -14,18 +14,18 @@ class TwigNodeStyle extends \Twig_Node implements \Twig_NodeOutputInterface
 
     /**
      * TwigNodeAssets constructor.
-     * @param \Twig_NodeInterface|null $body
+     * @param \Twig_Node|null $body
      * @param \Twig_Node_Expression|null $attributes
      * @param int $lineno
      * @param null $tag
      */
     public function __construct(
-        \Twig_NodeInterface $body = null,
+        \Twig_Node $body = null,
         \Twig_Node_Expression $file = null,
         \Twig_Node_Expression $group = null,
         \Twig_Node_Expression $priority = null,
         \Twig_Node_Expression $attributes = null,
-        $lineno,
+        $lineno = 0,
         $tag = null
     )
     {

+ 12 - 10
system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php

@@ -10,7 +10,13 @@ namespace Grav\Common\Twig\Node;
 
 class TwigNodeSwitch extends \Twig_Node implements \Twig_NodeOutputInterface
 {
-    public function __construct(\Twig_NodeInterface $value, \Twig_NodeInterface $cases, \Twig_NodeInterface $default = null, $lineno, $tag = null)
+    public function __construct(
+        \Twig_Node $value,
+        \Twig_Node $cases,
+        \Twig_Node $default = null,
+        $lineno = 0,
+        $tag = null
+    )
     {
         parent::__construct(array('value' => $value, 'cases' => $cases, 'default' => $default), array(), $lineno, $tag);
     }
@@ -24,20 +30,17 @@ class TwigNodeSwitch extends \Twig_Node implements \Twig_NodeOutputInterface
     {
         $compiler
             ->addDebugInfo($this)
-            ->write("switch (")
+            ->write('switch (')
             ->subcompile($this->getNode('value'))
             ->raw(") {\n")
             ->indent();
 
-        foreach ($this->getNode('cases') as $case)
-        {
-            if (!$case->hasNode('body'))
-            {
+        foreach ($this->getNode('cases') as $case) {
+            if (!$case->hasNode('body')) {
                 continue;
             }
 
-            foreach ($case->getNode('values') as $value)
-            {
+            foreach ($case->getNode('values') as $value) {
                 $compiler
                     ->write('case ')
                     ->subcompile($value)
@@ -53,8 +56,7 @@ class TwigNodeSwitch extends \Twig_Node implements \Twig_NodeOutputInterface
                 ->write("}\n");
         }
 
-        if ($this->hasNode('default') && $this->getNode('default') !== null)
-        {
+        if ($this->hasNode('default') && $this->getNode('default') !== null) {
             $compiler
                 ->write("default:\n")
                 ->write("{\n")

+ 6 - 1
system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php

@@ -10,7 +10,12 @@ namespace Grav\Common\Twig\Node;
 
 class TwigNodeTryCatch extends \Twig_Node
 {
-    public function __construct(\Twig_NodeInterface $try, \Twig_NodeInterface $catch = null, $lineno, $tag = null)
+    public function __construct(
+        \Twig_Node $try,
+        \Twig_Node $catch = null,
+        $lineno = 0,
+        $tag = null
+    )
     {
         parent::__construct(array('try' => $try, 'catch' => $catch), array(), $lineno, $tag);
     }

+ 1 - 1
system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php

@@ -27,7 +27,7 @@ class TwigTokenParserScript extends \Twig_TokenParser
      *
      * @param \Twig_Token $token A Twig_Token instance
      *
-     * @return \Twig_NodeInterface A Twig_NodeInterface instance
+     * @return \Twig_Node A Twig_Node instance
      */
     public function parse(\Twig_Token $token)
     {

+ 1 - 1
system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php

@@ -26,7 +26,7 @@ class TwigTokenParserStyle extends \Twig_TokenParser
      *
      * @param \Twig_Token $token A Twig_Token instance
      *
-     * @return \Twig_NodeInterface A Twig_NodeInterface instance
+     * @return \Twig_Node A Twig_Node instance
      */
     public function parse(\Twig_Token $token)
     {

+ 30 - 43
system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php

@@ -37,8 +37,7 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
         $stream->expect(\Twig_Token::BLOCK_END_TYPE);
 
         // There can be some whitespace between the {% switch %} and first {% case %} tag.
-        while ($stream->getCurrent()->getType() == \Twig_Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) == '')
-        {
+        while ($stream->getCurrent()->getType() === \Twig_Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) === '') {
             $stream->next();
         }
 
@@ -47,56 +46,45 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
         $expressionParser = $this->parser->getExpressionParser();
 
         $default = null;
-        $cases = array();
+        $cases = [];
         $end = false;
 
-        while (!$end)
-        {
+        while (!$end) {
             $next = $stream->next();
 
-            switch ($next->getValue())
-            {
+            switch ($next->getValue()) {
                 case 'case':
-                    {
-                        $values = array();
-
-                        while (true)
-                        {
-                            $values[] = $expressionParser->parsePrimaryExpression();
-                            // Multiple allowed values?
-                            if ($stream->test(\Twig_Token::OPERATOR_TYPE, 'or'))
-                            {
-                                $stream->next();
-                            }
-                            else
-                            {
-                                break;
-                            }
+                    $values = [];
+
+                    while (true) {
+                        $values[] = $expressionParser->parsePrimaryExpression();
+                        // Multiple allowed values?
+                        if ($stream->test(\Twig_Token::OPERATOR_TYPE, 'or')) {
+                            $stream->next();
+                        } else {
+                            break;
                         }
-
-                        $stream->expect(\Twig_Token::BLOCK_END_TYPE);
-                        $body = $this->parser->subparse(array($this, 'decideIfFork'));
-                        $cases[] = new \Twig_Node(array(
-                            'values' => new \Twig_Node($values),
-                            'body' => $body
-                        ));
-                        break;
                     }
+
+                    $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+                    $body = $this->parser->subparse(array($this, 'decideIfFork'));
+                    $cases[] = new \Twig_Node([
+                        'values' => new \Twig_Node($values),
+                        'body' => $body
+                    ]);
+                    break;
+
                 case 'default':
-                    {
-                        $stream->expect(\Twig_Token::BLOCK_END_TYPE);
-                        $default = $this->parser->subparse(array($this, 'decideIfEnd'));
-                        break;
-                    }
+                    $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+                    $default = $this->parser->subparse(array($this, 'decideIfEnd'));
+                    break;
+
                 case 'endswitch':
-                    {
-                        $end = true;
-                        break;
-                    }
+                    $end = true;
+                    break;
+
                 default:
-                    {
-                        throw new \Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
-                    }
+                    throw new \Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
             }
         }
 
@@ -127,7 +115,6 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
         return $token->test(array('endswitch'));
     }
 
-
     /**
      * {@inheritdoc}
      */

+ 1 - 1
system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php

@@ -28,7 +28,7 @@ class TwigTokenParserTryCatch extends \Twig_TokenParser
      *
      * @param \Twig_Token $token A Twig_Token instance
      *
-     * @return \Twig_NodeInterface A Twig_NodeInterface instance
+     * @return \Twig_Node A Twig_Node instance
      */
     public function parse(\Twig_Token $token)
     {

+ 10 - 7
system/src/Grav/Common/Twig/Twig.php

@@ -113,7 +113,10 @@ class Twig
                 $params['cache'] = new \Twig_Cache_Filesystem($cachePath, \Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION);
             }
 
-            if (!empty($this->autoescape)) {
+            if (!$config->get('system.strict_mode.twig_compat', true)) {
+                // Force autoescape on for all files if in strict mode.
+                $params['autoescape'] = true;
+            } elseif (!empty($this->autoescape)) {
                 $params['autoescape'] = $this->autoescape;
             }
 
@@ -122,10 +125,10 @@ class Twig
             if ($config->get('system.twig.undefined_functions')) {
                 $this->twig->registerUndefinedFunctionCallback(function ($name) {
                     if (function_exists($name)) {
-                        return new \Twig_Function_Function($name);
+                        return new \Twig_SimpleFunction($name, $name);
                     }
 
-                    return new \Twig_Function_Function(function () {
+                    return new \Twig_SimpleFunction($name, function () {
                     });
                 });
             }
@@ -133,10 +136,10 @@ class Twig
             if ($config->get('system.twig.undefined_filters')) {
                 $this->twig->registerUndefinedFilterCallback(function ($name) {
                     if (function_exists($name)) {
-                        return new \Twig_Filter_Function($name);
+                        return new \Twig_SimpleFilter($name, $name);
                     }
 
-                    return new \Twig_Filter_Function(function () {
+                    return new \Twig_SimpleFilter($name, function () {
                     });
                 });
             }
@@ -145,7 +148,7 @@ class Twig
 
             // set default date format if set in config
             if ($config->get('system.pages.dateformat.long')) {
-                $this->twig->getExtension('core')->setDateFormat($config->get('system.pages.dateformat.long'));
+                $this->twig->getExtension('Twig_Extension_Core')->setDateFormat($config->get('system.pages.dateformat.long'));
             }
             // enable the debug extension if required
             if ($config->get('system.twig.debug')) {
@@ -159,7 +162,7 @@ class Twig
             $pages = $this->grav['pages'];
 
             // Set some standard variables for twig
-            $this->twig_vars = $this->twig_vars + [
+            $this->twig_vars += [
                     'config'            => $config,
                     'system'            => $config->get('system'),
                     'theme'             => $config->get('theme'),

+ 81 - 28
system/src/Grav/Common/Twig/TwigExtension.php

@@ -18,11 +18,11 @@ use Grav\Common\Twig\TokenParser\TwigTokenParserTryCatch;
 use Grav\Common\Twig\TokenParser\TwigTokenParserMarkdown;
 use Grav\Common\User\User;
 use Grav\Common\Utils;
+use Grav\Common\Yaml;
 use Grav\Common\Markdown\Parsedown;
 use Grav\Common\Markdown\ParsedownExtra;
 use Grav\Common\Helpers\Base32;
 use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
-use Symfony\Component\Yaml\Yaml;
 
 class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface
 {
@@ -72,7 +72,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
             new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
             new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
             new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
-            new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction']),
+            new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['is_safe' => ['html']]),
             new \Twig_SimpleFilter('md5', [$this, 'md5Filter']),
             new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']),
             new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']),
@@ -88,9 +88,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
             new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']),
             new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']),
             new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
-            new \Twig_SimpleFilter('t', [$this, 'translate']),
-            new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
-            new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
             new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
             new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
             new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']),
@@ -100,6 +97,18 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
             new \Twig_SimpleFilter('print_r', 'print_r'),
             new \Twig_SimpleFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
             new \Twig_SimpleFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
+
+            // Translations
+            new \Twig_SimpleFilter('t', [$this, 'translate']),
+            new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
+            new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
+
+            // Casting values
+            new \Twig_SimpleFilter('string', [$this, 'stringFilter']),
+            new \Twig_SimpleFilter('int', [$this, 'intFilter'], ['is_safe' => true]),
+            new \Twig_SimpleFilter('bool', [$this, 'boolFilter']),
+            new \Twig_SimpleFilter('float', [$this, 'floatFilter'], ['is_safe' => true]),
+            new \Twig_SimpleFilter('array', [$this, 'arrayFilter']),
         ];
     }
 
@@ -111,7 +120,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
     public function getFunctions()
     {
         return [
-            new \Twig_SimpleFunction('array', [$this, 'arrayFunc']),
+            new \Twig_SimpleFunction('array', [$this, 'arrayFilter']),
             new \Twig_SimpleFunction('array_key_value', [$this, 'arrayKeyValueFunc']),
             new \Twig_SimpleFunction('array_key_exists', 'array_key_exists'),
             new \Twig_SimpleFunction('array_unique', 'array_unique'),
@@ -132,9 +141,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
             new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']),
             new \Twig_SimpleFunction('regex_filter', [$this, 'regexFilter']),
             new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
-            new \Twig_simpleFunction('t', [$this, 'translate']),
-            new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
-            new \Twig_simpleFunction('ta', [$this, 'translateArray']),
             new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
             new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']),
             new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
@@ -151,6 +157,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
             new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
             new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFilter']),
 
+            // Translations
+            new \Twig_simpleFunction('t', [$this, 'translate']),
+            new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
+            new \Twig_simpleFunction('ta', [$this, 'translateArray']),
         ];
     }
 
@@ -617,6 +627,62 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
         return ltrim($value, $chars);
     }
 
+    /**
+     * Casts input to string.
+     *
+     * @param mixed $input
+     * @return string
+     */
+    public function stringFilter($input)
+    {
+        return (string) $input;
+    }
+
+
+    /**
+     * Casts input to int.
+     *
+     * @param mixed $input
+     * @return int
+     */
+    public function intFilter($input)
+    {
+        return (int) $input;
+    }
+
+    /**
+     * Casts input to bool.
+     *
+     * @param mixed $input
+     * @return bool
+     */
+    public function boolFilter($input)
+    {
+        return (bool) $input;
+    }
+
+    /**
+     * Casts input to float.
+     *
+     * @param mixed $input
+     * @return float
+     */
+    public function floatFilter($input)
+    {
+        return (float) $input;
+    }
+
+    /**
+     * Casts input to array.
+     *
+     * @param mixed $input
+     * @return array
+     */
+    public function arrayFilter($input)
+    {
+        return (array) $input;
+    }
+
     /**
      * @return mixed
      */
@@ -693,7 +759,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
 
         $template = $env->createTemplate($twig);
         return $template->render($context);
-;
     }
 
     /**
@@ -748,7 +813,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
      * Output a Gist
      *
      * @param  string $id
-     * @param  string $file
+     * @param  string|bool $file
      *
      * @return string
      */
@@ -788,19 +853,6 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
         return str_pad($input, (int)$pad_length, $pad_string, $pad_type);
     }
 
-
-    /**
-     * Cast a value to array
-     *
-     * @param $value
-     *
-     * @return array
-     */
-    public function arrayFunc($value)
-    {
-        return (array)$value;
-    }
-
     /**
      * Workaround for twig associative array initialization
      * Returns a key => val array
@@ -976,7 +1028,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
     public function redirectFunc($url, $statusCode = 303)
     {
         header('Location: ' . $url, true, $statusCode);
-        die();
+        exit();
     }
 
     /**
@@ -1060,7 +1112,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
 
         if (file_exists($filepath)) {
             return file_get_contents($filepath);
-    }
+        }
 
         return false;
     }
@@ -1245,11 +1297,12 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
      * Dump/Encode data into YAML format
      *
      * @param $data
+     * @param $inline integer number of levels of inline syntax
      * @return mixed
      */
-    public function yamlEncodeFilter($data)
+    public function yamlEncodeFilter($data, $inline = 10)
     {
-        return Yaml::dump($data, 10);
+        return Yaml::dump($data, $inline);
     }
 
     /**

+ 41 - 30
system/src/Grav/Common/Uri.php

@@ -11,6 +11,7 @@ namespace Grav\Common;
 use Grav\Common\Config\Config;
 use Grav\Common\Language\Language;
 use Grav\Common\Page\Page;
+use Grav\Common\Page\Pages;
 use Grav\Framework\Route\RouteFactory;
 use Grav\Framework\Uri\UriFactory;
 use Grav\Framework\Uri\UriPartsFilter;
@@ -156,12 +157,6 @@ class Uri
             $uri = preg_replace('|^' . preg_quote($setup_base, '|') . '|', '', $uri);
         }
 
-        // If configured to, redirect trailing slash URI's with a 302 redirect
-        $redirect = str_replace($this->root, '', rtrim($uri, '/'));
-        if ($redirect && $uri !== '/' && $redirect !== $this->base() && $config->get('system.pages.redirect_trailing_slash', false) && Utils::endsWith($uri, '/')) {
-            $grav->redirect($redirect, 302);
-        }
-
         // process params
         $uri = $this->processParams($uri, $config->get('system.param_sep'));
 
@@ -206,9 +201,9 @@ class Uri
         }
 
         // Set some Grav stuff
-        $grav['base_url_absolute'] = $grav['config']->get('system.custom_base_url') ?: $this->rootUrl(true);
+        $grav['base_url_absolute'] = $config->get('system.custom_base_url') ?: $this->rootUrl(true);
         $grav['base_url_relative'] = $this->rootUrl(false);
-        $grav['base_url'] = $grav['config']->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
+        $grav['base_url'] = $config->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
 
         RouteFactory::setRoot($this->root_path);
         RouteFactory::setLanguage($language->getLanguageURLPrefix());
@@ -376,6 +371,17 @@ class Uri
         return $this->extension;
     }
 
+    public function method()
+    {
+        $method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
+
+        if ($method === 'POST' && isset($_SERVER['X-HTTP-METHOD-OVERRIDE'])) {
+            $method = strtoupper($_SERVER['X-HTTP-METHOD-OVERRIDE']);
+        }
+
+        return $method;
+    }
+
     /**
      * Return the scheme of the URI
      *
@@ -481,11 +487,9 @@ class Uri
     {
         if ($include_root) {
             return $this->uri;
-        } else {
-            $uri = str_replace($this->root_path, '', $this->uri);
-            return $uri;
         }
 
+        return str_replace($this->root_path, '', $this->uri);
     }
 
     /**
@@ -508,16 +512,10 @@ class Uri
     {
         $grav = Grav::instance();
 
-        // Link processing should prepend language
-        $language = $grav['language'];
-        $language_append = '';
-        if ($language->enabled()) {
-            $language_append = $language->getLanguageURLPrefix();
-        }
-
-        $base = $grav['base_url_relative'];
+        /** @var Pages $pages */
+        $pages = $grav['pages'];
 
-        return rtrim($base . $grav['pages']->base(), '/') . $language_append;
+        return $pages->baseUrl(null, false);
     }
 
     /**
@@ -633,10 +631,9 @@ class Uri
         }
 
         return $ip;
-
     }
-    /**
 
+    /**
      * Returns current Uri.
      *
      * @return \Grav\Framework\Uri\Uri
@@ -883,7 +880,26 @@ class Uri
     public static function parseUrl($url)
     {
         $grav = Grav::instance();
-        $parts = parse_url($url);
+
+        $encodedUrl = preg_replace_callback(
+            '%[^:/@?&=#]+%usD',
+            function ($matches) { return rawurlencode($matches[0]); },
+            $url
+        );
+
+        $parts = parse_url($encodedUrl);
+
+        if (false === $parts) {
+            return false;
+        }
+
+        foreach($parts as $name => $value) {
+            $parts[$name] = rawurldecode($value);
+        }
+
+        if (!isset($parts['path'])) {
+            $parts['path'] = '';
+        }
 
         list($stripped_path, $params) = static::extractParams($parts['path'], $grav['config']->get('system.param_sep'));
 
@@ -1262,7 +1278,7 @@ class Uri
     {
         if (!$this->post) {
             $content_type = $this->getContentType();
-            if ($content_type == 'application/json') {
+            if ($content_type === 'application/json') {
                 $json = file_get_contents('php://input');
                 $this->post = json_decode($json, true);
             } elseif (!empty($_POST)) {
@@ -1270,7 +1286,7 @@ class Uri
             }
         }
 
-        if ($this->post && !is_null($element)) {
+        if ($this->post && null !== $element) {
             $item = Utils::getDotNotation($this->post, $element);
             if ($filter_type) {
                 $item = filter_var($item, $filter_type);
@@ -1320,11 +1336,6 @@ class Uri
         $scriptPath = str_replace('\\', '/', $_SERVER['PHP_SELF']);
         $rootPath = str_replace(' ', '%20', rtrim(substr($scriptPath, 0, strpos($scriptPath, 'index.php')), '/'));
 
-        // check if userdir in the path and workaround PHP bug with PHP_SELF
-        if (strpos($this->uri, '/~') !== false && strpos($scriptPath, '/~') === false) {
-            $rootPath = substr($this->uri, 0, strpos($this->uri, '/', 1)) . $rootPath;
-        }
-
         return $rootPath;
     }
 

+ 28 - 62
system/src/Grav/Common/Utils.php

@@ -45,8 +45,20 @@ abstract class Utils
             /** @var UniformResourceLocator $locator */
             $locator = Grav::instance()['locator'];
 
-            // Get relative path to the resource (or false if not found).
-            $resource = $locator->findResource($input, false);
+            $parts = Uri::parseUrl($input);
+
+            if ($parts) {
+                $resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false);
+
+                if (isset($parts['query'])) {
+                    $resource = $resource . '?' . $parts['query'];
+                }
+            } else {
+                // Not a valid URL (can still be a stream).
+                $resource = $locator->findResource($input, false);
+            }
+
+
         } else {
             $resource = $input;
         }
@@ -262,7 +274,7 @@ abstract class Utils
         // is $break present between $limit and the end of the string?
         if ($up_to_break && false !== ($breakpoint = mb_strpos($string, $break, $limit))) {
             if ($breakpoint < mb_strlen($string) - 1) {
-                $string = mb_substr($string, 0, $breakpoint) . $break;
+                $string = mb_substr($string, 0, $breakpoint) . $pad;
             }
         } else {
             $string = mb_substr($string, 0, $limit) . $pad;
@@ -705,11 +717,11 @@ abstract class Utils
      * with reverse proxy setups.
      *
      * @param string $action
-     * @param bool   $plusOneTick if true, generates the token for the next tick (the next 12 hours)
+     * @param bool   $previousTick if true, generates the token for the previous tick (the previous 12 hours)
      *
      * @return string the nonce string
      */
-    private static function generateNonceString($action, $plusOneTick = false)
+    private static function generateNonceString($action, $previousTick = false)
     {
         $username = '';
         if (isset(Grav::instance()['user'])) {
@@ -720,29 +732,8 @@ abstract class Utils
         $token = session_id();
         $i = self::nonceTick();
 
-        if ($plusOneTick) {
-            $i++;
-        }
-
-        return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
-    }
-
-    //Added in version 1.0.8 to ensure that existing nonces are not broken.
-    private static function generateNonceStringOldStyle($action, $plusOneTick = false)
-    {
-        if (isset(Grav::instance()['user'])) {
-            $user = Grav::instance()['user'];
-            $username = $user->username;
-            if (isset($_SERVER['REMOTE_ADDR'])) {
-                $username .= $_SERVER['REMOTE_ADDR'];
-            }
-        } else {
-            $username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
-        }
-        $token = session_id();
-        $i = self::nonceTick();
-        if ($plusOneTick) {
-            $i++;
+        if ($previousTick) {
+            $i--;
         }
 
         return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
@@ -768,33 +759,20 @@ abstract class Utils
      * action is the same for 12 hours.
      *
      * @param string $action      the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
-     * @param bool   $plusOneTick if true, generates the token for the next tick (the next 12 hours)
+     * @param bool   $previousTick if true, generates the token for the previous tick (the previous 12 hours)
      *
      * @return string the nonce
      */
-    public static function getNonce($action, $plusOneTick = false)
-    {
-        // Don't regenerate this again if not needed
-        if (isset(static::$nonces[$action])) {
-            return static::$nonces[$action];
-        }
-        $nonce = md5(self::generateNonceString($action, $plusOneTick));
-        static::$nonces[$action] = $nonce;
-
-        return static::$nonces[$action];
-    }
-
-    //Added in version 1.0.8 to ensure that existing nonces are not broken.
-    public static function getNonceOldStyle($action, $plusOneTick = false)
+    public static function getNonce($action, $previousTick = false)
     {
         // Don't regenerate this again if not needed
-        if (isset(static::$nonces[$action])) {
-            return static::$nonces[$action];
+        if (isset(static::$nonces[$action][$previousTick])) {
+            return static::$nonces[$action][$previousTick];
         }
-        $nonce = md5(self::generateNonceStringOldStyle($action, $plusOneTick));
-        static::$nonces[$action] = $nonce;
+        $nonce = md5(self::generateNonceString($action, $previousTick));
+        static::$nonces[$action][$previousTick] = $nonce;
 
-        return static::$nonces[$action];
+        return static::$nonces[$action][$previousTick];
     }
 
     /**
@@ -818,20 +796,8 @@ abstract class Utils
         }
 
         //Nonce generated 12-24 hours ago
-        $plusOneTick = true;
-        if ($nonce === self::getNonce($action, $plusOneTick)) {
-            return true;
-        }
-
-        //Added in version 1.0.8 to ensure that existing nonces are not broken.
-        //Nonce generated 0-12 hours ago
-        if ($nonce === self::getNonceOldStyle($action)) {
-            return true;
-        }
-
-        //Nonce generated 12-24 hours ago
-        $plusOneTick = true;
-        if ($nonce === self::getNonceOldStyle($action, $plusOneTick)) {
+        $previousTick = true;
+        if ($nonce === self::getNonce($action, $previousTick)) {
             return true;
         }
 

+ 47 - 0
system/src/Grav/Common/Yaml.php

@@ -0,0 +1,47 @@
+<?php
+/**
+ * @package    Grav.Common
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Common;
+
+use Grav\Framework\File\Formatter\YamlFormatter;
+
+abstract class Yaml
+{
+    /** @var YamlFormatter */
+    private static $yaml;
+
+    public static function parse($data)
+    {
+        if (null === static::$yaml) {
+            static::init();
+        }
+
+        return static::$yaml->decode($data);
+    }
+
+    public static function dump($data, $inline = null, $indent = null)
+    {
+        if (null === static::$yaml) {
+            static::init();
+        }
+
+        return static::$yaml->encode($data, $inline, $indent);
+    }
+
+    private static function init()
+    {
+        $config = [
+            'inline' => 5,
+            'indent' => 2,
+            'native' => true,
+            'compat' => true
+        ];
+
+        static::$yaml = new YamlFormatter($config);
+    }
+}

+ 25 - 13
system/src/Grav/Console/Cli/InstallCommand.php

@@ -9,9 +9,9 @@
 namespace Grav\Console\Cli;
 
 use Grav\Console\ConsoleCommand;
+use RocketTheme\Toolbox\File\YamlFile;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputOption;
-use Symfony\Component\Yaml\Yaml;
 
 class InstallCommand extends ConsoleCommand
 {
@@ -71,9 +71,9 @@ class InstallCommand extends ConsoleCommand
 
         // Look for dependencies file in ROOT and USER dir
         if (file_exists($this->user_path . $dependencies_file)) {
-            $this->config = Yaml::parse(file_get_contents($this->user_path . $dependencies_file));
+            $file = YamlFile::instance($this->user_path . $dependencies_file);
         } elseif (file_exists($this->destination . $dependencies_file)) {
-            $this->config = Yaml::parse(file_get_contents($this->destination . $dependencies_file));
+            $file = YamlFile::instance($this->destination . $dependencies_file);
         } else {
             $this->output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
             if ($this->input->getArgument('destination')) {
@@ -85,6 +85,9 @@ class InstallCommand extends ConsoleCommand
             return;
         }
 
+        $this->config = $file->content();
+        $file->free();
+
         // If yaml config, process
         if ($this->config) {
             if (!$this->input->getOption('symlink')) {
@@ -153,23 +156,32 @@ class InstallCommand extends ConsoleCommand
 
         exec('cd ' . $this->destination);
         foreach ($this->config['links'] as $repo => $data) {
-            $from = $this->local_config[$data['scm'] . '_repos'] . $data['src'];
+            $repos = (array) $this->local_config[$data['scm'] . '_repos'];
+            $from = false;
             $to = $this->destination . $data['path'];
 
-            if (file_exists($from)) {
-                if (!file_exists($to)) {
-                    symlink($from, $to);
-                    $this->output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
-                    $this->output->writeln('');
-                } else {
-                    $this->output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
-                    $this->output->writeln('');
+            foreach ($repos as $repo) {
+                $path = $repo . $data['src'];
+                if (file_exists($path)) {
+                    $from = $path;
+                    continue;
                 }
-            } else {
+            }
+
+            if (!$from) {
                 $this->output->writeln('<red>source: ' . $from . ' does not exists, skipping...</red>');
                 $this->output->writeln('');
             }
 
+            if (!file_exists($to)) {
+                symlink($from, $to);
+                $this->output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
+                $this->output->writeln('');
+            } else {
+                $this->output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
+                $this->output->writeln('');
+            }
+
         }
     }
 }

+ 4 - 2
system/src/Grav/Console/ConsoleTrait.php

@@ -12,11 +12,11 @@ use Grav\Common\Grav;
 use Grav\Common\Composer;
 use Grav\Common\GravTrait;
 use Grav\Console\Cli\ClearCacheCommand;
+use RocketTheme\Toolbox\File\YamlFile;
 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
 use Symfony\Component\Console\Input\ArrayInput;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Yaml\Yaml;
 
 trait ConsoleTrait
 {
@@ -123,7 +123,9 @@ trait ConsoleTrait
         $local_config_file = $home_folder . '/.grav/config';
 
         if (file_exists($local_config_file)) {
-            $this->local_config = Yaml::parse(file_get_contents($local_config_file));
+            $file = YamlFile::instance($local_config_file);
+            $this->local_config = $file->content();
+            $file->free();
             return $local_config_file;
         }
 

+ 9 - 6
system/src/Grav/Console/Gpm/InstallCommand.php

@@ -444,18 +444,21 @@ class InstallCommand extends ConsoleCommand
     {
         $matches = $this->getGitRegexMatches($package);
 
-        foreach ($this->local_config as $path) {
+        foreach ($this->local_config as $paths) {
             if (Utils::endsWith($matches[2], '.git')) {
                 $repo_dir = preg_replace('/\.git$/', '', $matches[2]);
             } else {
                 $repo_dir = $matches[2];
             }
-
-            $from = rtrim($path, '/') . '/' . $repo_dir;
-
-            if (file_exists($from)) {
-                return $from;
+            
+            $paths = (array) $paths;
+            foreach ($paths as $repo) {
+                $path = rtrim($repo, '/') . '/' . $repo_dir;
+                if (file_exists($path)) {
+                    return $path;
+                }
             }
+
         }
 
         return false;

+ 5 - 2
system/src/Grav/Console/Gpm/VersionCommand.php

@@ -11,9 +11,9 @@ namespace Grav\Console\Gpm;
 use Grav\Common\GPM\GPM;
 use Grav\Common\GPM\Upgrader;
 use Grav\Console\ConsoleCommand;
+use RocketTheme\Toolbox\File\YamlFile;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputOption;
-use Symfony\Component\Yaml\Yaml;
 
 class VersionCommand extends ConsoleCommand
 {
@@ -84,7 +84,10 @@ class VersionCommand extends ConsoleCommand
                     }
                 }
 
-                $package_yaml = Yaml::parse(file_get_contents($blueprints_path));
+                $file = YamlFile::instance($blueprints_path);
+                $package_yaml = $file->content();
+                $file->free();
+
                 $version = $package_yaml['version'];
 
                 if (!$version) {

+ 18 - 9
system/src/Grav/Framework/Cache/CacheTrait.php

@@ -16,21 +16,18 @@ use Grav\Framework\Cache\Exception\InvalidArgumentException;
  */
 trait CacheTrait
 {
-    /**
-     * @var string
-     */
+    /** @var string */
     private $namespace = '';
 
-    /**
-     * @var int|null
-     */
+    /** @var int|null */
     private $defaultLifetime = null;
 
-    /**
-     * @var \stdClass
-     */
+    /** @var \stdClass */
     private $miss;
 
+    /** @var bool */
+    private $validation = true;
+
     /**
      * Always call from constructor.
      *
@@ -45,6 +42,14 @@ trait CacheTrait
         $this->miss = new \stdClass;
     }
 
+    /**
+     * @param $validation
+     */
+    public function setValidation($validation)
+    {
+        $this->validation = (bool) $validation;
+    }
+
     /**
      * @return string
      */
@@ -307,6 +312,10 @@ trait CacheTrait
      */
     protected function validateKeys($keys)
     {
+        if (!$this->validation) {
+            return;
+        }
+
         foreach ($keys as $key) {
             $this->validateKey($key);
         }

+ 0 - 10
system/src/Grav/Framework/Collection/ArrayCollection.php

@@ -24,11 +24,6 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
      */
     public function reverse()
     {
-        // TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
-        if (!method_exists($this, 'createFrom')) {
-            return new static(array_reverse($this->toArray()));
-        }
-
         return $this->createFrom(array_reverse($this->toArray()));
     }
 
@@ -42,11 +37,6 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
         $keys = $this->getKeys();
         shuffle($keys);
 
-        // TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
-        if (!method_exists($this, 'createFrom')) {
-            return new static(array_replace(array_flip($keys), $this->toArray()));
-        }
-
         return $this->createFrom(array_replace(array_flip($keys), $this->toArray()));
     }
 

+ 30 - 4
system/src/Grav/Framework/ContentBlock/ContentBlock.php

@@ -27,6 +27,7 @@ class ContentBlock implements ContentBlockInterface
     protected $tokenTemplate = '@@BLOCK-%s@@';
     protected $content = '';
     protected $blocks = [];
+    protected $checksum;
 
     /**
      * @param string $id
@@ -40,6 +41,7 @@ class ContentBlock implements ContentBlockInterface
     /**
      * @param array $serialized
      * @return ContentBlockInterface
+     * @throws \InvalidArgumentException
      */
     public static function fromArray(array $serialized)
     {
@@ -48,14 +50,14 @@ class ContentBlock implements ContentBlockInterface
             $id = isset($serialized['id']) ? $serialized['id'] : null;
 
             if (!$type || !$id || !is_a($type, 'Grav\Framework\ContentBlock\ContentBlockInterface', true)) {
-                throw new \RuntimeException('Bad data');
+                throw new \InvalidArgumentException('Bad data');
             }
 
             /** @var ContentBlockInterface $instance */
             $instance = new $type($id);
             $instance->build($serialized);
         } catch (\Exception $e) {
-            throw new \RuntimeException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
+            throw new \InvalidArgumentException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
         }
 
         return $instance;
@@ -104,9 +106,13 @@ class ContentBlock implements ContentBlockInterface
         $array = [
             '_type' => get_class($this),
             '_version' => $this->version,
-            'id' => $this->id,
+            'id' => $this->id
         ];
 
+        if ($this->checksum) {
+            $array['checksum'] = $this->checksum;
+        }
+
         if ($this->content) {
             $array['content'] = $this->content;
         }
@@ -158,6 +164,7 @@ class ContentBlock implements ContentBlockInterface
         $this->checkVersion($serialized);
 
         $this->id = isset($serialized['id']) ? $serialized['id'] : $this->generateId();
+        $this->checksum = isset($serialized['checksum']) ? $serialized['checksum'] : null;
 
         if (isset($serialized['content'])) {
             $this->setContent($serialized['content']);
@@ -169,6 +176,25 @@ class ContentBlock implements ContentBlockInterface
         }
     }
 
+    /**
+     * @param string $checksum
+     * @return $this
+     */
+    public function setChecksum($checksum)
+    {
+        $this->checksum = $checksum;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getChecksum()
+    {
+        return $this->checksum;
+    }
+
     /**
      * @param string $content
      * @return $this
@@ -222,7 +248,7 @@ class ContentBlock implements ContentBlockInterface
      */
     protected function checkVersion(array $serialized)
     {
-        $version = isset($serialized['_version']) ? (string) $serialized['_version'] : '1';
+        $version = isset($serialized['_version']) ? (int) $serialized['_version'] : 1;
         if ($version !== $this->version) {
             throw new \RuntimeException(sprintf('Unsupported version %s', $version));
         }

+ 11 - 0
system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php

@@ -61,6 +61,17 @@ interface ContentBlockInterface extends \Serializable
      */
     public function build(array $serialized);
 
+    /**
+     * @param string $checksum
+     * @return $this
+     */
+    public function setChecksum($checksum);
+
+    /**
+     * @return string
+     */
+    public function getChecksum();
+
     /**
      * @param string $content
      * @return $this

+ 1 - 0
system/src/Grav/Framework/ContentBlock/HtmlBlock.php

@@ -15,6 +15,7 @@ namespace Grav\Framework\ContentBlock;
  */
 class HtmlBlock extends ContentBlock implements HtmlBlockInterface
 {
+    protected $version = 1;
     protected $frameworks = [];
     protected $styles = [];
     protected $scripts = [];

+ 44 - 0
system/src/Grav/Framework/File/Formatter/FormatterInterface.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * @package    Grav\Framework\File\Formatter
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Framework\File\Formatter;
+
+interface FormatterInterface
+{
+    /**
+     * Get default file extension from current formatter (with dot).
+     *
+     * Default file extension is the first defined extension.
+     *
+     * @return string File extension (can be empty).
+     */
+    public function getDefaultFileExtension();
+
+    /**
+     * Get file extensions supported by current formatter (with dot).
+     *
+     * @return string[]
+     */
+    public function getSupportedFileExtensions();
+
+    /**
+     * Encode data into a string.
+     *
+     * @param array $data
+     * @return string
+     */
+    public function encode($data);
+
+    /**
+     * Decode a string into data.
+     *
+     * @param string $data
+     * @return array
+     */
+    public function decode($data);
+}

+ 83 - 0
system/src/Grav/Framework/File/Formatter/IniFormatter.php

@@ -0,0 +1,83 @@
+<?php
+/**
+ * @package    Grav\Framework\File\Formatter
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Framework\File\Formatter;
+
+class IniFormatter implements FormatterInterface
+{
+    /** @var array */
+    private $config;
+
+    /**
+     * IniFormatter constructor.
+     * @param array $config
+     */
+    public function __construct(array $config = [])
+    {
+        $this->config = $config + [
+                'file_extension' => '.ini'
+            ];
+    }
+
+    /**
+     * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
+     */
+    public function getFileExtension()
+    {
+        return $this->getDefaultFileExtension();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDefaultFileExtension()
+    {
+        $extensions = $this->getSupportedFileExtensions();
+
+        return (string) reset($extensions);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSupportedFileExtensions()
+    {
+        return (array) $this->config['file_extension'];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function encode($data)
+    {
+        $string = '';
+        foreach ($data as $key => $value) {
+            $string .= $key . '="' .  preg_replace(
+                    ['/"/', '/\\\/', "/\t/", "/\n/", "/\r/"],
+                    ['\"',  '\\\\', '\t',   '\n',   '\r'],
+                    $value
+                ) . "\"\n";
+        }
+
+        return $string;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function decode($data)
+    {
+        $decoded = @parse_ini_string($data);
+
+        if ($decoded === false) {
+            throw new \RuntimeException('Decoding INI failed');
+        }
+
+        return $decoded;
+    }
+}

+ 78 - 0
system/src/Grav/Framework/File/Formatter/JsonFormatter.php

@@ -0,0 +1,78 @@
+<?php
+/**
+ * @package    Grav\Framework\File\Formatter
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Framework\File\Formatter;
+
+class JsonFormatter implements FormatterInterface
+{
+    /** @var array */
+    private $config;
+
+    public function __construct(array $config = [])
+    {
+        $this->config = $config + [
+            'file_extension' => '.json',
+            'encode_options' => 0,
+            'decode_assoc' => true
+        ];
+    }
+
+    /**
+     * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
+     */
+    public function getFileExtension()
+    {
+        return $this->getDefaultFileExtension();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDefaultFileExtension()
+    {
+        $extensions = $this->getSupportedFileExtensions();
+
+        return (string) reset($extensions);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSupportedFileExtensions()
+    {
+        return (array) $this->config['file_extension'];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function encode($data)
+    {
+        $encoded = @json_encode($data, $this->config['encode_options']);
+
+        if ($encoded === false) {
+            throw new \RuntimeException('Encoding JSON failed');
+        }
+
+        return $encoded;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function decode($data)
+    {
+        $decoded = @json_decode($data, $this->config['decode_assoc']);
+
+        if ($decoded === false) {
+            throw new \RuntimeException('Decoding JSON failed');
+        }
+
+        return $decoded;
+    }
+}

+ 116 - 0
system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php

@@ -0,0 +1,116 @@
+<?php
+/**
+ * @package    Grav\Framework\File\Formatter
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Framework\File\Formatter;
+
+class MarkdownFormatter implements FormatterInterface
+{
+    /** @var array */
+    private $config;
+    /** @var FormatterInterface */
+    private $headerFormatter;
+
+    public function __construct(array $config = [], FormatterInterface $headerFormatter = null)
+    {
+        $this->config = $config + [
+            'file_extension' => '.md',
+            'header' => 'header',
+            'body' => 'markdown',
+            'raw' => 'frontmatter',
+            'yaml' => ['inline' => 20]
+        ];
+
+        $this->headerFormatter = $headerFormatter ?: new YamlFormatter($this->config['yaml']);
+    }
+
+    /**
+     * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
+     */
+    public function getFileExtension()
+    {
+        return $this->getDefaultFileExtension();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDefaultFileExtension()
+    {
+        $extensions = $this->getSupportedFileExtensions();
+
+        return (string) reset($extensions);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSupportedFileExtensions()
+    {
+        return (array) $this->config['file_extension'];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function encode($data)
+    {
+        $headerVar = $this->config['header'];
+        $bodyVar = $this->config['body'];
+
+        $header = isset($data[$headerVar]) ? (array) $data[$headerVar] : [];
+        $body = isset($data[$bodyVar]) ? (string) $data[$bodyVar] : '';
+
+        // Create Markdown file with YAML header.
+        $encoded = '';
+        if ($header) {
+            $encoded = "---\n" . trim($this->headerFormatter->encode($data['header'])) . "\n---\n\n";
+        }
+        $encoded .= $body;
+
+        // Normalize line endings to Unix style.
+        $encoded = preg_replace("/(\r\n|\r)/", "\n", $encoded);
+
+        return $encoded;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function decode($data)
+    {
+        $headerVar = $this->config['header'];
+        $bodyVar = $this->config['body'];
+        $rawVar = $this->config['raw'];
+
+        $content = [
+            $headerVar => [],
+            $bodyVar => ''
+        ];
+
+        $headerRegex = "/^---\n(.+?)\n---\n{0,}(.*)$/uis";
+
+        // Normalize line endings to Unix style.
+        $data = preg_replace("/(\r\n|\r)/", "\n", $data);
+
+        // Parse header.
+        preg_match($headerRegex, ltrim($data), $matches);
+        if(empty($matches)) {
+            $content[$bodyVar] = $data;
+        } else {
+            // Normalize frontmatter.
+            $frontmatter = preg_replace("/\n\t/", "\n    ", $matches[1]);
+            if ($rawVar) {
+                $content[$rawVar] = $frontmatter;
+            }
+            $content[$headerVar] = $this->headerFormatter->decode($frontmatter);
+            $content[$bodyVar] = $matches[2];
+        }
+
+        return $content;
+    }
+}

+ 96 - 0
system/src/Grav/Framework/File/Formatter/SerializeFormatter.php

@@ -0,0 +1,96 @@
+<?php
+/**
+ * @package    Grav\Framework\File\Formatter
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Framework\File\Formatter;
+
+class SerializeFormatter implements FormatterInterface
+{
+    /** @var array */
+    private $config;
+
+    /**
+     * IniFormatter constructor.
+     * @param array $config
+     */
+    public function __construct(array $config = [])
+    {
+        $this->config = $config + [
+                'file_extension' => '.ser'
+            ];
+    }
+
+    /**
+     * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
+     */
+    public function getFileExtension()
+    {
+        return $this->getDefaultFileExtension();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDefaultFileExtension()
+    {
+        $extensions = $this->getSupportedFileExtensions();
+
+        return (string) reset($extensions);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSupportedFileExtensions()
+    {
+        return (array) $this->config['file_extension'];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function encode($data)
+    {
+        return serialize($this->preserveLines($data, ["\n", "\r"], ['\\n', '\\r']));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function decode($data)
+    {
+        $decoded = @unserialize($data);
+
+        if ($decoded === false) {
+            throw new \RuntimeException('Decoding serialized data failed');
+        }
+
+        return $this->preserveLines($decoded, ['\\n', '\\r'], ["\n", "\r"]);
+    }
+
+    /**
+     * Preserve new lines, recursive function.
+     *
+     * @param mixed $data
+     * @param array $search
+     * @param array $replace
+     * @return mixed
+     */
+    protected function preserveLines($data, $search, $replace)
+    {
+        if (is_string($data)) {
+            $data = str_replace($search, $replace, $data);
+        } elseif (is_array($data)) {
+            foreach ($data as &$value) {
+                $value = $this->preserveLines($value, $search, $replace);
+            }
+            unset($value);
+        }
+
+        return $data;
+    }
+}

+ 103 - 0
system/src/Grav/Framework/File/Formatter/YamlFormatter.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * @package    Grav\Framework\File\Formatter
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Framework\File\Formatter;
+
+use Symfony\Component\Yaml\Exception\DumpException;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Yaml as YamlParser;
+use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYamlParser;
+
+class YamlFormatter implements FormatterInterface
+{
+    /** @var array */
+    private $config;
+
+    public function __construct(array $config = [])
+    {
+        $this->config = $config + [
+            'file_extension' => '.yaml',
+            'inline' => 5,
+            'indent' => 2,
+            'native' => true,
+            'compat' => true
+        ];
+    }
+
+    /**
+     * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
+     */
+    public function getFileExtension()
+    {
+        return $this->getDefaultFileExtension();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDefaultFileExtension()
+    {
+        $extensions = $this->getSupportedFileExtensions();
+
+        return (string) reset($extensions);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSupportedFileExtensions()
+    {
+        return (array) $this->config['file_extension'];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function encode($data, $inline = null, $indent = null)
+    {
+        try {
+            return (string) YamlParser::dump(
+                $data,
+                $inline ? (int) $inline : $this->config['inline'],
+                $indent ? (int) $indent : $this->config['indent'],
+                YamlParser::DUMP_EXCEPTION_ON_INVALID_TYPE
+            );
+        } catch (DumpException $e) {
+            throw new \RuntimeException('Encoding YAML failed: ' . $e->getMessage(), 0, $e);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function decode($data)
+    {
+        // Try native PECL YAML PHP extension first if available.
+        if ($this->config['native'] && function_exists('yaml_parse')) {
+            // Safely decode YAML.
+            $saved = @ini_get('yaml.decode_php');
+            @ini_set('yaml.decode_php', 0);
+            $decoded = @yaml_parse($data);
+            @ini_set('yaml.decode_php', $saved);
+
+            if ($decoded !== false) {
+                return (array) $decoded;
+            }
+        }
+
+        try {
+            return (array) YamlParser::parse($data);
+        } catch (ParseException $e) {
+            if ($this->config['compat']) {
+                return (array) FallbackYamlParser::parse($data);
+            }
+
+            throw new \RuntimeException('Decoding YAML failed: ' . $e->getMessage(), 0, $e);
+        }
+    }
+}

+ 1 - 11
system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php

@@ -32,11 +32,6 @@ trait ObjectCollectionTrait
             $list[$key] = is_object($value) ? clone $value : $value;
         }
 
-        // TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
-        if (!method_exists($this, 'createFrom')) {
-            return new static($list);
-        }
-
         return $this->createFrom($list);
     }
 
@@ -170,12 +165,7 @@ trait ObjectCollectionTrait
     {
         $collections = [];
         foreach ($this->group($property) as $id => $elements) {
-            // TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
-            if (!method_exists($this, 'createFrom')) {
-                $collection = new static($elements);
-            } else {
-                $collection = $this->createFrom($elements);
-            }
+            $collection = $this->createFrom($elements);
 
             $collections[$id] = $collection;
         }

+ 25 - 4
system/src/Grav/Framework/Object/Base/ObjectTrait.php

@@ -15,7 +15,7 @@ namespace Grav\Framework\Object\Base;
  */
 trait ObjectTrait
 {
-    static protected $prefix;
+    /** @var string */
     static protected $type;
 
     /**
@@ -23,18 +23,28 @@ trait ObjectTrait
      */
     private $_key;
 
+    /**
+     * @return string
+     */
+    protected function getTypePrefix()
+    {
+        return '';
+    }
+
     /**
      * @param bool $prefix
      * @return string
      */
     public function getType($prefix = true)
     {
+        $type = $prefix ? $this->getTypePrefix() : '';
+
         if (static::$type) {
-            return ($prefix ? static::$prefix : '') . static::$type;
+            return $type . static::$type;
         }
 
         $class = get_class($this);
-        return ($prefix ? static::$prefix : '') . strtolower(substr($class, strrpos($class, '\\') + 1));
+        return $type . strtolower(substr($class, strrpos($class, '\\') + 1));
     }
 
     /**
@@ -108,7 +118,7 @@ trait ObjectTrait
      */
     public function serialize()
     {
-        return serialize($this->jsonSerialize());
+        return serialize($this->doSerialize());
     }
 
     /**
@@ -124,6 +134,14 @@ trait ObjectTrait
         $this->doUnserialize($data);
     }
 
+    /**
+     * @return array
+     */
+    protected function doSerialize()
+    {
+        return $this->jsonSerialize();
+    }
+
     /**
      * @param array $serialized
      */
@@ -159,10 +177,13 @@ trait ObjectTrait
 
     /**
      * @param string $key
+     * @return $this
      */
     protected function setKey($key)
     {
         $this->_key = (string) $key;
+
+        return $this;
     }
 
     abstract protected function doHasProperty($property);

+ 198 - 0
system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php

@@ -0,0 +1,198 @@
+<?php
+/**
+ * @package    Grav\Framework\Object
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Framework\Object\Collection;
+
+use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
+use Doctrine\Common\Collections\Expr\Comparison;
+
+class ObjectExpressionVisitor extends ClosureExpressionVisitor
+{
+    /**
+     * Accesses the field of a given object.
+     *
+     * @param object $object
+     * @param string $field
+     *
+     * @return mixed
+     */
+    public static function getObjectFieldValue($object, $field)
+    {
+        $op = $value = null;
+
+        $pos = strpos($field, '(');
+        if (false !== $pos) {
+            list ($op, $field) = explode('(', $field, 2);
+            $field = rtrim($field, ')');
+        }
+
+        if (isset($object[$field])) {
+            $value = $object[$field];
+        } else {
+            $accessors = array('', 'get', 'is');
+
+            foreach ($accessors as $accessor) {
+                $accessor .= $field;
+
+                if (!method_exists($object, $accessor)) {
+                    continue;
+                }
+
+                $value = $object->{$accessor}();
+                break;
+            }
+        }
+
+        if ($op) {
+            $function = 'filter' . ucfirst(strtolower($op));
+            if (method_exists(static::class, $function)) {
+                $value = static::$function($value);
+            }
+        }
+
+        return $value;
+    }
+
+    public static function filterLower($str)
+    {
+        return mb_strtolower($str);
+    }
+
+    public static function filterUpper($str)
+    {
+        return mb_strtoupper($str);
+    }
+
+    public static function filterLength($str)
+    {
+        return mb_strlen($str);
+    }
+
+    public static function filterLtrim($str)
+    {
+        return ltrim($str);
+    }
+
+    public static function filterRtrim($str)
+    {
+        return rtrim($str);
+    }
+
+    public static function filterTrim($str)
+    {
+        return trim($str);
+    }
+
+    /**
+     * Helper for sorting arrays of objects based on multiple fields + orientations.
+     *
+     * @param string   $name
+     * @param int      $orientation
+     * @param \Closure $next
+     *
+     * @return \Closure
+     */
+    public static function sortByField($name, $orientation = 1, \Closure $next = null)
+    {
+        if (!$next) {
+            $next = function() {
+                return 0;
+            };
+        }
+
+        return function ($a, $b) use ($name, $next, $orientation) {
+            $aValue = static::getObjectFieldValue($a, $name);
+            $bValue = static::getObjectFieldValue($b, $name);
+
+            if ($aValue === $bValue) {
+                return $next($a, $b);
+            }
+
+            return (($aValue > $bValue) ? 1 : -1) * $orientation;
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function walkComparison(Comparison $comparison)
+    {
+        $field = $comparison->getField();
+        $value = $comparison->getValue()->getValue(); // shortcut for walkValue()
+
+        switch ($comparison->getOperator()) {
+            case Comparison::EQ:
+                return function ($object) use ($field, $value) {
+                    return static::getObjectFieldValue($object, $field) === $value;
+                };
+
+            case Comparison::NEQ:
+                return function ($object) use ($field, $value) {
+                    return static::getObjectFieldValue($object, $field) !== $value;
+                };
+
+            case Comparison::LT:
+                return function ($object) use ($field, $value) {
+                    return static::getObjectFieldValue($object, $field) < $value;
+                };
+
+            case Comparison::LTE:
+                return function ($object) use ($field, $value) {
+                    return static::getObjectFieldValue($object, $field) <= $value;
+                };
+
+            case Comparison::GT:
+                return function ($object) use ($field, $value) {
+                    return static::getObjectFieldValue($object, $field) > $value;
+                };
+
+            case Comparison::GTE:
+                return function ($object) use ($field, $value) {
+                    return static::getObjectFieldValue($object, $field) >= $value;
+                };
+
+            case Comparison::IN:
+                return function ($object) use ($field, $value) {
+                    return \in_array(static::getObjectFieldValue($object, $field), $value, true);
+                };
+
+            case Comparison::NIN:
+                return function ($object) use ($field, $value) {
+                    return !\in_array(static::getObjectFieldValue($object, $field), $value, true);
+                };
+
+            case Comparison::CONTAINS:
+                return function ($object) use ($field, $value) {
+                    return false !== strpos(static::getObjectFieldValue($object, $field), $value);
+                };
+
+            case Comparison::MEMBER_OF:
+                return function ($object) use ($field, $value) {
+                    $fieldValues = static::getObjectFieldValue($object, $field);
+                    if (!is_array($fieldValues)) {
+                        $fieldValues = iterator_to_array($fieldValues);
+                    }
+                    return \in_array($value, $fieldValues, true);
+                };
+
+            case Comparison::STARTS_WITH:
+                return function ($object) use ($field, $value) {
+                    return 0 === strpos(static::getObjectFieldValue($object, $field), $value);
+                };
+
+            case Comparison::ENDS_WITH:
+                return function ($object) use ($field, $value) {
+                    return $value === substr(static::getObjectFieldValue($object, $field), -strlen($value));
+                };
+
+
+            default:
+                throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator());
+        }
+    }
+}

+ 2 - 1
system/src/Grav/Framework/Object/Interfaces/ObjectCollectionInterface.php

@@ -8,13 +8,14 @@
 
 namespace Grav\Framework\Object\Interfaces;
 
+use Doctrine\Common\Collections\Selectable;
 use Grav\Framework\Collection\CollectionInterface;
 
 /**
  * ObjectCollection Interface
  * @package Grav\Framework\Collection
  */
-interface ObjectCollectionInterface extends CollectionInterface, ObjectInterface
+interface ObjectCollectionInterface extends CollectionInterface, Selectable, ObjectInterface
 {
     /**
      * Create a copy from this collection by cloning all objects in the collection.

+ 35 - 0
system/src/Grav/Framework/Object/ObjectCollection.php

@@ -8,9 +8,11 @@
 
 namespace Grav\Framework\Object;
 
+use Doctrine\Common\Collections\Criteria;
 use Grav\Framework\Collection\ArrayCollection;
 use Grav\Framework\Object\Access\NestedPropertyCollectionTrait;
 use Grav\Framework\Object\Base\ObjectCollectionTrait;
+use Grav\Framework\Object\Collection\ObjectExpressionVisitor;
 use Grav\Framework\Object\Interfaces\NestedObjectInterface;
 use Grav\Framework\Object\Interfaces\ObjectCollectionInterface;
 
@@ -36,6 +38,39 @@ class ObjectCollection extends ArrayCollection implements ObjectCollectionInterf
         $this->setKey($key);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function matching(Criteria $criteria)
+    {
+        $expr     = $criteria->getWhereExpression();
+        $filtered = $this->getElements();
+
+        if ($expr) {
+            $visitor  = new ObjectExpressionVisitor();
+            $filter   = $visitor->dispatch($expr);
+            $filtered = array_filter($filtered, $filter);
+        }
+
+        if ($orderings = $criteria->getOrderings()) {
+            $next = null;
+            foreach (array_reverse($orderings) as $field => $ordering) {
+                $next = ObjectExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next);
+            }
+
+            uasort($filtered, $next);
+        }
+
+        $offset = $criteria->getFirstResult();
+        $length = $criteria->getMaxResults();
+
+        if ($offset || $length) {
+            $filtered = array_slice($filtered, (int)$offset, $length);
+        }
+
+        return $this->createFrom($filtered);
+    }
+
     protected function getElements()
     {
         return $this->toArray();

+ 4 - 4
system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php

@@ -95,10 +95,10 @@ trait ObjectPropertyTrait
     }
 
     /**
-     * @param string $property      Object property to be fetched.
-     * @param mixed $default        Default value if property has not been set.
-     * @param bool $doCreate        Set true to create variable.
-     * @return mixed                Property value.
+     * @param string $property          Object property to be fetched.
+     * @param mixed $default            Default value if property has not been set.
+     * @param callable|bool $doCreate   Set true to create variable.
+     * @return mixed                    Property value.
      */
     protected function &doGetProperty($property, $default = null, $doCreate = false)
     {

+ 4 - 5
system/src/Grav/Framework/Route/Route.php

@@ -178,7 +178,7 @@ class Route
      */
     public function withGravParam($param, $value)
     {
-        return $this->withParam('gravParams', $param, $value);
+        return $this->withParam('gravParams', $param, null !== $value ? (string)$value : null);
     }
 
     /**
@@ -222,17 +222,16 @@ class Route
     protected function withParam($type, $param, $value)
     {
         $oldValue = isset($this->{$type}[$param]) ? $this->{$type}[$param] : null;
-        $newValue = null !== $value ? (string)$value : null;
 
-        if ($oldValue === $newValue) {
+        if ($oldValue === $value) {
             return $this;
         }
 
         $new = clone $this;
-        if ($newValue === null) {
+        if ($value === null) {
             unset($new->{$type}[$param]);
         } else {
-            $new->{$type}[$param] = $newValue;
+            $new->{$type}[$param] = $value;
         }
 
         return $new;

+ 17 - 0
system/src/Grav/Framework/Route/RouteFactory.php

@@ -28,6 +28,23 @@ class RouteFactory
         return new Route($parts);
     }
 
+    public static function createFromString($path)
+    {
+        $path = ltrim($path, '/');
+        $parts = [
+            'path' => $path,
+            'query' => '',
+            'query_params' => [],
+            'grav' => [
+                'root' => self::$root,
+                'language' => self::$language,
+                'route' => $path,
+                'params' => ''
+            ],
+        ];
+        return new Route($parts);
+    }
+
     public static function getRoot()
     {
         return self::$root;

+ 340 - 0
system/src/Grav/Framework/Session/Session.php

@@ -0,0 +1,340 @@
+<?php
+/**
+ * @package    Grav\Framework\Session
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Framework\Session;
+
+/**
+ * Class Session
+ * @package Grav\Framework\Session
+ */
+class Session implements SessionInterface
+{
+    /**
+     * @var bool
+     */
+    protected $started = false;
+
+    /**
+     * @var Session
+     */
+    protected static $instance;
+
+    /**
+     * @inheritdoc
+     */
+    public static function getInstance()
+    {
+        if (null === self::$instance) {
+            throw new \RuntimeException("Session hasn't been initialized.", 500);
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function __construct(array $options = [])
+    {
+        // Session is a singleton.
+        if (\PHP_SAPI === 'cli') {
+            self::$instance = $this;
+
+            return;
+        }
+
+        if (null !== self::$instance) {
+            throw new \RuntimeException('Session has already been initialized.', 500);
+        }
+
+        // Destroy any existing sessions started with session.auto_start
+        if ($this->isSessionStarted()) {
+            session_unset();
+            session_destroy();
+        }
+
+        // Set default options.
+        $options += array(
+            'cache_limiter' => 'nocache',
+            'use_trans_sid' => 0,
+            'use_cookies' => 1,
+            'lazy_write' => 1,
+            'use_strict_mode' => 1
+        );
+
+        $this->setOptions($options);
+
+        session_register_shutdown();
+
+        self::$instance = $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getId()
+    {
+        return session_id();
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setId($id)
+    {
+        session_id($id);
+
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getName()
+    {
+        return session_name();
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setName($name)
+    {
+        session_name($name);
+
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setOptions(array $options)
+    {
+        if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+            return;
+        }
+
+        $allowedOptions = [
+            'save_path' => true,
+            'name' => true,
+            'save_handler' => true,
+            'gc_probability' => true,
+            'gc_divisor' => true,
+            'gc_maxlifetime' => true,
+            'serialize_handler' => true,
+            'cookie_lifetime' => true,
+            'cookie_path' => true,
+            'cookie_domain' => true,
+            'cookie_secure' => true,
+            'cookie_httponly' => true,
+            'use_strict_mode' => true,
+            'use_cookies' => true,
+            'use_only_cookies' => true,
+            'referer_check' => true,
+            'cache_limiter' => true,
+            'cache_expire' => true,
+            'use_trans_sid' => true,
+            'trans_sid_tags' => true,           // PHP 7.1
+            'trans_sid_hosts' => true,          // PHP 7.1
+            'sid_length' => true,               // PHP 7.1
+            'sid_bits_per_character' => true,   // PHP 7.1
+            'upload_progress.enabled' => true,
+            'upload_progress.cleanup' => true,
+            'upload_progress.prefix' => true,
+            'upload_progress.name' => true,
+            'upload_progress.freq' => true,
+            'upload_progress.min-freq' => true,
+            'lazy_write' => true,
+            'url_rewriter.tags' => true,        // Not used in PHP 7.1
+            'hash_function' => true,            // Not used in PHP 7.1
+            'hash_bits_per_character' => true,  // Not used in PHP 7.1
+            'entropy_file' => true,             // Not used in PHP 7.1
+            'entropy_length' => true,           // Not used in PHP 7.1
+        ];
+
+        foreach ($options as $key => $value) {
+            if (is_array($value)) {
+                // Allow nested options.
+                foreach ($value as $key2 => $value2) {
+                    $ckey = "{$key}.{$key2}";
+                    if (isset($value2, $allowedOptions[$ckey])) {
+                        $this->ini_set("session.{$ckey}", $value2);
+                    }
+                }
+            } elseif (isset($value, $allowedOptions[$key])) {
+                $this->ini_set("session.{$key}", $value);
+            }
+        }
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function start($readonly = false)
+    {
+        // Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836
+        if (isset($_COOKIE[session_name()]) && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[session_name()])) {
+            unset($_COOKIE[session_name()]);
+        }
+
+        $options = $readonly ? ['read_and_close' => '1'] : [];
+
+        $success = @session_start($options);
+        if (!$success) {
+            $last = error_get_last();
+            $error = $last ? $last['message'] : 'Unknown error';
+            throw new \RuntimeException('Failed to start session: ' . $error, 500);
+        }
+
+        $params = session_get_cookie_params();
+
+        setcookie(
+            session_name(),
+            session_id(),
+            time() + $params['lifetime'],
+            $params['path'],
+            $params['domain'],
+            $params['secure'],
+            $params['httponly']
+        );
+
+        $this->started = true;
+
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function invalidate()
+    {
+        $params = session_get_cookie_params();
+        setcookie(
+            session_name(),
+            '',
+            time() - 42000,
+            $params['path'],
+            $params['domain'],
+            $params['secure'],
+            $params['httponly']
+        );
+
+        session_unset();
+        session_destroy();
+
+        $this->started = false;
+
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function close()
+    {
+        if ($this->started) {
+            session_write_close();
+        }
+
+        $this->started = false;
+
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function clear()
+    {
+        session_unset();
+
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getAll()
+    {
+        return $_SESSION;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getIterator()
+    {
+        return new \ArrayIterator($_SESSION);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function isStarted()
+    {
+        return $this->started;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function __isset($name)
+    {
+        return isset($_SESSION[$name]);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function __get($name)
+    {
+        return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function __set($name, $value)
+    {
+        $_SESSION[$name] = $value;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function __unset($name)
+    {
+        unset($_SESSION[$name]);
+    }
+
+    /**
+     * http://php.net/manual/en/function.session-status.php#113468
+     * Check if session is started nicely.
+     * @return bool
+     */
+    protected function isSessionStarted()
+    {
+        return \PHP_SAPI !== 'cli' ? \PHP_SESSION_ACTIVE === session_status() : false;
+    }
+
+    /**
+     * @param string $key
+     * @param mixed $value
+     */
+    protected function ini_set($key, $value)
+    {
+        if (!is_string($value)) {
+            if (is_bool($value)) {
+                $value = $value ? '1' : '0';
+            }
+            $value = (string)$value;
+        }
+
+        ini_set($key, $value);
+    }
+}

+ 147 - 0
system/src/Grav/Framework/Session/SessionInterface.php

@@ -0,0 +1,147 @@
+<?php
+/**
+ * @package    Grav\Framework\Session
+ *
+ * @copyright  Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
+ * @license    MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Framework\Session;
+
+/**
+ * Class Session
+ * @package Grav\Framework\Session
+ */
+interface SessionInterface extends \IteratorAggregate
+{
+    /**
+     * Get current session instance.
+     *
+     * @return Session
+     * @throws \RuntimeException
+     */
+    public static function getInstance();
+
+    /**
+     * Get session ID
+     *
+     * @return string|null Session ID
+     */
+    public function getId();
+
+    /**
+     * Set session ID
+     *
+     * @param string $id Session ID
+     *
+     * @return $this
+     */
+    public function setId($id);
+
+    /**
+     * Get session name
+     *
+     * @return string|null
+     */
+    public function getName();
+
+    /**
+     * Set session name
+     *
+     * @param string $name
+     *
+     * @return $this
+     */
+    public function setName($name);
+
+    /**
+     * Sets session.* ini variables.
+     *
+     * @param array $options
+     *
+     * @see http://php.net/session.configuration
+     */
+    public function setOptions(array $options);
+
+    /**
+     * Starts the session storage
+     *
+     * @param bool $readonly
+     * @return $this
+     * @throws \RuntimeException
+     */
+    public function start($readonly = false);
+
+    /**
+     * Invalidates the current session.
+     *
+     * @return $this
+     */
+    public function invalidate();
+
+    /**
+     * Force the session to be saved and closed
+     *
+     * @return $this
+     */
+    public function close();
+
+    /**
+     * Free all session variables.
+     *
+     * @return $this
+     */
+    public function clear();
+
+    /**
+     * Returns all session variables.
+     *
+     * @return array
+     */
+    public function getAll();
+
+    /**
+     * Retrieve an external iterator
+     *
+     * @return \ArrayIterator Return an ArrayIterator of $_SESSION
+     */
+    public function getIterator();
+
+    /**
+     * Checks if the session was started.
+     *
+     * @return Boolean
+     */
+    public function isStarted();
+
+    /**
+     * Checks if session variable is defined.
+     *
+     * @param string $name
+     * @return bool
+     */
+    public function __isset($name);
+
+    /**
+     * Returns session variable.
+     *
+     * @param string $name
+     * @return mixed
+     */
+    public function __get($name);
+
+    /**
+     * Sets session variable.
+     *
+     * @param string $name
+     * @param mixed  $value
+     */
+    public function __set($name, $value);
+
+    /**
+     * Removes session variable.
+     *
+     * @param string $name
+     */
+    public function __unset($name);
+}

+ 18 - 0
user/pages/01._recits/_12-juillet-2017/text.md

@@ -0,0 +1,18 @@
+---
+title: '12 juillet 2017'
+image_align: left
+id: rct_12072017
+---
+
+Je suis de retour rue de la boulangerie, à Franciade, la petite boutique où j'avais été lors de ma toute première visite à Saint-Denis. Le Tote bag " la vie dyonisienne" m'accompagne toujours.
+Andreea, avec qui j'avais brièvement échangé lors de cette première visite, est présente dans la boutique et je profite de l'absence de clients pour lui proposer un petit entretien. Elle me raconte alors l'histoire de Franciade, autour de laquelle l'entretien tournera beaucoup. On sent toute l'importance et la place que prend cette association dans sa vie. Je comprends que l’association Franciade a 13 ans et la boutique dans laquelle nous nous trouvons existe depuis 7 ans. Son but est de valoriser le patrimoine de la ville de Saint-Denis et le savoir faire des artisans. Si à l’origine il s’agissait surtout de vendre des céramiques, répliques des objets trouvés dans les fouilles du site archéologique , il y a aujourd’hui beaucoup de savoir-faire différents représentés à travers la boutique.
+Les projets de l’association sont multiples : édition, photographie, écriture collective, chantiers et création diverses.. touchant un panel d’acteurs encore une fois très variés. Pour Andréea, ce sont les années de travail de la directrice qui font qu’elle a aujourd’hui un grand réseau de contact et que l’équipe arrive à impliquer autant de gens. Alors, lorsque je demande si Franciade entretien des liens avec d’autres associations du coin, la réponse est évidente :
+« Oui, bien sûr, on a notre petit réseau comme Artefact, la coopérative Pointcarré, Adada.. il y a énormément d’artistes et d’initiatives ici. Tout le monde connaît quelqu’un qui connaît quelqu’un… et les projets foisonnent ! »
+
+Franciade diffuse par flyers et par Facebook. Placés dans une rue qui n’a pas beaucoup de passage, l’équipe a besoin de ramener des gens de l’extérieur qui ne passeraient pas autrement ici. C’est pourquoi ils utilisent beaucoup les réseaux sociaux, qui leur permettent de rappeler aux gens qu’ils sont présents. Leur site internet, quant à lui, existe surtout comme vitrine et pour archiver les 13 années d’activités. « Avec tous les artistes qui passent, c’est bien d’avoir un endroit ou l’on peut retrouver la trace de tout ça. » 
+Pour Andréea, l’outil numérique a une importance toute singulière. En effet, celle-ci vit loin de sa famille et ne pourrait imaginer son quotidien sans Skype et la possibilité de « se voir » que le logiciel lui offre.  Ses systèmes de communications se mélangent et se croisent au fil de ses besoins.
+En terme de contact local pour mon expérience, et en conclusion de cet entretien, Andréae m’invite à aller à la rencontre les membres de Point Carré.
+
+Je m’y rend directement à la suite de cet échange : c’est juste à côté, rue Gabriel Péri.
+Point Carré est une coopérative d’artisans locaux, avec un espace de vente, de café et un espace de co-working.J’y rencontre Wiebke.Nous échangeons brievement sur le projet mais, trop occupée dans les temps à venir pour envisager un entretien, Wiebke préfére m'envoyer directement vers l'une de ses amies dont un projet récent fait écho au mien. Elle s'appelle Manon, je note ses coordonnées pour plus tard. Wiebke travaille à la Coopérative mais est aussi artiste plasticienne, installée à la Briche Foraine, un lieu qui accueille des ateliers d’artiste dans le nord de Saint Denis. Son activité porte notamment sur le travaille de la laine, en lien avec les Clinamen, des bergers de Saint Denis..
+Pendant notre échange dans le café, un groupe de personnes discute de Royal Deluxe, une troupe de personnages géants articulés, passés au Havre, ou je réside, le week-end précédent. Une jeune femme m’interpelle en plaisantant et m'informe qu'elle est à l'origine des dessins sérigraphiées sur le sac " la vie Dyonisienne" que je porte et qu’elle portait elle aussi lors de sa visite au Havre pour Royal deluxe. Nous sourions à l’idée que nous étions au moins deux au Havre ce week-end là à arborer les couleurs dyonisiennes.

+ 13 - 0
user/pages/01._recits/_12-septembre-2017/text.md

@@ -0,0 +1,13 @@
+---
+title: '12 septembre 2017'
+image_align: left
+id: rct_12092017
+---
+
+Depuis quelques semaines, je suis en contact avec Manon, dont Wiebke de Point Carré m'avait donné le numéro de téléphone. Il y a quelques temps déjà, Manon avait mené un projet de cartographie du réseau artistique de Saint-Denis et il a donc semblé évident à Wiebke que nous aurions beaucoup à échanger. Elle ne s’était pas trompée.
+Après avoir eu un peu de mal à faire concorder nos disponibilités sur Saint-Denis, j'ai rejoint Manon un midi sur son lieu de travail, dans le quartier de La Défense à Paris.
+Installées sur un banc, je l’écoute me raconter comment elle est tombée sous le charme de Saint-Denis au cours du projet qu'elle y a mené :
+« L’idée de l’étude, c’était de comprendre les liens entre les lieux plutôt institutionnels et les lieux plutôt alternatifs. J’allai donc voir soit des lieux, soit des artistes, pour qu’ils me parlent de leur parcours, de leurs réseaux etc.. et c’était génial parce que je rencontrais de personnes en personnes. »
+C’est par le biais de toutes ces rencontres que Manon a pu avoir un apperçu de la richesse de projets, activités et émulations du territoire de Saint Denis.
+« Ce qui me plait aussi là bas c’est que par exemple si je vais au Pavillon je sais que je vais croiser des gens que je connais. J’y rejoins une amie, on est deux, on finit la soirée on est 10 à tables parce que les gens viennent discuter, tu t’es croisé une fois ou deux dans un projet du coin et on fini par boire des coups ensemble. «  Rapidement, elle a décidé de s’y installer et il lui a fallu moins de 8 mois pour se familiariser avec ce nouveau territoire.
+Lorsque Manon décrit sa "ville à echelle humaine" comme un espace pleins de ressources et pleins de vie, sa bienveillance à l’égard de sa nouvelle ville me donne envie de continuer à découvrir les lieux à travers son regard.  Le Pavillon, un café du centre ville, semble être l'un de ses lieux fétiche et je décide donc d’aller m’y installer l'après-midi même, le temps d'un café.

+ 10 - 0
user/pages/01._recits/_13-septembre-2017/text.md

@@ -0,0 +1,10 @@
+---
+title: '13 septembre 2017'
+image_align: left
+id: rct_130917
+---
+
+Cet après midi, j’ai rendez-vous avec Nadia, du centre socioculturel coopératif de Saint-Denis. J'ai eu vent de ce centre sur Facebook, grâce à des "likes" de récents contact dyonisiens, comme Pascale.
+Lorsque j'ai contacté l'équipe du centre par l’adresse mail que j'ai trouvé sur la page Facebook,  Nadia m’a tout de suite répondu et invité à passer directement dans les locaux.
+Arrivée au 28 rue Jean Jaurès je suis reçue très chaleureusement par Nadia. Autour d’un café dans ce lieu encore peu meublé, Nadia entreprend de me retracer l'historique de ce projet coopératif, porté par l'association Coopérence dont elle s’occupe. Anciennement situé rue Gabriel Péri, le centre coopératif se trouve aujourd’hui dans les locaux de La Maison des projets, le temps d’acceder au local – aujourd’hui en travaux - qui lui est destiné place du 8 mai 1945. Aujourd’hui monté en association, le centre a vocation à devenir une SCIC, une société coopérative d’interêt collectif.
+En pleine transition, le lieu est actif pour autant : L’entretien avec Nadia est sans cesse coupé par de nouveaux arrivants, des questions, des tâches à gérer. Une belle ruche se construit, faisant foi de toute l’énergie nécessaire à cet ambitieux projet. En repartant à la fin de cet échange, je rencontre Pascale, qui prépare justement les nouvelles activités qu’elle va commencer à mener dans le local prochainement. 

+ 24 - 0
user/pages/01._recits/_19-juin-2017/text.md

@@ -0,0 +1,24 @@
+---
+title: '19 juin 2017'
+image_align: left
+id: rct_190617
+---
+
+Il y a quelques jours, j'ai échangé avec [Pasacale](#int_pascale) dont j'avais eu vent du travail sur [l'affiche](#img_mj2) de la Maison Jaune.
+En début d'après midi, nous nous retrouvons à la Maison Jaune, où Pascale me fait découvrir le lieu. Elle m’explique : « C’est une maison de quartier qui a ouvert au mois de Novembre. l’idée c’est que cet espace soit mis à disposition des habitants, qu’on y organise des choses mais aussi qu’il y ai de la place pour leurs propres initiatives… »
+Pascale a découvert le lieu en octobre 2016, en passant devant. Elle n’habite pas loin et était justement à la recherche d’un lieu pour son projet de tricot-partage, un projet de tricot solidaire.
+Je lui demande de m’en dire plus sur son projet et fait vite le lien avec le patchwork de formes tricotés, monumental, qui git au sol de la Maison Jaune :
+«  Le projet de tricot solidaire c’était, au vu de la grosse vague de froid, de réaliser ,avec la participation du public, des carrés de laine de 30cm de côté assemblés en couvertures. Ces couvertures sont à destination de la Croix Rouge et distribuées lors des maraudes. L’envie était aussi que chacun, tout âge confondu, puisse participer à sa manière, en venant pour tricoter, pour apprendre ou pour transmettre. »
+Pour se fournir en laine, Pascale a lancé un appel au don dans Saint-Denis, qui a vite débordé du territoire par le biais d’internet. Elle a reçu des colis de Paris, de Bretagne, de Nice..
+« j’ai même eu un message d’une personne dans un petit village d’ardèche, qui m’a écrit en me disant qu’elle tricoterait des petits carrés de laine pour nous les envoyer. »
+Ici, internet est un vecteur indéniable de rencontre, mais le « fil participatif » comme l’appel Pascale, s’est construit de bien des manières. Le projet est nomade dans Saint-Denis, permettant au groupe de rencontrer d’avantage de personnes et d’ouvrir le réseau initial. Les moyens de communication du projet sont aussi divers. Les habitants de Saint-Denis ont vent du projet par des relais Facebook, des affichages en ville, des flyers.. jusqu’à l’impression même du flyer :
+« Il y a quelques temps, chez l’imprimeur, je faisais mes photocopies pour l’évenement à la Maison Jaune. Au moment de régler j’ai posé mes copies sur le comptoir. Une dame était derrière moi et a lu « tricot ». Elle m’a demandé ce qu’était l’événement et elle est venue. Depuis, elle vient régulièrement et nous sommes assez liées. »
+
+Dans le collectif, c’est sur l’application what’s app que la communication interne s'organise. Vers l’extérieur, c’est plutôt Facebook.
+Lorsque nous évoquons les moyens d’échange et de communication à disposition d’une association comme celle-ci, Pascale observe que l’outil numérique leur permet d’agréger des personnes qui ne sont pas forcément disponibles en temps et en heure,  ou n’ont pas la possibilité de se déplacer, mais qui souhaitent quand même être informés, participer et être tenus au courant des activités. 
+Elle note cependant que « la communication papier et la communication numérique sont vraiment complémentaires. Certains utilisent les deux régulièrement mais d’autres sont beaucoup moins familiers de l’outil informatique. Il y a des personnes qui n’ont pas d’e-mail et ne sont joignables que par téléphone, voire qui n’ont pas de téléphone portable. Je crois aussi que c’est bien de ne pas uniformiser l’information.»
+
+Pendant notre entretien, apparaît Sofiane, un habitant impliqué dans le collectif de la Maison Jaune, qui projette d'y organiser des ateliers de patisseries, étant lui-même en formation patissier. 
+On sent dans ce lieu l’émulation de quelque chose qui naît, qui ne sait pas toujours où il va mais qui est bien là.
+Ce premier échange a été très instructif et Pascale m’ayant évoqué plusieurs lieux de Saint-Denis, je profite de l'après-midi pour les repérer. En chemin, je croise des tricoteuses : un petit groupe tricote dans la rue, sur les arbres, les barrières. Ca fait tout de suite écho avec le projet tricot-partage de Pascale, et me pousse à m'arrêter discuter.
+C'est comme ça que je fais la connaissance d'Océane, de l'association Dechets d'arts, qui participe ici à une "opération street mamies", me dit-elle. Elle me laisse sa carte pour que nous nous recontactions plus tard.

+ 24 - 0
user/pages/01._recits/_25-juillet-2017/text.md

@@ -0,0 +1,24 @@
+---
+title: '25 juillet 2017'
+image_align: left
+id: rct_250717
+---
+
+Ce matin je suis allée visiter le 6B, un lieu de création et de diffusion installé dans un ancien immeuble de bureau situé sur les quais dans le quartier de la Gare à Saint-Denis. 
+Ouvert depuis 2010, le lieu dispose d'espaces de travail et de diffusion sur 6 étages et accueille plus de 150 résidents.  Autogéré par ses résidents, il regroupe musiciens, cinéastes, graphistes, artisans, travailleurs sociaux, comédiens, danseurs, peintres, sculpteurs, architectes… et héberge des événements comme des expositions, spectacles, séminaires, colloques.. lance des actions culturelles de proximité, festivals éphémères,Son objectif est de proposer une culture à portée de tous. 
+Je n’y croise pas grand monde, c’est les vacances, mais découvre les étages par une petite visite improvisée.
+
+Cette après-midi, je quitte Saint-Denis pour retourner dans Paris : j'ai rendez-vous avec Victoria - contactée via Marie.P. Victoria était étudiante à Paris 8 à Saint Denis mais vit dans Paris. Je la retrouve sur la place de la République et nous nous installons à la terasse du café Pierre. 
+Dès nos premiers échanges, ce qu’elle me dit de sa connaissance du territoire de Saint Denis me confirme ce qu’avais pu remarquer Marie : « Mes premières années à Paris 8, je ne connaissais que la sortie du métro université et les alentours de Paris 8.. j’ai découvert petit à petit Saint Denis par des amis de la fac qui y habitent. Au début je savais à peine dans quelle direction était le centre ville. »
+Au fil du temps, elle a ainsi découvert Saint-Denis, surtout à travers les logements de ses amis, jusqu’à ce qu’elle découvre l’Attiéké. Déclaré « Centre-Social Auto-Organisé », l’Attiéké réunit des personnes «  avec ou sans papiers / avec ou sans-logis » travaillant ou non, en réquisitionnant un lieu jusque là resté vide, abandonné. Devenant un logement pour tous, le lieu devient aussi un espace de rencontre, d’échange, d’entre-aide et d’activités publiques.
+C’est au moment de l’expulsion du lieu que Victoria a fait la connaissance des ses occupants.
+Des étudiants de Paris 8 s’organisaient alors pour les aider à occuper des lieux pour leur retrouver un espace de vie. «  Un groupe assez militant s’est formé et j’ai découvert les activités et la diversité du lieu. J’y suis beaucoup allée. » 
+Lorsque nous évoquons les canaux de communications liées à un tel lieu, victoria m’explique que dans un système précaire comme celui-là, la communication investit peu les plate-formes comme Facebook avec les événements etc. Que pllus discrètement, ça fonctionne aussi très bien.
+« On se tiens au courant quand on se rencontre ou on s’envoie quelques messages, des mails ou des sms. On utilise beaucoup l’affiche dans les quartiers donc ça a tendance à rester très local et à fédérer des groupes d’habitants. »
+Au cours de son cursus à Paris 8, c’est autour d’un projet co-créé avec quelques autres étudiants que Victoria a pu explorer le collectif et l’alternatif : Le Collectif Kabane.
+« A l’université j’étais en cours d’art mais on avait ni atelier ni espace de convivialité pour se retrouver ou travailler en groupe. On a décidé de s’aménager un espace pour ça, tout autant symbolique que concret, et on a commencé à construire une cabane. »
+Si la cabane a disparu, le collectif , lui, a continué à se documenter sur les réseaux alternatifs, les lieux auto-gérés. Kabane se définit comme un collectif expérimental nomade, qui travaille sur la co-production de nouvelles formes de vie et de rapport social.
+Le partage de connaissance est au coeur de leur démarche, et s’exprime aussi dans leur choix d’utilisation du web et des outils numériques : « Pour la Kabane on a pas mal utilisé les outils Framasoft, des framapad etc, on a essayé d’éviter les google docs et compagnie au sein du collectif car la question du logiciel libre et de l’outil collaboratif était important pour nous. Pour faire le lien avec d’autres étudiants on utilisait surtout les listes de mails. On a beaucoup de gens qui ne sont pas sur Facebook dans nos contacts et on essaye aussi de s’émanciper car on a tendance à s’enfermer dans les infos qui s’y trouves et à ne plus chercher l’information ailleurs.»
+L’une de leur action retient mon attention : Le projet « Copiothèque » au coeur duquel le partage de connaissance prend toute sa place, mélangeant la rencontre physique et le partage numérique.
+A la fois « lieu de rencontre nomade » qui invite à partager de la documentation sur un tapis, au sol dans l’espace public, la copithèque est aussi un lieu d’échange numérique via une Pirate Box ou il est possible d’envoyer ou télécharger du contenu en s’y connectant.
+Lorsque je quitte Victoria, elle prépare son départ pour un tour de France des lieux alternatifs de quelques mois..

+ 12 - 0
user/pages/01._recits/_26-juillet-2017/text.md

@@ -0,0 +1,12 @@
+---
+title: '26 juillet 2017'
+image_align: left
+id: rct_260717
+---
+
+En ce début d’ après-midi,  j'ai rendez-vous avez Océane, rencontrée quelques semaines plus tôt devant la Maison des Séniors. Elle m'avait proposé de la rejoindre sur un chantier qu’elle mène pour le collectif MundGawi. En arrivant, j'y rencontre Laurent, artisant-sculpteur qui participe lui aussi au chantier et qui m’accueille avant l’arrivée d’Océane. Je lui propose de se joindre à nous le temps de l’entretien. Océane fait partie de l’association Dechets d’Arts, dont l’objectif est de sensibiliser les citoyens à des comportements eco-résponsables autour du dechet et au travers de la création artistique. La démarche de l’association est de faire du dechet une resssource avec une approche poétique, tout en mettant en place des ateliers créatifspour apprendre des techniques de réemploi.
+« Le projet des streeet mamies durant lequel tu m’as croisé en fait partie. On part de la technique du crochet adapté à des materiaux de récupération comme du sac plastique et on invite des séniors femmes pour aussi réaffirmer la place et des séniors et de la femme dans l’espace public. C’est un vecteur de rencontre. »
+Océane est impliquée dans beaucoup d’actions et de collectifs dans Saint-Denis. Il lui est arrivé de rencontrer des gens d’abord sur des plateformes numériques pour la construction de projets. « Pour certains on a d’abord discuté que ce soit sur des commentaires Facebook ou sur l’application what’s app. Je me suis notamment rapproché d’une structure de maison de quartier coopérative qui est en train de se monter pas loin d’ici et eux, c’est leur principal moyen de communiquer donc pour beaucoup j’ai fait leur connaissance sur what’s app et je les rencontre au fur et à mesure. »
+Laurent habite Paris et se rend à Saint-Denis essentiellement pour se rendre au 6B, ou il a son atelier, mais connais très peu Saint-Denis. Le chantier sur lequel nous nous trouvons est une occasion pour lui d’enclancher des collaborations avec d’autres créateurs du site. 
+Océane en revanche habite et connaît bien sa ville de Saint Denis, elle me livre les endroits liés à son quotidien :
+« L’école rue du corbillon, le college jsd place de la résistance, le petit café au Pavillon éventuellement en accompagnant mes filles le matin. Un petit tour au Point Carré de temps en temps, parce que je suis reliée au projet aussi. Le théâtre Gerard Phillipe, pas de manière assidue mais régulièrement. L’Adada, l’artefact 93 , le parc de la légion d’honneur avec mes filles ou simplement la place de la basilique pour faire du roller. Le 6B également, parce que j’ai mon bureau là bas et pour participer à des événement. C’est là où on s’est rencontrés avec Laurent. Le chapiteau Rajganawak aussi, qui a ouvert récemment. Il y a un cabaret, des événements, des cours de yoga..et enfin les petits commerces de proximité autour de chez moi. »

+ 15 - 0
user/pages/01._recits/_3-mai-2017/text.md

@@ -0,0 +1,15 @@
+---
+title: '3 mai 2017'
+image_align: left
+id: rct_030517
+---
+
+En cette première semaine de mai, je réfléchi à la construction concrête du projet.
+Après un premier – et rapide- tour d'horizon de Saint-Denis, il m'apparait assez clairement que la ville possède un tissu associatif énorme, très diversifié et étendu. On peut sans doute en déduire une attitude plutot collective des riverains et l’existence d’un grand nombre d'activités et de liens - discrets ou non - entre les habitants de Saint-Denis.
+Considérant le territoire sous cet angle, je comprend l'aspect monumental de tenter de cartographier ces actions discrètes, ce réseau de convivialité. Comment s’immiscer assez profondément dans la vie dyonisienne pour en ressortir ses anecdotes, ses "activités informelles" entre voisins, ses discussions de cafés.. sans même y habiter ?
+Il me semble par ailleurs tout à fait subjectif d'affirmer à un moment donné une cartographie de la vie d’un territoire, quel qu’il soit. Tout ce que j'aurais raté, tous ceux que je n'aurais pas croisé ne feraient pas partie de cette cartographie. Quel sens en tirer ?
+Je décide d’aborder le projet d'une autre manière. Non pas avec l'ambition de cartographier ou d’inventorier des liens ou des actions mais plutot avec l'envie de vivre une expérience au cœur de Saint-Denis. Celle-ci se construira au rythme de mes allées et venues. Je m'éloigne ainsi d'une intention de photographier un réseau à un moment précis, avec plutôt l’envie de me laisser porter par celui-ci. Ou plutôt par l’un d’entre eux. Par un réseau, celui dans lequel je cheminerai au fil du hasard, laissant  l'intuition, le feeling des rencontres et le temps œuvrer. 
+Par ma première entrée qu'est Synesthésie dans cet environnement de Saint-Denis, je rencontre d’ors et déjà quelques personnes, et je prends le parti de discuter avec celles-ci et de leur demander à chacune de me présenter quelqu’un d’autre,etc,etc. C'est dans ce réseau que je cheminerais, le découvrant au fur et à mesure.
+Maintenant, quel sera l'objet des rencontres ? Il m'importe de découvrir les habitants comme des acteurs de la vie de Saint-Denis en comprenant le rôle qu'ils y jouent, quel qu'il soit. Mais je les questionnerais aussi sur leur rapport au numérique, afin de tenter de cerner quel place a celui-ci dans leurs échanges au quotidien.
+Mon intuition est qu'il est aujourd'hui difficile de séparer un réseau dit "physique" d’un réseau " virtuel" ou "numérique" mais que ceux-ci s'imbriquent, s'hybrident, se complètent.
+A travers ces échanges je tenterais ainsi de questionner les outils numériques comme vecteurs de convivialité - ou non.

+ 8 - 0
user/pages/01._recits/_3-octobre-2017/text.md

@@ -0,0 +1,8 @@
+---
+title: '3 octobre 2017'
+image_align: left
+id: rct_03102017
+---
+
+Retour au Pavillon, cette fois accompagnée. J’y rejoint Solen, que je rencontre sur les recommendations de Manon. Nous n’avons pas choisi ce point de rencontre au hasard : Solen habite juste à côté et habituée des lieux, c’est justement ici qu’elle avait rencontré Manon, par le biais d’une amie. Solen est arrivée à Saint Denis il y a quelques années pour se rapprocher de son lieu de travail, plus au Nord. En cherchant un logement, elle a été agréablement surprise par la ville dont elle s’était forgée une image négative. N’y connaissant personne au départ, c’est principalement à travers le réseau de l’AMAP qu’elle s’est ouverte à de nouvelles activités et rencontres sur Saint-Denis. Plus tard, une période de chômage l’a encouragée à s’investir davantage dans la vie associative du territoire et elle a notamment pu réfléchir à un projet de café culturel, toujours en cours avec d’autres habitants, dont Olivier,  que j’avais déjà prévu de rencontrer le lendemain. Lorsque je fais la remarque, Solen me confie que c’est justement la personne vers qui elle m’aurait invité à aller. 
+Cette coïncidence me fait souligner que durant ces semaines passées ici je suis souvent retombé sur les même personnes et qu’ainsi je perçois bien que je chemine dans un réseau sans tellement en croiser d’autres. Solen réagit : « Je crois que partout, dans les réseaux de personnes, on peut vite avoir l’impression d’un réseau fermé sur lui-même . C’est sûr qu’au sein de l’AMAP par exemple, ou des gens que je fréquente on retombe sur des profils assez similaires, socialement, culturellement.. qu’il y a plein d’endroits qui ont beau être proches de chez moi , je ne les fréquente pas, parce que naturellement, on va toujours vers ce qu’on connaît je pense, les lieux qui nous sont familiers. Pour autant, entre chacun de ces réseaux, il y a des ponts qui se créent.  »

+ 15 - 0
user/pages/01._recits/_4-octobre-2017/text.md

@@ -0,0 +1,15 @@
+---
+title: '4 octobre 2017'
+image_align: left
+id: rct_04102017
+---
+
+Pour la première fois, je ne m’arrête pas à la station de métro Basilique mais pousse jusqu’à Université : Ce matin, je rejoint Marie.W dont le contact m’avait été donné par Victoria.
+Anciennement étudiante à Paris 8 et vivant non loin, Marie m’a donné rendez-vous devant l’université pour me donner l’occasion de la découvrir.
+En l’y attendant je découvre des affiches de l’Attiéké datant de fin septembre, faisant état de leur recherche de lieux dont ils ont  «  besoin pour habiter le monde ». En attendant, accompagné d’ateliers vélos, cantine, kiosque,..le centre social auto-géré continue dans la rue.
+Lorsque Marie me rejoint nous entrons dans l’université en quête d’un lieu pour discuter. La cafeteria en travaux nous pousse à chercher une salle vide pour s’y poser. Marie m’assure qu’elle n’avait pas de difficulté à le faire à l’époque de ses études. Pourtant au bout d’une dizaine de minutes, après avoir dérangé quelques classes, on ne trouve toujours pas notre bonheur.
+Marie a alors l’idée d’aller voir vers les studios, ou elle a passé beaucoup de temps et dont elle connait biens les équipes, si un bureau serait libre. On est invitées à utiliser un studio d’enregistrement radio, avec tout le dispositif qui va avec. L’expérience est surprenante : nous nous retrouvons toutes deux autour de cette table, casques sur la tête, et on se prête au jeu..
+Marie habite Saint-Denis depuis 5 ans :  « J’y suis venu parce que je voulais aller à l’université de Paris 8 et pour moi c’était très important d’habiter au même endroit que là ou j’étudiais pour pouvoir créer des passerelles entre l’université et mon chez moi, pour pouvoir inviter des gens, avoir un rapport avec l’extérieur. » Comme beaucoup, elle a mis du temps à découvrir Saint-Denis au-delà de l’université, d’autant plus qu’elle habite dans le secteur proche de l’université. La taille de Paris-8, véritable université-monde, demande déjà d’y consacrer beaucoup de temps.
+Plus tard, c’est à travers des lieux comme le bar Le Pavillon, la galerie ADADA, le cinéma l’écran ou encore le théatre Gerard Philippe qu’elle a pu rencontré davantage de personnes dans le centre de Saint-Denis..mais aussi via l’AMAP « Court-circuit » de Saint-Denis, qui, pour elle comme pour beaucoup, représente bien plus qu’une question d’alimentation.
+« C’est surtout un moyen de se rencontrer et de se connaître entre dyonisiens. On a une liste mail où on se file des infos, qu’on ai quelques chose à donner, qu’on cherche un medecin, un plombier..qu’on informe d’une date de concert.. tout ça passe par la liste de discussion. Mon dernier colocataire je l’ai trouvé par ce biais là.»
+Par ailleurs, le rapport qu’entretien Marie au numérique et plus généralement à la connexion au monde est interessant : Par choix, elle n’a longtemps pas eu de téléphone, aujourd’hui elle en possde un mais sans accè à un internet. Il y a encore quelques temps, elle se rendait au Taxiphone – cyber café de son quartier – pour acceder à sa boîte mail et effectuer des recherches. Par peur de la surconnexion, de se couper du contact direct avec le monde, Marie multiplie les astuces pour ne pas faciliter sa connexion numérique. Pour autant, des paradoxes se créent. Elle possède notamment une web-trotter, inititalement acquérie pour ses séjours à l a campagne, qui au contraire lui permet de se connecter partout.. mais uniquement sur ordinateur. Elle remarque d’elle même que les limites qu’elle se pose bougent avec le temps :  «. je triche un peu, c’est en train d’évoluer doucement. »

+ 15 - 0
user/pages/01._recits/_5-octobre-2017/text.md

@@ -0,0 +1,15 @@
+---
+title: '5 octobre 2017'
+image_align: left
+id: rct_05102017
+---
+
+Lorsque j’avais rencontré Pascale la première fois, elle m’avait évoqué la crêperie « La Bigoudène » comme un lieu assez fréquenté par les Dyonisiens. Je profite d’avoir du temps ce midi pour y déjeuner.
+Un rayon de soleil bienvenu me permet de m’installer en terrasse, avec vue sur la Basilique. Autour de moi, j’entend les discussions d’un service de mairie venu déjeuner là.
+Il est 16H lorsque je retrouve Olivier au 4 Place Paul Langevin, local de l’AMAP Court-circuit.
+J’ai pu rencontrer Olivier par le biais d’Elodie, membre de synsthésie. Elle s’est installée recemment sur Saint-Denis dans la colocation d’Olivier dont elle a eu vent par la mailing list de l’AMAP.
+Ce n’est donc pas par hasard que nous nous retrouvons dans ce local, car si ma rencontre avec olivier est relié à l’Amap, pour lui,  c’est toute sa vie dyonisienne qui l’est.
+En effet, arrivé il y a 3 ans à Saint-Denis, c’est par la rencontre d’une « amapienne » comme il dit, qu’il s’est inscrit à court-circuit et qu’il a pu s’ouvrir sur la ville :
+« C’est là que mon réseau Dyonisien à commencé à s’ouvrir. Au début je venais pour mes paniers de légumes et petit à petit j’ai commencé à faire connaissance avec les gens, à utiliser le réseau. La liste de discussion est très active, sur une multitude de sujets. On est 700 inscrits sur la liste, donc 700 foyers, ça donne une nombre de personnes impressionnant. Il y a même un framapad pour se communiquer tous les bons plans, les bonnes adresses d’artisants, de restaurants, de spécialistes médicaux etc.. »
+Si ce n’est pas la première fois qu’on me parle de l’AMAP comme d’un réseau qui permet de s’ouvrir sur Saint-Denis je me pose pour autant la question de l’envergure de cette ouverture : Est-ce un réseau particulier ou un point de rencontre entre des réseaux, formé personnes d’horizons, modes de vies et cultures très différentes ? Olivier me fait part d’un constat surprenant : « L’AMAP est ici situé dans la cité Langevin, et je crois qu’on a aucun inscrit dans les habitants de la cité, ce qui peut être assez étonnant.. » L’AMAP est géograhiquement située dans cette cité par l’opportunité que représentait ce local, mais ne touche pas pour autant la population avoisinante. Je le questionne justement sur cette problématique de mixité sociale et culturelle, dont j’ai cru comprendre par solen et leur projet de café culturel qu’elle lui était importante. Olivier a en effet cette envie de créer un lieu féderateur, capable de rassembler et nourir les gens en toute simplicité. S’il est davantage séduit par une idée de lieu nomade, pouvant aller aux devant des autres, partir à la rencontre d’espaces plus isolés, il n’exclu pas la piste de s’associer avec le nouveau centre social coopératif porté par Nadia et envisage de porter la partie café.
+Olivier avait entendu parler de Nadia et son projet au hasard d’une conversation avec une amapienne.. Nous concluons l’échange sur son constat : « Avoir du réseau ça simplifie vraiment les choses. » 

+ 11 - 0
user/pages/01._recits/_5-septembre-2017/text.md

@@ -0,0 +1,11 @@
+---
+title: '5 septembre 2017'
+image_align: left
+id: rct_050917
+---
+
+Il est 14H lorsque je pars à la rencontre de Martine, dont j'ai eu le contact par Victoria. Nous avons rendez-vous au café le Basilic, sur la place de la Basilique à Saint-Denis. Nous y commandons deux cafés.
+L’entretien avec Martine est dense: Sociologue et habitante de Saint-Denis depuis 1986, elle porte un regard riche et critique sur sa ville. Malgrès tout, elle confesse : «  Moi j’habite ici depuis 86 et finalement, je connais un peu le centre ville car j’y habite, je connais la fac parce que j’y ai été amené à y bosser, mais il y a des quartiers,  ou je n’ ai jamais mis les pieds.  En centre ville, on a tendance à dire qu’« on » est Saint Denis. Mais en réalité lorsque l’on parle de Saint Denis, on parle ou d’une grande entité abstraite ou d’un petit bout de quartier qu’on connaît. »
+Sur ce constat, j’évoque la raison de ma venue et la notion de réseau qui se dégage du projet. Martine souligne :
+« L’idée de réseau, c’est compliqué car c’est une notion abstraite.  Il y a une époque ou j’entendais beaucoup « Saint Denis est un village ». Saint-Denis est un village, c’est vrai, mais d’un petit noyau de gens. Par rapport à la Grande ville c’est pas vraiment un village, peut etre DES villages.. »
+Cette remarque m’invite à préciser l’objectif de cette expérience: La réalité est que je viens à la rencontre d’un réseau de Saint-Denis, parmis tant d’autres. Ce sont mes premières rencontres qui on axé un cheminement vers d’autres personnes et le réseau qu’elles constituent. L’expérience que je mène ici ne pourra donc jamais prétendre à avoir rencontré «les habitants de Saint-Denis » mais bien « des »habitant de , voire d’un, Saint-Denis.

+ 13 - 0
user/pages/01._recits/_6-juillet-2017/text.md

@@ -0,0 +1,13 @@
+---
+title: '6 juillet 2017'
+image_align: left
+id: rct_060717
+---
+
+Ce matin,  j’ai rendez-vous par Skype avec [Marie.P](), avec qui j’ai brièvement échangé par mail, pour un entretien à distance. La sonnerie retenti sur mon ordinateur, nous sommes « là » et je découvre le visage de [Marie](), dans son bureau baigné de soleil. Je suis au [Havre,]() elle à Paris, et durant l’heure qui suit nous discuterons ensemble de Saint-Denis. Je lui explique le projet en quelques mots et l’invite à se présenter.
+Marie enseigne à [Paris 8]() depuis 4 ans mais n’habite pas Saint-Denis. Lorsqu’elle a commencé à y enseigner, elle a vite constaté qu’avec le metro qui va directement de Paris à l’université, il est facile de circonscrire son réseau exclusivement à l’université sans jamais marcher autour de la fac. «  Ce que j’essaye de faire dans certains de mes séminaires à l’université c’est justement d’essayer d’en sortir et de comprendre comment on peut travailler dehors. On a choisi le centre ville de Saint-Denis où on a travaillé notamment avec Synesthésie et La Maison Jaune. »
+Durant l’entretien, nous abordons le rapport au numérique et Marie m’explique qu’elle considère qu’Internet fluidifie la communication mais qu’il y est aussi beaucoup question de partage, de mise en commun au-delà de la mise en relation. Elle précise :
+« Je m’en sers aussi beaucoup pour faire des recherches, comme un espace de connaissance.
+J’actualise mon [site internet]() et j’ai deux blogs pédagogiques ou j’essaye de poster des choses « utiles ». Internet propose des lieux de recherche sur la pédagogie collective ou je peux aussi toucher plus d’étudiants.»
+Communiquant essentiellement par mail, Marie estime qu’Internet est fondamental pour elle. Avec ses étudiants, les outils numériques lui permettent par ailleurs de travailler en commun sans que tout le monde soit disponible au même moment, de faire vivre un projet avec plus d’autonomie.
+C’est d’ailleurs avec l'une de ses anciennes étudiantes, [Victoria](), que Marie choisi de me mettre en contact, par [mail]().

+ 12 - 0
user/pages/01._recits/_6-juin-2017/text.md

@@ -0,0 +1,12 @@
+---
+title: '6 juin 2017'
+image_align: left
+id: rct_060617
+---
+
+Je fais aujourd'hui la connaissance de [Loyce](), en stage à Synesthésie et étudiante à [Paris 8](). 
+Je teste avec elle le format d’interview que j'ai imaginé et la mise en contact avec une tierce personne. Elle m’envoie vers [Marie.P](), artiste et enseignante à Paris 8 : elle a été l'une de ses étudiantes.
+[Bachir,]() qui a justement travaillé avec elle lors d’un atelier à Synesthésie, nous met en lien directement par mail.
+Dans l'après-midi, je découvre l’emplacement de la [Maison Jaune](), alors fermée. Sur la devanture, une affiche présente un projet de [Tricot Partage](img_tp), porté par une certaine [Pascale](). Je retrouve les infos sur la[ page Facebook]() et contacte Pascale pour lui proposer une rencontre. 
+Peu familière de l’environnement, l’aspect labyrinthique de la [Dalle](), faite de coins et de recoins, ne m’invite pas à m’y ballader : la formation de l’espace accentue chez moi le sentiment d’être perdue.
+Avant de repartir, je déambulle dans le centre de Saint-Denis, marchant au hasard. J’essaye de me rendre plus familière du centre mais ai tendance à graviter peu loin de la [station de métro ]()que je connais. 

+ 46 - 24
user/pages/02._interviews/_andrea/text.md

@@ -1,53 +1,75 @@
 ---
-title: Andrea
+title: Andreea
 image_align: left
 id: int_andreea
 ---
 
-Andreea Cerguta ; [ 11 Juillet 2017]
-rencontrée le 11 Juillet 2017 dans la boutique Franciade.
+Rencontrée le 11 Juillet 2017 dans la boutique [Franciade](#mp_franciade#img_fr).
 
-J’avais **auparavant** découvert Franciade en faisait des _recherches_ web sur Saint Denis et m’était rendue à Franciade lors de ma premiere visite à Saint-Denis.
+J’avais découvert [Franciade](#img_fc) auparavant en faisait des [recherches]() internet et m’y était [rendue](#mp_franciade) lors de ma [premiere visite](#rct_110417) à Saint-Denis.
+
+
+**Êtes-vous à l’origine de Franciade ?**
 
-**Depuis quand travaillez-vous à Franciade, est-ce que vous êtes à l’origine de cette structure ?**
 Non pas du tout, moi je suis arrivée il y a un peu plus de trois ans, mais ça va faire treize ans que l’association existe. Tout était déjà lancé quand je suis arrivée.
 
-Est-ce que vous pouvez m’expliquer un peu l’histoire de l’association ?
+
+**Est-ce que vous pouvez m’expliquer un peu l’histoire de l’association ?**
+
 L’association Franciade a donc 13 ans et la boutique dans laquelle nous nous trouvons a déjà 7 ans. Le but de l’association est de valoriser le patrimoine de la ville de Saint-Denis et le savoir faire des artisans, à l’origine c’était surtout des céramiques qui sont enfait des répliques des objets trouvés dans les fouilles du site archéologique mais on ne se restreint pas aux céramiques. Beaucoup de savoir-faires différents sont représentés à travers la boutique.
 
-J’imagine que cette diversité d’acteurs amène aussi une pluralité de projets ?
-Oui tout à fait on ne reste pas que dans la production d’objets, on participe a beaucoup de projets qui n’ont rien a voir avec la céramique. On est en ce moment sur le projet «  la basilique vue de chez moi »  avec les habitants qui ont vus sur la fameuse basilique.
+
+**J’imagine que cette diversité d’acteurs amène aussi une pluralité de projets ?**
+
+Oui tout à fait, on ne reste pas que dans la production d’objets: on participe a beaucoup de projets qui n’ont rien a voir avec la céramique. On est en ce moment sur le projet « la basilique vue de chez moi »  avec les habitants qui ont vus sur la fameuse basilique.
 C’est un projet d’édition de photo et de témoignage et leur rapport a la basilique.
 
-Comment est-ce que vous réunissez ces différents acteurs  ?
-Ce sont les années de travail de la directrice Carine qui font qu’elle a un grand réseau de contact, et parfois on répond à des appels à projets, d’autres fois on nous contacte de l’extérieur.
-On fonctionne beaucoup avec les appels à projets car la [boutique](/?rct=r110417&map=mp_maison_jaune) ne suffit pas à faire vivre l’assocation et payer tous les salaires de Franciade. On a aussi des projets qu’on auto finance et qu’on lance nous même.
 
-Comment est-ce que vous vous ancrez dans le territoire ?
-Déjà, nos clients se fidélisent car ils nous font confiance et parlent de nous à l’extérieur. Nous on essaye de se faire connaître aussi par des flyers et par Facebook car on est pas très bien placés et on a pas beaucoup de passage. On doit donc ramener des gens de l’extérieur qui ne passeraient pas autrement ici. On utilise donc les réseaux sociaux, ça nous permet de rappeller aux gens qu’on est présents.
+**Comment est-ce que vous réunissez ces différents acteurs  ?**
+
+Ce sont les années de travail de la directrice qui font qu’elle a un grand réseau de contact, et parfois on répond à des appels à projets, d’autres fois on nous contacte de l’extérieur.
+On fonctionne beaucoup avec les appels à projets car la [boutique](https://www.franciade.fr?target=_blank) ne suffit pas à faire vivre l’assocation et payer tous les salaires de Franciade. On a aussi des projets qu’on auto finance et qu’on lance nous même.
+
+
+**Comment est-ce que vous vous ancrez dans le territoire ?**
+
+Déjà, nos clients se fidélisent car ils nous font confiance et parlent de nous à l’extérieur. Nous on essaye de se faire connaître aussi par des flyers et par [Facebook](https://www.facebook.com/profile.php?id=100010285387327?target=_blank) car on est pas très bien placés et on a pas beaucoup de passage. On doit donc ramener des gens de l’extérieur qui ne passeraient pas autrement ici. On utilise donc les[ réseaux sociaux](), ça nous permet de rappeller aux gens qu’on est présents.
+
+
+**C’est plus efficace pour vous les réseaux sociaux que le site internet ?**
 
-C’est plus efficace pour vous les réseaux sociaux que le site internet ?
 Oui car on en a pas la même utilité, sur le site on présente surtout l’association et ses projets mais on ne présente pas les produits que l’on vend. C’est surtout pour archiver les 13 années d’activités, avec tous les artistes qui passent, c’est bien d’avoir un endroit ou l’on peut retrouver la trace de tout ça.
 
-Je vois sur le comptoir un flyer qui indique «  racontez nous votre ville », qu’est ce c’est ?
+
+**Je vois sur le comptoir un flyer qui indique « racontez nous votre ville », qu’est ce c’est ?**
+
 Ca c’est un projet auto-financé, en cours, ou l’ambition c’est d’écrire un « guide amoureux de la ville », on a recueilli des témoignages, commencé des ateliers d’écriture.
 
-Est-ce que ça a été facile de féderer les gens ?
+
+**Est-ce que ça a été facile de féderer les gens ?**
+
 Oui, réunir les gens et récolter les témoignages ça a été facile, le plus compliquer c’est de financer pour produire le guide et payer les gens qui travaillent à le mettre en forme.
 
-En tant qu’habitante, est-ce que vous êtes liée à St denis ?
-Pas tellement mais c’est vrai que la plupart des choses que je fais c’est en lien avec l’association car on fait tellement de choses ici..
 
-Franciade travaille avec d’autres asso ?
+**En tant qu’habitante, est-ce que vous êtes liée à St denis ?**
+
+Pas tellement mais c’est vrai que la plupart des choses que je fais sont en lien avec l’association car on fait tellement de choses ici..
+
+
+**Est-ce que Franciade travaille avec d’autres structures ?**
+
 Oui, un petit réseau comme Artefact, la coopérative Pointcarré, Adada.. il y a énormément d’artistes et d’initiatives ici. Tout le monde connaît quelqu’un qui connaît quelqu’un… et les projets foisonnent ! On a fait des choses avec la maison des seniors aussi.
 
-J’avais croisé Dechets D’art qui intervenait a la maison des seniors..
+
+**J’ai justement croisé l'association Dechets D’art qui intervenait a la maison des seniors..**
+
 Oui, ils font partis d’Artefact aussi, on connaît bien tous les acteurs de ce groupe là.
 
-Et dans votre vie quotidienne, quelle place ont les outils numériques ?
+
+**Et dans votre vie quotidienne, quelle place ont les outils numériques ?**
+
 Dans le travail, on utilise beaucoup les mails, tout le temps, entre nous pour s’organiser et monter les projets.
 Dans ma vie personnelle ça a une importance énorme car ma famille habite ailleurs.
-Ca nous facilite la vie. Ils sont en Roumanie alors skype, c’est tous les jours.. je pourrais les appeler mais c’est pas pareil. Avec mes amies aussi, elles sont éparpillées un peu partout dans le monde donc j’aurais du mal à m’en passer. C’est sûr que je préférerais un contact face à face, mais sans ça je ne sais pas comment je pourrais tenir sans voir ma famille, là je les vois.
+Ca nous facilite la vie. Ils sont en Roumanie alors skype, c’est tous les jours.. je pourrais les appeler mais c’est pas pareil. Avec mes amies aussi, elles sont éparpillées un peu partout dans le monde donc j’aurais du mal à m’en passer. C’est sûr que je préférerais un contact face à face, mais sans ça je ne sais pas comment je pourrais tenir sans voir ma famille.. là, je les vois.
 
-Alors, une dernière question avec qui est-ce que vous pourriez me mettre en contact ?
-Elie ou Wiebke de Point Carré. Passez les voir directement, il y a plein de choses à découvrir là bas.
+Pour la suite de votre projet, vous pouvez aller voir Elie ou Wiebke de [Point Carré](#mp_poincarre#img_pointcarre). Passez les voir directement, il y a plein de choses à découvrir là bas.

+ 8 - 6
user/pages/02._interviews/_manon-dumond/text.md

@@ -4,17 +4,19 @@ image_align: left
 id: int_manon
 ---
 
-Manon; [12 septembre ]
-rencontrée le 12 septembre 2017 à la Défense
+Rencontrée le [12 septembre 2017](#rct_120917) à [La Défense](#mp_defense).
+C’est [Wiebke]() qui m’a évoqué le travail de Manon lors de mon passage à Point Carré le [12 Juillet](#rct_120717).
 
-C’est Wiebke  qui m’a évoqué le travail de Manon lors de mon passage à Point Carré le 12 Juillet.
 
-Wiebke m’a parlé de toi en évoquant un travail de cartographie que tu avais mené sur les reseaux d’artistes de Saint Denis, est-ce que tu peux me parler un peu de tout ça ?
-Je vais t’expliquer un peu mon parcours. J’ai fait mon mémoire sur la place des collectifs dans la production urbaine et pour ça j’ai étudié deux collectifs dans Paris. A l’occasion d’un projet qui croisait mon sujet de mémoire j’ai travaillé avec « Grand Paris aménagement » ou j’ai fini par entrer en stage. A l’occasion de ce stage j’ai rencontré pas mal de monde à Aubervilliers dont les Frères poussières, la villa Medicis..et pleins d’autres. La villa medicis c’est un lieu de résidence ou tu peux rencontrer pleins d’artistes, pleins de compagnies.. là bas j’y ai rencontré iIris, l’administratrice, et Garance, qui était en alternance .. bref j’ai rencontré des gens qui sont devenus des amis et j’ai adoré travailler là bas. 
+**Wiebke m’a parlé de toi en évoquant un travail de cartographie que tu avais mené sur les reseaux d’artistes de Saint Denis, est-ce que tu pourrais m'en dire un peu plus ?**
+
+Je vais t’expliquer un peu mon parcours : j’ai fait mon mémoire sur la place des collectifs dans la production urbaine et pour ça j’ai étudié deux collectifs dans Paris. A l’occasion d’un projet qui croisait mon sujet de mémoire j’ai travaillé avec « Grand Paris aménagement » ou j’ai fini par entrer en stage. A l’occasion de ce stage j’ai rencontré pas mal de monde à Aubervilliers dont les Frères poussières, la villa Medicis..et pleins d’autres. La villa medicis c’est un lieu de résidence ou tu peux rencontrer pleins d’artistes, pleins de compagnies.. là bas j’y ai rencontré iIris, l’administratrice, et Garance, qui était en alternance .. bref j’ai rencontré des gens qui sont devenus des amis et j’ai adoré travailler là bas. 
 Après ce mémoire et après ce stage, où j’avais donc travaillé avec Elsa vivant, elle m’a proposé de l’accompagner dans le labo de recherches en sciences sociales ou elle travaille, pour étudier les réseaux artistiques en Seine Saint-Denis, parce que j’y avais déjà beaucoup d’entrées dues à mon précédent travail sur Aubervilliers. J’avais aussi déjà rencontré pas mal d’acteurs du coin par le biais de Grand Paris aménagement. On avait rencontré déjà des gens de la Briche, de Synesthésie, du 6B... L’idée c’était de comprendre les liens entre les lieux plutôt institutionnels et les lieux plutôt alternatifs. J’allai donc voir soit des lieux, soit des artistes, pour qu’ils me parlent de leur parcours, de leurs réseaux etc.. et c’était génial parce que je rencontrais de personnes en personnes.
 Dans ce cadre là j’ai rencontré notamment Wiebke, découvert la Briche etc.
 
-Comment tu prenais connaissance de ces lieux ou ces artistes ? Comment tu rentrais en contact avec eux ?
+
+**Comment prenais-tu connaissance de ces lieux ou ces artistes ? Et comment entrais-tu en contact avec eux ?**
+
 Mon entrée principale était vraiment les lieux. Par exemple, pour la villa Medicis j’ai rencontré l’administratrice qui m’a dit « tiens, va voir machin », à chaque fois que je rencontrais quelqu’un il me disais «  ah mais tu a déjà été voir machin ? » qui était aussi dans la ville, mais c’était dans un lieu bien défini.
 Le 6B c’est pareil, je me suis rendu sur le lieux et j’ai pris les contacts qu’il y avait au rez-de-chaussé, directement sur place. Après dans le 6b c’était du bouche à oreille.
 Wiebke par exemple, j’étais passée par une personne de la villa Medicis, qui m’avait transmis les coordonnée d’Oriane  qui était venue à un moment à la villa medici pour des automates et qui a  coté de ça bosse en tant qu’artisan dans un atelier de la Briche donc que j’ai rencontré. On a bien discuté, elle m’a fait rencontrer des gens de la Briche et m’a parlé du travail de Wiebke en me disant « elle a un super projet sur la toison dyonisienne, la laine etc » et voilà j’ai donc rencontré Wiebke.

+ 66 - 0
user/pages/02._interviews/_marie-w/text.md

@@ -0,0 +1,66 @@
+---
+title: Marie.W
+image_align: left
+id: int_mariew
+---
+
+Marie.W
+A Paris 8 dans le studio radio le 04 octobre 2017 à 10h30.
+C’est Victoria qui m’a donné son contact.
+
+Peutx-tu me parler de ton rapport à Saint Denis
+
+J’y habite depuis 5 ans. J’y suis venu parce que je voulais aller à l’université de Paris 8 et pour moi c’était très important d’habiter au même endroit que là ou j’étudiais pour pouvoir créer des passerelles entre l’université et mon chez moi, pour pouvoir inviter des gens, avoir un rapport avec l’extérieur. C’était ma vision de ce que ça devait être. J’ai toujours habité près des endroit ou j’ai étudié et ou j’ai travaillé et pour moi c’est une donnée assez importante. Peut etre en regard aussi que j’ai pas de voiture, j’essaye de faire tout à pied ou en vélo. Je me suis donc implantée à St Denis dans une petite maison en colloc et finallement après mes études j’y suis restée parce que j’ai découvert une ville que je n’aurais jamais découvert autrement. Quand je suis arrivée à Paris au départ j’habitais Paris et j ‘étais à la Sorbonne, c’est plus tard que j’ai eu envie de découvrir Paris 8.
+
+Comment tu as eu envie d’y aller ?
+
+J’ai fait mes études à Nancy en licence et mon rêve était d’aller à Paris, j’y vais et je commence un master de lettre à Paris. Finallement le parcours tout tracé ne m’a pas convenu et à la suite de ça je pars en stop pendant 2 ans en Europe et j’y découvre les milieux alternatifs. Au cours de cette expérience je me questionne sur mon mode de vie et notamment mon rapport au collectif. Un jour une amie m’invite à une réunion féministe à Paris 8, que j’ai trouvé super interessante. Je fais cependant remarquer à mon amie que je trouve qu’une personne prend quand même beaucoup la parole et elle me dit : C’est la prof. C’est un cours de philosophie. Et là je me dit Ok, il faut que je vienne à Paris 8. Je suis entrée donc à nouveau en Master de lettre mais avec un tout autre parcours, un tout autre profil.
+
+Tu me disais que tu habites à coté de l’université, comment est-ce que tu as l’impression de connaître le reste de Saint Denis ?
+
+Au début, j’étais vraiment très présente à Paris 8 parce que c’est une université-monde, c’est tellement énorme que ma vie c’était à Paris 8. Je ne connaissais donc même pas tant mon quartier que ça surtout que c’est un espace qui est en train de bouger, d’avoir de plus en plus de commerces mais au départ y’avais pas encore le tram, pas bcp de commerce donc pas bcp de vie dans ce quartier. Je sortais très peu à Saint Denis dans le centre. Par contre ma rue c’est des anciennes maisons ouvrières et donc y’a tout un tas de gens qui sont là depuis 40 ans , qui sont très aidants les uns envers les autres avec un rapport vraiment chouette entre les habitants. Ma voisine prend le courrier de toute le monde quand on est pas là.. C’est vraiment depuis l’année dernière que j’ai réussi un peu à quitter le cocon Paris 8 et à m’intégrer à Saint Denis et finalement du coup c’est principalement dans le centre de St Denis. J’ai peu de rapport avec mon quartier à part avec mes voisins. Je croise aussi des habitants parce que je vais au taxiphone.
+
+Le taxiphone ?
+
+C’est comme un cyber café. Jusqu’à très recemment je n’avais pas Internet chez moi, par choix. 
+A Paris 8 avec la Bibliothèque ouverte jusque 20H je n’en avais pas besoins et ça me permettais de segmenter un peu des parties de ma vie avec des espaces connectés et des espaces non connectés. Je n’ai pas de Smartphone non plus. J’ai été un peu prof et parfois ça m’a amené a devoir travailler un peu plus tard quand même et donc j’allais au TaxiPhone. Je trouve que c’est un endroit extraordinaire, à la fois il vend des clopes, des cafés, il récupère le courrier, il fait des photocopies, tu peux utiliser Internet, appeler en Afrique.. Et du coup par ce biais là je connais des gens du quartier que j’y croisais régulièrement et que j’aurais jamais rencontré par ailleurs. Tu y rencontre des gens qui ne parlent pas forcément français, tu y croise des sphères sociales et culturelles. C’est aussi que c’est un endroit très peu cher et t’y va tel que t’es avec des besoins élémentaires de café, d’appel, de connexion web, ça marche bien. C’est un lieu très vivant ou on se rencontre sans se rencontrer mais à force de s’y croiser on discute, on crée du lien. C’est un lieu qui fait vraiment partie de mon intégration de quartier.
+Mais sinon c’est vrai que les gens que j’ai rencontré à st Denis ça a quand même été plus en centre ville à travers des lieux un peu symboliques à Saint Denis, comme le bar Le Pavillon, la galerie ADADA, le cinéma l’écran, le théatre Gerard Philippe. Les habitudes un peu des bobos de St Denis..Pour le coup à ce niveau là ça ressemble à un village. Tu regarde quand tu passe au Pavillon si y’a pas des gens que tu connais, parce que c’est un lieu qui se trouve un peu dans un carrefour et ou tu vois tout de suite les gens qui y sont installés. Depuis que je m’intègre dans le centre ville je découvre st denis comme un village.
+
+Est-ce que tu as l’impression de découvrir un village dans une ville, parmis des villages?
+C’est vrai qu’à la fois je trouve qu’il y a un milieu alternatif assez étendu et en meme temps..
+Pourquoi le Pavillon est symbolique à St Denis c’est parcque dans bcp de bars de st denis tu va trouver quasiment que des hommes, alors qu’au Pavillon c’est un bar vraiment mixte , qui a un emplacement géographique assez stratégique, avec une bonne visibilité. Et le patron a une grande ouverture d’esprit. Tu peux y proposer des choses un peu insolites, des concerts, nous on y a fait une performance artistique avec une amie. C’est pour ça que ce lieu a une grande importance.
+
+Tu fais des activité dans ST Denis ?
+Je fais partie d’un réseau qui est devenu vraiment important pour moi et pour bcp de gens je pense à St Denis qui est l’AMAP. Et enfait cet amap c’est plein de choses, c’est plus qu’une amap. Manger des bons légumes c’est cool mais c’est presque un pretexte. C’est surtout un moyen de se rencontrer et de se connaître entre dyonisiens. On a une liste mail où on se file des infos, qu’on ai quelques chose à donner, qu’on cherche un medecin, un plombier..qu’on informe d’une date de concert.. tout ça passe par la liste de discussion. Et ça marche très bien, tu envois un mail tu es sûr d’avoir une réponse. Mon dernier colocataire je l’ai trouvé par ce biais là. Y’a même des gens qui ne sont plus à L’AMAP, parce qu’ils ont déménagé par exemple, et qui restent inscrits sur les listes de discussion pour avoir les infos.
+
+Et pour être reliées à cette chaine de mails, tu le fais depuis le Taxiphone ? Tu n’as toujours pas internet chez toi ?
+Non maintenant j’ai une web-trotter, ce qui me permet d’avoir internet aussi quand je vais dans ma maison en normandie par exemple. C’est une petite boîte que tu peux trimballer partout et que tu branche en usb pour avoir le Wifi. Donc c’est un peu comme une box que tu peux transporter facilement pour te connecter à la campagne, dans le train.. Donc je triche un peu, c’est en train d’évoluer doucement.
+
+Donc finallement pour quelqu’un de déconnecté tu es plutôt ultra connectée ?
+Oui c’est vrai mais je n’ai pas de smarthpone donc il faut que j’ai accès à un ordinateur si je veux me connecter, ce qui me met dans une autre configuration où je ne suis pas tout le temps connecté.
+Je pense que dans le réseau alternatif y’a encore beaucoup de gens comme moi qui ont des vieux téléphones basiques qui appellent et envoient des sms pour être joignable mais pas connectés en permanence. Pour ma part je privilégie toujours le contact direct au téléphone. A un moment donné je n’avais qu’un téléphone fixe, puis pendant quelques temps plus de téléphone du tout donc il fallait se fixer des rendez-vous. Mais on a pu me repprocher de faire subir mon mode de vie aux autres, par exemple une fois le rdv fixé on ne pouvait pas me prévenir d’un retard, décaler d’une demi heure etc..
+Maintenant j’ai un téléphone portable mais j’essaye de ne pas avoir de Smartphone. Je vois déjà l’évolution avant j’avais ni internet ni téléphone et déjà j’ai ma web-trotter et un téléphone.  Cet englobement me fait peur, je n’ai pas envie d’avoir une vie virtuelle. Je veux bien que ce soit un outil qui me permette d’avoir un contact humain direct, mais pas que ça remplace les échanges directs. Mon usage du numérique se cantonne aux recherches et aux mails, je n’ai pas Facebook, pour des raisons idéologiques aussi, je n’ai pas twiter, pas snapchat, je n’y connais rien.
+
+Tu dissocie complétement une virtuelle d’une vie physique ?
+Oui, car j’ai l’impression qu’on se crée une image de ce qu’on veut montrer de soit et j’ai pas envie d’entrer dans cette logique, en montrant des choses attrayantes me concernant pour qu’on ai envie de me rencontrer. 
+
+Ca c’est plus dans le concept du réseau social, mais l’outil mail qui te permet de densifier ton réseau, par exemple avec l’amap, peut difficilement être dissocié de ta vie physique non ?
+C’est vrai mais en même temps ce qui est étrange parfois c’est que des personnes vont me répondre, par exemple sur les discussions de l’AMAP, et je ne sais pas qui elles sont. Ca engendre quand même une frustration. Par exemple lorsque j’ai cherché mon colocataire sur cette liste mail, des personnes m’ont répondu car mon annonce leur avait donné envie de me rencontrer, bien qu’ils ne cherchaient pas de logement dans l’immediat. Ce que j’aime bien par exemple au Pavillon c’est le fait que ça puisse etre une rencontre spontannée, physique. J’ai l’impression qu’avant, quand on avait pas de téléphone portable, il y avait les café ou on savait que vers tel heure on pourrait y rencontrer des connaissances, au Pavillon c’est un peu comme ça.
+Et il y encore un peu ça à St Denis parce que c’est une ville ou, j’ai l’impression, les gens sont très connectés, y’a beaucoup de choses qui se passent, beaucoup de réseau. Et je trouve ça mieux que le réseau informatique , même si on peut dire que ça fonctionne ensemble, mais je pense que c’est parcque les gens font des choses ensemble physiquement que ce réseau est interessant.
+Du coup là effectivement on parle pas d’une « autre vie », c’est juste un outil derrière tout l’échange social entre les gens. Et ce qui m’inquiètes c’est que cet outil prenne plus de place et « devienne la vie ». 
+A saint Denis y’a aussi un truc très pragmatique qui fait que c’est pas le cas, c’est qu’il y a des gens dans le milieu alternatif qui sont dans le refus de Facebook, Twitter, etc , n’ont pas de smartphone etc et concretement pour les voir il faut se débrouiller autrement. Et il y a aussi le fait que St Denis soit une ville pauvre, tout le monde n’est pas équipé pour être connecté et il y a une sorte d’instabilité de la connexion qui pousse + les gens à se parler. Des fois, à mal se parler, c’est sûr, mais en étant dans un échange direct.Il y a beaucoup de groupes de gens dans la rue qui discutent des heures de jour ou de nuit. Il y a une vraie facilité communicationnelle, même si bien sûr il reste des groupes. Mon ancien colloque, en 3 mois il connaissait tout Saint Denis.. 
+
+Victoria m’a évoqué l’Atiéké, est-ce que tu saurais m’en parler un peu ?
+J’ai suivi surtout les débuts de l’Atiéké, il n’existe plus aujourd’hui, il y a eu des expulsions.
+Ca a été un lieu qui a été important et vraiment interessant. Ils ont réussis à proposer des activités multiples pour des publics assez différents, à la fois atelier vélo, d’alphabétisation, pour les enfants, des projections militantes, des débats, un lieu de vie. Ils ont vraiment réussi à en faire un lieu social auto-géré. C’est à dire qui répond à des demandes sociales, qui est ouvert sur l’extérieur et pas un squat fermé et qui reste dans l’entre-soit. Il y avait une vraie implication dans le territoire et ça à vraiment marché. T’allais en soirée à l’Atiéké, tu croisais toute sorte de gens, de mentalité, de culture, sans être maté à l’entrée, sans même payer, c’était prix libre. 
+C’est resté 2 ans et demi/3 ans sur le territoire, donc avec un vrai temps de s’implanter, alors que souvent les squat ça a une durée de vie assez courte, plutot d’un an.
+
+Comment marchait la communication ?
+N’étant pas très connectée j’ai peut-être loupé des choses mais en tous cas il y avait des mails pour les grands énévements, des sms.. des affiches aussi car il y avait une vraie volontée d’ouverture.
+Mais en réalité, généralement c’est plus facile d’entrée dans un squat si tu y connais quelqu’un.. C’est pour ça aussi que sur l’Atiéké en particulier il y a eu un travail sémantique sur le fait d’appeller ça un centre social auto géré et non un squat, pour pas renvoyer à un imaginaire «  sale, malpropre, drogue, illégal..) alors que centre social on parle tout de suite d’un lieu de vie ouvert.
+Le bouche à oreille est super important.
+Dans les squat moi que j’ai pu rencontrer à l’occasion de mon voyage en europe c’était des squats un peu plus fermés donc ça fonctionne plus par code, dans le sens ou tu rentre dans le groupe soit parce que tu y connais quelqu’un soit parce que, dans ta façon de t’exprimer, de te vétir, tu répond aux même « codes », tu es reconnu par le groupe. Par exemple parce que tu fait du stop, de la récup sur un marché.. y’a une sorte de reconnaissant sociale qui agit et c’est comme ça que tu rencontre le groupe. Ca reste très fermé donc la communication est assez limitée pour se contenter du bouche à oreilles, du contact direct.
+
+Pour terminer peux-tu me dire comment tu as rencontré victoria ?
+J’ai connu victoria dans un cours dans un master science de l’éduc, on était dans un groupe affinitaire et on a travaillé sur un même projet. Ca a crée un lien entre nous, qui avons passé plus de temps au café à se questionner sur la pédagogie du cours en lui même que sur le sujet de travail de notre groupe. 
+Je n’étais plus à Paris 8 lorsqu’elle à monté le collectif Kabane avec d’autres étudiants mais j’ai suivit l’initiative.

+ 50 - 0
user/pages/02._interviews/_martine/text.md

@@ -0,0 +1,50 @@
+---
+title: Martine
+image_align: left
+id: int_martine
+---
+
+u café Le Basilic sur la place de la Basilique de Saint-Denis.
+Le 5 Septembre 2017 à 14h30.
+Victoria Linhares m’a donné son contact mail/téléphone par mail à la suite de notre rencontre place de la république.
+
+(Habite ilot 9 – immeuble en brique)
+
+Explication du projet..
+
+Effectivement Saint Denis c’est un bon endroit pour étudier cette question de réseau..
+Quand on dit Saint-Denis, en plus, c’est tellement vaste.. moi j’habite ici depuis 86 et finalement, je connais un peu le centre ville car j’y habite, je connais la fac parce que j’y ai été amené à y bosser, mais y’a des quartiers,  je n’y ai jamais mis les pieds. Et je n’y mettrais pas les pieds, ou vraiment occasionnellement, mais je n’y connais pas quoi que ce soit.
+En te parlant je me rend compte que nous, en centre ville, on a tendant à dire « on » est Saint Denis. Mais en réalité lorsque l’on parle de Saint Denis, on parle ou d’une grande entité abstraite ou d’un petit bout de quartier qu’on connaît. Je connais un peu la plaine maintenant parce que que j’y ai travaillé depuis 2 ans avec des étudiants, mais c’est vrai que j’ai l’habitude de plaisanter sur le fait que « Mon » Saint Denis va de la Fac à la porte de Paris en passant par le théatre gérard philippe.
+Comme on est dans du dense, ma connaissance de St Denis est à la fois approfondie sur certains plans et très très approximative sur pleins de choses.
+
+Martine me requestionne sur le projet et ses problématiques
+Moi je vois dans ma génération, alors que ça a été une appropriation assez tardive, qu’il y a une intégration vraiment impressionnante de ces outils, qui on pu nous paraître rébarbatifs au début mais qui sont entrés dans notre vie avec une facilité.. alors que c’était pas du tout notre culture.
+Même les ordinateurs, moi au boulot en 90 j’avais demandé à mon boss un ordi et il me répondait « t’es pas secrétaire », bon ben 3 ans plus tard on avait tous des ordinateurs avec nos mails et tout ça. Y’a eu un changement de culture très rapide et c’était frappant dans le monde du travail. 
+
+Est-ce que tu peux me parler un peu de ton rapport à ce territoire ?
+Donc moi je suis arrivée fin 1986 dans un immeuble de la zac basilique, au dessus de Mc Do, un immeuble d’HLM. On est tous arrivés un peu en même temps, c’était des immeubles neufs. Moi j’étais au chômage à l’époque et très rapidement avec d’autres gens on a monté une association de locataire qui a été très dynamique. Je me suis pas mal occupé de cette amicale là, après on a monté un collectif d’asso qui faisait ses activités dans un local juste au dessus du C&A. Le collectif était branché sur des activités comme de la musique pour enfant, théâtre pour enfant, beaucoup de corporel : du yoga, qui est toujours là, du stretching, du théâtre. 
+En 2003 j’ai repris des études à la Fac de Paris 8, j’ai fait un DESS, une branche un peu tordue de la sociologie, qui m’a bien plu et puis j’ai continué en thèse. J’ai fait des enquêtes sociologiques avec un prof sur Saint-Denis et je viens de terminer et de soutenir ma thèse en juin. Un long parcours.
+Entre temps j’ai pas mal bossé dans les milieux militants et des projets de quartiers. Avec des copains artistes on avait fabriqué des chars avec lesquels on déambulait dans les quartiers.
+
+Par rapport à l’asso de locataires, y’a pas mal de choses qui ont changées aujourd’hui.
+C’est un peu les «  choses qui ne se disent pas ». Nous quand on est arrivés, en 1986 c’était dans un HLM privé. Une logique HLM mais pas l’organisme public, donc un peu plus cher et donc avec une population plutôt « Classe moyenne blanche ». Petit à petit, cette population soit s’en va, soit accède à la propriété et peu à peu on assiste à un changement de population. Aujourd’hui la grande majorité des familles sont africaines. Ce que ça change, c’est qu’il y a plus de monde, plus de monde dans les espaces communs et on constate aussi  un effondrement des associations. Ce qui se passe c’est que notre méthode de militants classique ne fonctionne plus. On était très «  ordre du jour/réunions ». Mais on s’est rendus compte que ça marchait plus, d’abord parce que car beaucoup ne viennent pas aux réunions, Peut-être parce que trop formel, parce qu’ils parlaient pas toujours bien, que les femmes ne viennent pas avec les hommes, toutes sortes de choses qui étaient plutôt de l’ordre culturel et qu’on a pas pu anticiper. On pu a trouver des astuces par exemple avec les femmes, on faisait des thés en passant par les copines, etc.. mais pas pour tout. 
+Pour la communication on a arrêté l’affichage, les boites aux lettres, on passe quasiment que par mail avec ses avantages et ses inconvénients, car ceux qui n’ont pas d’adresse mail ou ne s’en servent pas beaucoup, ils n’ont juste pas l’info. J’ai décidé de me créer une page Facebook parce que je me suis rendu compte que c’était plus simple pour relayer les infos du quartier, car chacun peut les découvrir aussi par ses propres contacts. C’est un peu une page «  locale » pour moi, j’y raconte pas ma vie c’est plus une démarche sociale/professionnelle..
+
+L’idée de réseau, c’est compliqué car c’est une notion abstraite. 
+Il y a une époque ou j’entendais beaucoup « Saint Denis est un village ». Saint-Denis est un village, c’est vrai, mais d’un petit noyau de gens. Par rapport à la Grande ville c’est pas vraiment un village, peut etre DES villages..
+Mais en même temps je remarque, notamment en étant dans cet HLM, que la proximité spatiale ne crée pas naturellement de proximité entre les gens. Il y a pleins de choses qui ne se croisent pas. Notre local avait été une grande leçon pour moi. Au début on avait l’objectif classique de défendre l’interet du locataire. Mais on a eu envie de choses plus amusantes donc comme on avait ce local, on a ouvert des ateliers, des activités dans le local. On partait du principe qu’un travail de ce type allait forcément créer une vie sociale dans l’immeuble. Mais chemin faisant on s’est rendu compte qu’on avait quasi personnes de l’immeuble qui venait. On avait aussi été cambriolé plusieurs fois et on avait su que quelques fois c’était des gamins du coin. Donc j’ai commencé à réfléchir en prenant un peu le problème à l’envers et je me suis dit, ben peut-être qu’on le squatte enfait ce local. Peut-être qu’on s’impose et que quelque part les mômes nous disaient « Ben pourquoi nous on y entrerait pas aussi ? ». Dans les réunions, ça commençait à parler de sécuriser et fermer encore plus le local. Mais c’était pas du tout la solution. Du coup on a essayé d’ouvrir beaucoup plus, de laisser l’espace. Y’a eu tout le travail de Julia aussi avec les jeunes, et au final c’était eux qui nous gardaient la salle. Et puis les gamins pour le coup, ils en ont du réseau. Ils savent tous qui est qui, qui habite où. Quand y’a un gouter d’anniversaire il faut pas 15min pour que tout le monde soit au courant qu’il y a du gateau dans le coin. Des volets et des clefs, ok, un minimum, mais pas de grilles, pas de digicodes, il faut pas cloisonner tous les espaces.
+
+Tu me disais tout à l’heure que tu habite la Zac Basilique, c’est étonnant l’architecture de l’ilot..
+Oui c’est sur, nous on habite de l’autre coté de la Maison Jaune. C’est une drôle d’architecture, avec ses recoins aussi. Mais là ils veulent rénover et mettre des barrière, tout cloisonner. C’est un vrai mépris des usages. Au dessus de Carrefour ils en on foutu partout des grilles, c’est immonde. 
+Si on avait un immeuble classique on aurait pu accepter des digicodes sans trop de problème mais avec l’architecture qu’on a, avec tous les accès, les ouvertures, on aura que ça partout.
+
+Je vais revenir un peu à tes moyens d’échange, tu me parlais tout à l’heure de ta page Facebook, quel rapport tu peux avoir toi à ses outils ?
+J’avais l’habitude d’envoyer des mails à mon fils, pour lui faire part d’une chose que j’avais vu ou lui dire quelque chose, et l’autre jour il me dit «  maman c’est les vieux qui communiquent par mail ». C’était au moment où j’ouvrais ma page Facebook et il m’a envoyé un petit message dessus, alors maintenant je lui réponds là, sur messenger, et quand je lui envoie une info, un lien, même du salon à sa chambre, je lui envoie sur messenger.
+Quand je me suis mise à l’ordinateur au boulot ça a bougé tres vite, je trouve que ma génération s’est adapté super vite. On s’est vite mis aux mails, à internet. Au début ça me gonflait et maintenant on s’en sert tout le temps. Puis maintenant ya Facebook que je considère pour le moment comme un petit réseau de copains, plutot militant. Je poste un petit peu, tu iras voir ma page, et je relais quelques trucs, pas trop non plus car j’ai remarqué que ceux qui postent tout le temps ça peu devenir vite fatiguant et l’info se noie. Et je relais pas les histoires de petits chiens, de petits chats..qui m’amusent bien par ailleurs. C’est plutot pour moi une manière de relayer. Pour échanger et converser, je suis beaucoup plus mail.
+J’ai un ami, Pascal, prof à Paris 8 avec qui j’ai monté la Fabrique des Sociologies, qui ecrit vraiment ses mails, pas comme on peut le faire des fois dans un style un peu oral. Il ecrit, il prend le temps, il formule. On a commencé à beaucoup correspondre un peu à la manière d’un journal, on raconte. On reprend un point et on commence à le développer. Et du coup on avance. On pose vraiment nos réfléxions.
+Avec tout une bande de doctorants on échange comme ça, par listes de mails, pour organiser nos événements et tout ça.. Pour un projet là, l’un d’entre nous, plutôt jeune, à ouvert un Wiki, mais nous qui sommes plus agés on est perdus là dessus. Y’a pas de mail qui nous dit qu’il y a une nouveauté sur le Wiki alors on avait fini par un peu l’abandonner. On s’était remis à faire des mails, mais notre ami nous a relancé pour le Wiki car c’est vrai que pour l’archive c’est mieux. Ca nous demande un effort, c’est un peu plus long à intégrer. Une sorte d’approche visuelle qui est différente.
+
+
+
+
+

+ 22 - 0
user/pages/02._interviews/_nadia/text.md

@@ -0,0 +1,22 @@
+---
+title: Nadia
+image_align: left
+id: int_nadia
+---
+
+On a commencé notre activité type centre social, on est parti sur le principe d’un espace de vie sociale et on a commencé dans la cité Gabriel Péri, c’était un petit local résidentiel qu’on avait négocié avec le Bailleur un petit 2 pièces. Tu retrouveras des photos sur notre page Facebook car j’essaye de raconter au fur et à mesure ce qui s’y passe pour fédérer un peu. Et là on a négocié avec la ville pour intégrer cette « maison des projets » pour accueillir le projet de centre social en attendant que nos locaux définitis nous soient livrés pour mars 2018.
+Donc l’association coopérence gardera quand même un pied ici par la suite comme base opérationnelle car il y a beaucoup d’autres projets à développer. Il y a donc une nouvelle entité juridique qui va naitre avec un statut de centre social coopératif qui sera la premier en France.
+
+
+L’idée qui porte cette asso depuis le début c’est celle d’expérimenter un centre social coopératif, aborder la question sociale en incluant la dimension économique mais pas n’importe laquelle.
+On est membres des centres sociaux depuis longtemps, parfois même des administrations des centres sociaux, on connaît bien l’outil centre social. 
+
+Donc pour le moment c’est moi je gère un peu tout, qui trouve les outils pour faire marcher tout ça. Je me débrouille mais c’est vrai que j’ai pas grandit avec et que je le ressens. J’espère trouver chemin faisant les bonnes compétences et les bons outils, car l’idée de la coopérative c’est aussi de trouver les compétences adéquates à ce qu’il y a faire dans les membres de la coop. Mettre au service du collectif ses compétences et voir ce que ça génère ensuite comme moyens. On commence pas par les moyens mais par les richesses « humaines » a disposition.
+Pour le moment coté « administratif » je fait tout de chez moi car on viens d’arriver je n’ai pas encore de matériel, de connexion internet. La page Facebook je la gère de chez moi, le site internet aussi, j’avais commencé à travailler sur une NewsLetter mais ça me prends trop de temps.
+J’ai un google groupe où j’inscris tous ceux qui me donnent leur coordonnées. On est 140 personnes pour le moment sur ce groupe. Il y a un peu de tout, des associations, des institutions, des habitants.. tout ça se monte au fur et à mesure et je tiens tout le monde au courant. On retrouve les mêmes infos sur ce groupe, dans les mails, que sur la page Facebook. Voilà en gros comment on communique et sinon on utilise les relais locaux, les réseaux de la ville, le JSD.. 
+Mon job c’est ça surtout, coordonner, mettre en lien, en relation.. le nom coopérence il ne vient pas de nul part.  On pense qu’il y a quelque chose à creuser autour des territoires, des acteurs, des habitants,.. une intelligence collective avec une dimension territoriale à travailler. Ce projet de centre social c’est un exemple, car par définition c’est plurigénérationel, polyforme, polymorphe, donc c’est parfait pour nous. Tout est possible.
+
+Non je n’y habite plus, j’y ai habité. J’ai travaillé pour cette ville en 90’ et en 2013, comme éducatrice spécialisé d’abord puis comme directrice de démarche quartier et j’ai vite basculé sur un poste de chargé de mission sur la création d’un centre social dans le quartier de l’université. On y a travaillé avec les acteurs du coin. Donc forcément pour ce projet là j’avais déjà un peu d’expérience, et du réseau. Du réseau de « long court ». Le facteur temps est important aussi dans la construction d’un réseau.
+Donc j’ai vécu ici dans les années 90’ mais les premiers qui sont venus ici étaient mes grands parents dans le cadre de l’immigration. Mon grand-père travaillait sur le site EDF-GDF donc il y a une partie de ma famille qui a habité ici. Ils ont fait partie des vielles vagues migratoires qui ont construit la ville au moment ou on était encore sur une urbanisation très industrielle.
+
+

+ 60 - 0
user/pages/02._interviews/_oceane-et-laurent/text.md

@@ -0,0 +1,60 @@
+---
+title: 'Océane et Laurent'
+image_align: left
+id: int_ocelau
+---
+
+Sur la place du 8 mai 1945 à Saint-Denis, pendant un chantier pour le collectif Mund Gawi. Le 26 Juillet 2017.
+J’ai rencontré Océane quelques semaine plus tôt lors d’une intervention des Street Mamies devant la maison des Séniors. Je la retrouve aujourd’hui place du 8 mai 1945, lorsque j’arrive elle est partie chercher à manger et je rencontre Laurent..
+
+
+Explication du projet..
+...Océane je t’ai donc rencontré il y a quelques semaines en face de la Maison des Seniors  lors d’une intervention tricot. Aujourd’hui tu es avec Laurent place du 8 mai 1945 .
+
+Peux-tu déjà me parler de ta structure Dechets d’arts ?
+Alors Dechets d’arts c’est d’abord la rencontre de deux amies, Nicole qui travaillait surtout sur la prévention autour des dechets et de l’écologie et d’Anne dominique qui elle est plasticienne et qui a toujours travaillé a partir de ce qu’elle trouvait et qui voit le dechet comme unr ressource. C’est donc le mélange de leur deux approche qui a crée dechets d’art dont le concept est de sensibiliser à des comportements plus eco citoyens autour de dechet et au travers de la création artistique. Comment faire du dechet une resssource avec une approche poétique. On apporte cette sensibilité par le faire et sans panneau de sensibilisation avec du chiffre etc. On met en place pas mal d’ateliers pour apprendre des techniques, des outils etc ..
+Voilà pour l’asso. On fonctionne en réseau professionnel depuis le début. Y’a un noyau dur qui change au fil des années et le reste c’est des artistes indépendant avec qui on travail régulièrement ou non. Ca se fait sous forme d’ateliers ou d’interventions ponctuelles. 
+On a deux modes d’interventions. D’un coté on vend de la prestation et d’un autre on développe nous meme des projets pour lesquels ont va chercher des subventions. Le projet des streeet mamies durant lequel tu m’as croisé en fait partie. On part de la technique du crochet adapté à des materiaux de récup et on invite des séniors femmes pour aussi réaffirmer la place et des séniors et de la femme dans l’espace public. C’est aussi un vecteur de rencontre.
+
+Vous parlez de rencontre, justement comment vous faites se rencontrer tout ça, comment vous communiquez ?
+Pendant un temps on a eu une newsletter et desormais on alimente surtout une ou des pages facebook, en fonction des projets. Je suis présente sur d’autres réseaux mais finalement c’est surtout ça qu’on alimente plus facilement et plus spontannément. On a un site internet également qui est aussi vieux qui l’asso et qu’on actualise pas tellement mais qui nous sert de vitrine. 
+
+Pour un projet comme celui des street mamies je peux cependant deviner que la pateforme facebook n’est peut être pas la plus adaptée pour le public qu’il touche, comment vous procedez dans ce cas ?
+Effectivement là on va plus utiliser le téléphone. On a quelques personnes qui suivent leurs mails et donc on utilise beaucoup les listing et puis sinon c’est le téléphone.
+Pour des projets plus récents j’ai commencé à utiliser les google groupes après une tentative de groupe Facebook mais tout le monde n’était pas inscrit sur Facebook.
+
+A un niveau « informationel » qu’est-ce que tu utilise comme outils ou plateforme ?
+Donc comme on disais j’utilise pas mal Facebook et pour certains projets on est pas mal sur What’s app.
+
+Es-ce que tu y communique avec des personnes que tu avais déjà rencontré ou tu en a rencontré certaines via ces échanges numériques ?
+Alors pour beaucoup on se connaissait déjà mais c’est vrai que pour certains on a d’abord discuté que ce soit sur des commentaires Facebook ou sur l’application what’s app. Je me suis notamment rapproché d’une structure de maison de quartier coopérative là qui est en train de se monter pas loin et c’est vrai qu’eux c’est leur principal moyen de communiquer donc pour beaucoup j’ai fait leur connaissance sur what’s app et je les rencontre au fur et à mesure.
+
+J’évoque le fait qu’à la Maison Jaune ils l’utilisent aussi beaucoup et Laurent me demande de qui je parle, il n’a jamais été voir encore la maison jaune et est curieux de découvrir.
+
+Laurent, quelle pratique du numérique tu peux avoir dans ton métier ?
+Alors j’utilise surtout des logiciels de plans pour mon travail, quelques outils administratifs à la limite pour constituer mes dossiers mais sinon je suis plus une personne de terrain, avec mes chantiers. 
+
+Et dans ta vie personnelle ?
+Oui pour communiquer avec mes proches j’utilise forcément les nouvelles technologies mais je crois que je fais partie des nostalgiques de « l’avant  ça ».
+Je regrette qu’on ai tant de mal à s’en défaire. J’utilise aussi comme océane l’évoquait, Messenger, what’s app, facebook mais aussi pour un côté plus « pratique » j’utilise les réseaux Air Bnb, vélib..même Tinder ..!
+
+Et l’un comme l’autre, vous avez des contacts avec qui vous échangez quasiment que via le web ?
+Océane : oui moi par rapport à l’éloignement j’ai mes deux sœurs et d’une très bonne amie qui sont en guadeloupe donc on échange essentiellement par messenger, facetime..
+Laurent : Moi Skype et tout ça j’aime pas du tout, j’évite d’utiliser ça. Mais la plupart de mes amis, je peux ne pas les voir pendant des mois et quand on se voit on reprend comme la fois d’avant mais on échange pour ainsi dire pas entre deux, je n’aime pas devoir prévoir un créneau, sans se voir.
+
+Quelle pratique de Saint Denis vous avez ? Océane toi tu es habitante de St Denis ?
+Océane : Oui alors moi j’habite ici, pas loin du théâtre, mes filles vont a l’école ici. L’école rue du corbillon on l’autre au college jds place de la résistance, le petit café au pavillon éventuellement en l’accompagnant le matin. Un petit tour au point carré de temps en temps, parce que je suis reliée au projet aussi. Le théâtre, pas de manière assidue mais régulièrement. L’Adada, l’artefact 93 , le parc de la légion d’honneur avec mes filles ou simplement la place de la basilique pour faire du roller. Le 6B également, parce que j’ai mon bureau là bas et pour participer à des événement.
+Le 6B c’est là où on s’est rencontrés avec Laurent. Le chapiteau Rajganawak aussi, qui a ouvert récemment. Y’a un cabaret, des événements, des cours de yoga..
+Au niveau des petits commerces après c’est plus les petits commerces de proximité autour de chez moi. 
+Laurent : Moi c’est vrai que je connais pas beaucoup les alentours, j’habite paris et pour le moment, comme j’habite loin, je viens essentiellement pour mon espace de travail au 6B. Je connais pas du tout Saint-Denis. C’est pas pareil quand tu met une heure pour venir.
+
+Océane remarque sur mon carnet que j’ai rencontré Wiebke de Point Carré, elle me signale donc :
+Wiebke est aussi très en lien avec Rajganawak, parcqu’elle fait partie de la Briche et ils font beaucoup de choses en collaboration. D’ailleurs elle est aussi liée aux clinamen, les bergers urbains qui font partis du collectif Mund Gawi. 
+
+Oui d’ailleurs aujourd’hui vous travaillez ensemble pour le collectif MundGawi, est-ce que vous pouvez m’en dire plus ?
+Océane : Donc c’est un collectif qui s’est monté au moment de la COP 21, il y avait à l’origine beaucoup de structures, dans l’idée de faire un alternatiba Saint denis mais avec l’idée de trouver un espace où expérimenter pérènne plutot qu’un village de 2/3 jours. Donc au départ beaucoup de structures étaient interessées et finalement dans le temps on est 5 structures à constituer et porter le collectif. Il y a donc : Rien ne se perd, Shakti 21 ( outils de cuisson écologique ), clinamen berger urbains, landykadi ( jardins potager, amélioration de vie de quartier, événements culturels autour de l’afrique) , Dechet d’art, et une strcuture d’aubervillers qui du coup suit d’un peu plus loin du fait de sa non appartenance au territoire.
+J’ai vu aussi tout à l’heure que tu avais repéré Bonjour Voisins Cop’Billon, moi je suis pas mal en lien du fait que ma fille soit à l’école là-bas, mais les street mamies aussi on fait des interventions dans cette rue.
+
+
+
+

+ 42 - 0
user/pages/02._interviews/_olivier/text.md

@@ -0,0 +1,42 @@
+---
+title: olivier
+image_align: left
+id: int_olivier
+---
+
+Peux-tu me parler de ton rapport à Saint-Denis ?
+
+J’habite à Saint-Denis depuis 3 ans, j’ai découvert l’AMAP via une Amapienne lors d’un événement à la mairie. En discutant elle m’invite à m’inscrire à l’amap et effectivement je me suis renseigné et ça m’a bien parlé, j’ai rencontré quelques personnes et je me suis inscrit. C’est là que mon réseau Dyonisien à commencé à s’ouvrir. Au début je venais pour mes paniers de légumes et petit à petit j’ai commencé à faire connaissance avec les gens, à utiliser le réseau. La liste de discussion est très active, sur une multitude de sujets. On est 700 inscrits sur la liste, donc 700 foyers, ça donne une nombre de personnes impressionnant. Il y a même un framapad pour se communiquer tous les bons plans, les bonnes adresses d’artisants, de restaurants, de spécialistes médicaux etc.. Il y a une acceptation de toute forme d’échange avec une auto-restriction pour ne pas « blinder » les boîtes mails des gens. Il y en a qui abandonnent parce qu’en terme de mails/jours c’est très conséquent. Mon collocataire, lorsqu’il s’est inscrit, à reçu une 50aine de mails le premier jour et a pris peur. Je lui ai dit de ne pas se décourager mais c’est vrai qu’il faut prendre l’habitude de faire le ménage tous les jours.
+
+Comment fonctionne l’AMAP ?
+
+La particularité de cette Amap c’est que c’est géré par les membres, sans chef, hierarchie, contrôle. C’est l’initiative des gens qui engage les choses. Quelqu’un veut prendre des commandes aurpès d’un producteur, il peut lancer le truc. Il y a de plus en plus de producteurs et de produits différents, avec tout le monde qui met la main à la pâte pour distribuer, organiser etc..c’est basé sur la confiance. Il y a la question de la transmission qui est très importante dans la cohésion du groupe.
+
+J’avais vu sur le site internet qu’il y avait un forum, est-ce que c’est utilisé ?
+
+Non, ça c’est le 1er site qui a été conçu mais il n’est plus utilisé aujourd’hui. Je n’ai pas connu cette période là. Le forum a juste été testé mais ça n’a pas été concluant. Ce réseau fonctionne aussi beaucoup par le contact direct, on a un cahier où l’on fait des croix, pour les présences, les commances.. la ccopérative à coté fonctionne en parallèle avec un site qui permet de s’inscrire lorsque l’on ne peut pas se déplacer, pour les permanences par exemple, mais sans que a remette en cause l’existence du cahier : on repporte les inscritptions en ligne sur le cahier et pas le contraire.
+Ne pas tout numériser systématiquement ça oblige aussi plus ou moins à venir, à se parler, se rencontrer. Et puis tout le monde n’a pas accès à ces outils. 
+
+Avec autant d’inscrit c’est étonnant que ça marche aussi simplement.
+Oui surtout qu’aujourd’hui il y a deux coop, la dyonicoop qui compte aussi environs 400 coopérateur, et la coop Bel air qui en a une centaine. Elles sont toutes deux issues du réseau amapien, c’est les même personnes qui ont initiés la coopérative, qui date de 2/3 ans, mais ça attire un peu d’autres personnes aussi. Ca reste plus rare car ça reste le même réseau de personne.
+Par exemple, l’amap est ici situé dans la cité Langevin, et je crois qu’on a aucun inscrit dans les habitants de la cité, ce qui peut être assez étonnant..
+
+Et par exemple tes colloc tu les as trouvés sur le réseau de l’Amap ?
+Oui, je suis passé par la mailist pour me simplifier la vie et en me disant que j’avais certainement plus de chance d’y trouver des personnes avec qui j’aurais des points communs. J’ai trouvé très facilement des gens très enthousiastes. Pour ça l’amap me simplifie considérablement la vie, je n’ai plus cette sensation de manquer de quelque chose, lorsque je fais une recherche je suis quasi-certains de trouver une réponse à ma question ou une solution à mon problème via ce réseau.  Il y a toujours quelqu’un pour te répondre. C’est comme ça que j’ai rencontré Elodie aussi.
+
+D’ailleurs il y a quelques jours j’étais avec quelqu’un que tu connais apparement, Solen, qui m’a parlé de votre projet commun et m’as dit que tu saurais certainement mieux en parler qu’elle.
+On a monté un collectif il y a quelques mois, qui s’appelle YAKA. Ce qui nous interessait à la base c’était d’ouvrir un bar à vocation d’accueillir et de faire vivre toute la mixité de Saint-Denis. L’idée du bar c’est parti simplement de cette fonction de rassembler, nourrir les gens. On avait à cœur que ce soit Nomade, pour aller à la rencontre des gens. Puis ai venu le projet Coopérence qui se met doucement en place pour préparer leur futur local place du 8 mai dans lequel un café est prévu. Tout reste à imaginer et reste ouvert à qui veut pour la gestion donc on s’y interesse pour penser notre projet. Avec le centre social on est dans la même envie d’ouvrir à tous, du plus « bobo » au plus en difficulté, avec un esprit fédérateur ,de partage et de rassemblement. C’est la même envie qui porte notre projet nomade, de déconstruire les préjugés que les populations portent les unes sur les autres et donner la parole. Une personne de l’asso à déjà lancé une première action sous forme de Radio avec un collectif de Lycéen qui s’appelle les Kolporters et donc l’idée c’est de faire une radio mobile avec eux et de créer quelques émissions.
+
+Comment tu vis ta vie Dyonisienne en dehors de l’amap, des activités, des lieux emblématiques ?
+
+Je suis dans la Chorale du Jazz Club qui a été montée il y a deux ans, je suis aussi au conservatoire de Saint-Denis. J’ai connu la Chorale par la liste de l’AMAP . 
+
+Tu utilises d’autres canaux d’informations ?
+J’utilise pas mal FB aussi, par exemple je sais que si je veux des infos sur Rajganawak je peux en trouver sur Facebook bien que ce ne soit pas ce que j’utilise le plus.
+Il y a beaucoup de bouches à oreille aussi, par exemple la rencontre avec Coopérence s’est faites par une amie amapienne qui disais qu’elle allait à une réunion le lendemain et c’est comme ça que j’y suis allé. Avoir du réseau ça simplifie vraiment les choses.
+Je pense que quand on densifie son réseau direct humain, l’outil numérique reste à son stade d’outil. Il sert le contact et la rencontre. Parfois à l’AMAP on teste de nouveaux outils pour améliorer le système, comme umap pour se localiser lorsque l’on ne peut venir chercher son panier, pour qu’un amapien voisin nous dépanne. Ca crée encore d’autres liens.
+Sinon avec YAKA on utlise what’s app pour les conversations de groupe.
+
+
+
+

+ 43 - 0
user/pages/02._interviews/_pascale/text.md

@@ -0,0 +1,43 @@
+---
+title: Pascale
+image_align: left
+id: int_pascale
+---
+
+Est-ce que vous pouvez me parler un peu de la Maison Jaune ?
+C’est une maison de quartier qui a ouverte au mois de Novembre. l’idée c’est que cet espace soit mis à disposition des habitants, qu’on y organise des choses mais aussi qu’il y ai de la place pour leurs propres initiatives. Le but ce n’est pas qu’on arrive avec déjà des projets x ou y fermés, car il y a pas mal d’habitants du coin qui ne sortent pas si facilement de chez eux donc on essaye de trouver un moyen de les impliquer d’avantage et de les amener à s’approprier aussi les lieux. Les enfants font pas mal le lien car ils sont curieux et pas trop timides, ils viennent voir ce qui se passe et participent. Et parfois ,derrière les parents découvrent à travers eux, en venant les chercher. Mais c’est un vrai travail…
+
+Comment s’est créé le collectif ? Est-ce que vous vous connaissiez déjà ?
+Non pas du tout, moi j’ai eu vent de l’ouverture de ce lieu en octobre dernier, en passant dans le coin, et je cherchais justement un local pour mon projet de tricot et j’ai contacté Julia qui s’occupait un peu d’organiser tout ça et qui cherchait à s’entourer pour enclencher des dynamiques avec le voisinage.
+On se réunit tous les lundi soirs pour structurer le collectif, avec de nouvelles personnes qui s’impliquent au fur et à mesure.
+
+Explication du projet, j’évoque le questionnement de la résidence sur la notion de réseau, de connexion et des outils numériques dans nos échanges..
+Pascale réagit :
+C’est vrai que l’outil numérique permet d’agréger des personnes qui ne sont pas forcément disponibles en temps et en heure,  ou n’ont pas la possibilité de se déplacer, mais qui souhaitent quand même être informés, de participer et d’être tenus au courant des activités. Pour nous, en tant qu’association – avec la Maison Jaune - c’est une problématique intéressante et qui nous concerne directement. 
+
+Est-ce que vous avez vécu la coupure Internet d’il y a quelques semaines ? Et est-ce que cela a impacté votre quotidien, vos habitudes ?
+Oui, j’ai été coupé pendant une dizaine de jours et forcément, ça impact : on regarde moins souvent les mails, on est un peu moins «  au courant ». On est un peu coupés du reste, des information et du réseau professionnel. Je ne dirais pas que ça ai impacté le plan « social » par contre, ça a retardé, c’est sûr, mais n’a pas été l’objet d’une rupture de contact. C’est surtout la fluidité des recherches et de l’information que ça entrave.
+
+Quel pratique vous avez du numérique et du web ?
+J’en ai un usage très régulier. Les échanges de mails sont quotidiens surtout dans le travail mais j’utilise aussi beaucoup les réseaux sociaux comme Facebook ou même instagram que j’utilise depuis très peu de temps essentiellement pour communiquer sur les projets, avoir de la visibilité. C’est un peu comme une vitrine de projet.
+Sur Facebook, j’ai ma page professionnelle pour mon travail de plasticienne, une page plus privée où je partage les petites choses que j’aime et j’ai crée une page spécifique au projet de tricot partage pour pouvoir communiquer facilement avec les participants. 
+
+Est-ce que vous pouvez m’en dire un peu plus sur le projet tricot-partage que bous venez d’évoquer ?
+Ce projet de tricot à été démarré au mois de Fevrier. Globalement c’est un projet de tricot solidaire. L’idée était, au vu de la grosse vague de froid, de réaliser avec la participation de public des carrés de laine de 30cm de côté assemblés en couvertures. Ces couvertures sont à destination de la Croix Rouge et distribuées lors des maraudes. L’envie était aussi que chacun, tout âge confondu, puisse participer à sa manière, en venant pour tricoter, pour apprendre ou pour transmettre. Il y a beaucoup de dames expérimentés qui ont formés des débutants, même des petits. Beaucoup d’enfants sont revenus après avoir appris comme ça, pour continuer leurs œuvres. Ils se sont pris au jeu et je garde leurs petits tricots de semaines en semaines. 
+J’ai aussi lancé un appel à dons de laine et j’ai reçu des colis de laine de plusieurs villes de France par des personnes qui avaient eu vent du projet à travers la page Facebook. J’ai reçu comme ça des boîtes à chaussures remplie de pelotes venus de Paris mais aussi de Bretagne, de Nice.. j’ai même eu un message d’une personne dans un petit village d’ardèche, qui m’a écrit en me disant qu’elle tricoterait des petits carrés de laine pour nous les envoyer.
+Des habitants de Saint-Denis m’appellent ou me contactent sur internet pour me dire qu’ils ont vus nos communications papier ou autre, pour me prévenir qu’ils ont de la laine à offrir.
+Le projet est aussi nomade, ce qui nous permet au fil du temps de rencontrer et de rallier d’avantage de personnes et d’ouvrir le réseau initial. D’ailleurs on a l’envie de participer à des événements nationaux.
+Dans le symbole, c’est un projet fort, ce que j’appelle le « fil participatif » : le tricot nous permet de tisser du lien. La mixité qu’on y retrouve est forte entre les catégories sociales, les cultures, les générations et ça a répondu à beaucoup d’attente en terme de convivialité mais aussi de bien être. Parce que le tricot c’est aussi un moment de méditation et d’échange, les gens se rencontrent.
+Il y a quelques temps, chez l’imprimeur, je faisais mes photocopies de flyers pour l’évenement à la Maison Jaune. Au moment de régler j’ai posé mes copies sur le comptoir. Une dame était derrière moi et a vu « tricot » et m’a demandé ce qu’était l’événement. Je lui ai donc expliqué que c’était à la Maison Jaune, en quoi consistait le tricot partage, et elle est venue. Depuis, elle vient régulièrement et nous sommes assez liées. On s’est donc rencontrée par ce heureux hasard et il se trouve que j’aurais fait mes photocopies 10 min avant ou 10min après ça n’aurait peut-être jamais eu lieu.
+
+Globalement, comment est-ce que vous communiquez sur vos projets ?
+Pour ce projet, je communique les horaires et dates de rencontre surtout par mailing. Et par des affiches et des flyers dans le quartier. 
+
+Est-ce que vous pensez que vous auriez moins de monde présents aux rencontres de tricot sans la diffusion sur les réseaux web ?
+Oui sans doute, même s’il y en a certains qui viennent parce qu’ils ont vu l’affiche, ou un des flyers car j’en dispose un peu partout, mais c’est vrai que la communication papier et la communication numérique sont vraiment complémentaires. Certains utilisent les deux régulièrement mais d’autres sont beaucoup moins familiers de l’outil informatique. Il y a des personnes qui n’ont pas d’e-mail et ne sont joignables que par téléphone, voire qui n’ont pas de téléphone portable.  
+Je crois aussi que c’est bien de ne pas uniformiser l’information, je remarque que même les personnes habituées à communiquer par mail ou à se renseigner sur internet aiment bien avoir le petit papier qui récapitule les information à mettre dans le sac ou à placarder sur le frigo.
+
+Comment est-ce que vous êtes relié au tissu associatif environnant ?
+Déjà il faut dire qu’à saint Denis il y a énormément d’associations, qui balayent un éventail énorme. Que ce soit artistique, culturel, sportive, très militantes ou autour des cultures. On a beaucoup d’assos qui représentent des communautés d’à travers le monde. 
+Il y a beaucoup de bouche à oreille mais il y a aussi la Maison de la vie associative qui regroupe la communication des différentes structures. On a aussi le journal de saint Denis qui fonctionne bien où il y a un espace pour les assos, publier des annonces etc. Et puis y’a quelques lieux emblématiques ou l’on dépose nos flyers, comme la crêperie La Bigoudène ou beaucoup de gens se retrouvent le matin pour boire le café, le cinéma, la maison de la vie associative..
+

+ 38 - 0
user/pages/02._interviews/_solen/text.md

@@ -0,0 +1,38 @@
+---
+title: Solen
+image_align: left
+id: int_solen
+---
+
+Rencontrée au Pavillon le 3 octobre à 17h30
+C’est Manon Dumond qui m’a transmis son contact.
+
+Pour commencer, est-ce que tu peux me parler de ton lien avec Saint-denis, si tu y habites, y travailles.. ?
+
+J’habite à Saint-Denis depuis 4 ans, j’habite juste à côté de là où on se trouve, je suis arrivée en Juillet 2013 après un master d’urbanisme à Lyon. J’ai trouvé du travail dans le Val d’Oise et Saint-Denis s’est avéré être un bon compromis géographique entre le Val d’Oise et Paris, et financièrement aussi. J’avais une amie qui venait de trouver du boulot dans le nord de Paris donc on a cherché à faire une collocation ici. Quand je suis venue visiter l’appartement j’ai été agréablement surprise de Saint-Denis, j’en avais une mauvaise image.
+Avec ma deuxième colloc, on s’est inscrites à l’AMAP, et ça m’a ouvert sur un réseau Dyonsien que je n’avais pas du tout. Ca fonctionne beaucoup par mails et beaucoup de gens se font passer des annonces en tous genre. C’est comme ça que j’ai vu une annonce pour des cours de chants qui s’avéraient être à deux pas de chez moi, donc je suis allée voir. Et via ce cours j’ai commencé à rencontrer d’autres personnes sur Saint-Denis, à sortir plus. 
+J’ai eu une période de chômage aussi qui m’a permis de m’investir dans des assos, dans la coopérative de la Ferme et aux permanences de l’AMAP. Ca m’a permis de rencontrer une femme qui était en formation de coaching et je lui ai confié mes questionnements sur ma vie professionnel donc je suis devenu un peu son cobaye de coaching.
+Dans ce questionnement de reconversion professionnel j’ai beaucoup réfléchi à un projet d’ouverture de café culturel dans Saint-Denis. J’ai commencé à en parler autour de moi, notamment à une personne de l’AMAP qui s’appelle Oliver.
+
+Olivier ? Ah ben tiens, justement, je le rencontre Jeudi ..
+
+Ah bon ? C’est drôle, c’est vers lui que je t’aurais envoyé.
+
+Ben du coup il pourra t’en parler aussi. On en a beaucoup discuté ensemble car c’est une envie qu’il avait aussi depuis son arrivée à Saint-Denis. Ici à part le Pavillon il n’y a pas vraiment « d’ambiance café ». Il y a Point Carré maintenant mais c’est pas un « grand » café. A lyon j’avais plus ça, des café ou tu peux te poser en journée, un peu ambiance canapé où tu te vois bien même aller travailler, et je retrouve pas trop ça à Saint-Denis. Et comme Olivier connais beaucoup de monde à Saint-Denis, via l’AMAP aussi, il a proposé qu’on se retrouve à 4/5personnes pour discuter de cette envie. Il y avait un peu deux idées, certains partisans d’un café mobile et d’autres plutot un espace permanent, on s’est dit que l’un n’empêche pas l’autre.. on s’est réunis comme ça assez régulièrement pour définir ce qu’on voulait mettre dans ce projet. On a rencontré une personne de la ville de Saint-Denis qui nous a parlé des initiatives qui existent déjà à Saint-Denis, notamment, le projet de centre social coopératif, Coopérence. On a été à une de leur réunion suite à ça, on a rencontré Nadia et dans leur projet ils envisageaient une partie café donc ils étaient assez enthousiastes que des gens puissent avoir envie de s’en occuper.
+Suite à ça on a été mis en contact avec une prof d’archi qui a chapeauté l’exposition d’un concours sur le thème de « ma cantine en ville ». Des étudiants avaient imaginés des cantines mobiles et après l’exposition des structures elles étaient démontées et stockées à Nantes. Donc on a pris contact avec les étudiants et certains, des Dyonisens d’ailleurs, ont acceptés de nous donner leur structure. L’idée avec ces structures c’est de pouvoir imaginer un projet nomade dans différents quartiers. Je n’en sais pas beaucoup plus en ce moment car avec la reprise du travail j’ai du mal à être présente dans le projet.. Mais en tous cas il y a vraiment quelque chose de stimulant à Saint-Denis, pas forcément visible de l’extérieur mais quand tu commence un peu à rentrer dans certaines choses, à rencontrer des gens, tu te rends compte que ça fourmille. C’est une échelle particulière, c’est pas comme à Paris. On peut encore se rencontrer « par hasard », comme ici au Pavillon. C’est là que j’ai rencontré Manon d’ailleurs car je connaissais l’amie avec qui elle était et on a discuté comme ça.
+
+Quelles activités tu pratiques dans St-Denis, tu me parlais du Chant tout à l’heure ?
+Là je viens d’arrêter le chant car notre prof est partie dans le sud. Je vais de temps en temps à Rajganawak voir des spectacles. Je trouve qu’il manque des choses pour la vie nocturne à Saint-Denis, Rajganawak participe à changer ça. Ici, au Pavillon, il y a bien des événements mais c’est plus informel. Tu peux facilement y improviser un petit concert, une performance artistique.. mais ce n’est pas communiqué. On vient surtout pour y boire des verres.
+Andromède, une fille qui faisait du chant avec moi, est très investie dans la vie associative. Elle est sur un projet qui s’appelle le jardin des chaumettes.
+
+Quel usage des outils numériques tu as, pour communiquer, te tenir au courant ?
+
+Les événements de Rajganawak c’est essentiellement via Facebook que je suis au courant. Après j’utilise beaucoup les mails notamment concernant l’AMAP, c’est très central.
+Personnellement je m’appuie beaucoup sur FB pour tout ce qui est évenementiel. Pour communiquer avec mes amis j’utilise beaucoup messenger et what’s app. C’est surtout des groupes.
+Pour la vie Dyonisienne c’est beaucoup de bouche à oreille, je sais beaucoup de choses par Olivier parce que lui il parle a tout le monde alors il est toujours au courant. La communication est pas forcément excellente par la communication classique, mais c’est aussi qu’il n’y a pas vraiment de grosses institutions en dehors du Théâtre Gérad Phillipe. 
+
+Comment est-ce que tu perçoit le réseau qui t’entoure, est-ce que tu as le sentiments que beaucoup de réseaux fusionnent ou qu’ils ne font que se croiser ?
+Je crois que partout, dans les réseaux de personnes, on peut vite avoir l’impression d’un réseau fermé sur lui-même . C’est sûr qu’au sein de l’AMAP par exemple, ou des gens que je fréquente on retombe sur des profils assez similaires, socialement, culturellement.. qu’il y a plein d’endroits qui ont beau être proches de chez moi , je ne les fréquente pas, parce que naturellement, on va toujours vers ce qu’on connaît je pense, les lieux qui nous sont familiers. Pour autant, entre chacun de ces réseaux, il y a des ponts qui se créent.  
+
+
+

+ 39 - 0
user/pages/02._interviews/_victoria/text.md

@@ -0,0 +1,39 @@
+---
+title: victoria
+image_align: left
+id: int_victoria
+---
+
+dans un café place de la République à Paris le 25 Juillet 2017.
+Marie Preston connaissait Victoria via l’université Paris 8 et nous a mis en contact par mail.
+
+
+Explication du projet..
+...C’est donc Marie Preston, enseignante de Paris 8 où tu étudie, qui m’a envoyé vers toi..[…]
+
+Marie m’a expliqué que tu étais assez active notamment dans les réseaux militant de Saint Denis, que peux tu m’en dire ?
+Alors, moi à Paris 8 je fais partie d’un groupe d’étudiant mais qui n’est pas liée aux comités d’étudiants etc. Enfait, à l’université Paris 8 j’étais en cours d’art mais on avait ni atelier ni espace de convivialité pour se retrouver ou travailler en groupe. On a décidé de s’aménager un espace pour ça, tout autant symbolique que concret, et on a commencé à construire une cabane. Ca a pris beaucoup de temps qu’il y a eu beaucoup de bureaucratie, on avait commencé a construire sans autorisation, puis il s’est avéré qu’il nous en fallait une, etc..ça a bien pris trois ans à se monter jusqu’à ce qu’elle soit détruite cette année..par une erreur. Enfait le personnel de la logistique à commencé à s’inquiéter je pense de l’afluence qu’il y avait dedans. Ca commençait à être le bordel parcque on était beaucoup à squater. C’est l’endroit parfait, un endroit un peu caché, tout petit, mais tout le monde aimait s’y retrouver. Certains avaient commencé à aménager des espaces de jardins sur le côté. Les jardins continuent à vivre d’ailleurs. C’est vrai que nous comme on est en Master maintenant, on a moins de temps pour s’en occuper et on a pas voulu que ce soit « notre » endroit donc on a complétement laissé la main là dessus. Du coup c’est vrai que y’a pas mal de monde qui squattait, y’en avait même qui revenait le soir passer la barrière pour venir faire la fête le samedi.. c’était un peu trop du coup au bout d’un moment l’université à voulu chercher un responsable mais dans ce genre de projet y’en a pas vraiment de désigné, donc au final, un peu sur un malentendu, ça a fini par être détruit.
+Donc voilà on a arrêté avec la cabane mais de là on a rencontré d’autres étudiants assez actifs qui occupait aussi des salles, notamment pour protester contre la loi travail ou autre et on en est venus à rencontrer des personnes qui étaient dans des lieux auto-géré etc.
+On a rencontré une personne habitant de Saint Denis qui habite là depuis longtemps, qui connais pleins de choses ici et qui a participé notamment à l’occupation de l’atiéké qui est un centre social auto-géré. On a commencé à visiter ce type d’endroits auto-géré , des espaces artistiques comme La Briche.
+
+Es-tu habitante de Saint-Denis ? Quel lien tu as avec ce territoire ?
+Alors non, moi je connaissais pas du tout Saint-denis, je suis Brésilienne et j’ai habité à Aix-en-provence avant de venir ici. Je voulais entrer en école d’art mais je ne connaissais pas la différence entre les beaux arts et l’art à l’université. C’est un peu par erreur que  je suis arrivée là et c’est vrai que je voulais pas venir à Saint-Denis car je trouvais ça loin, etc. Et finalement j’ai atteri ici et j’ai trop aimé. J’ai même fait un master 1 à la Sorbone et j’ai préféré revenir ici. 
+Par contre je n’y habite pas, je vis dans Paris et les premières années dans saint denis je ne connaissais que la sortie du métro université et les alentours de Paris 8.. j’ai découvert petit à petit par des amis de la fac qui habitent dans le coin. Au début je savais à peine dans quelle direction était le centre ville.
+
+Et du coup par quels lieux tu as découvert Saint-Denis ? Des commerces, des cafés.. ?
+Je n’allais pas tellement dans les cafés, plutot chez des amis et la plupart habitent entre la gare et le centre ville donc c’est plutot là que je suis allée au début, sur le chemin. On allait dans des cafés autour de la Fac mais sinon c’est plutot des fêtes chez des amis. Et puis après j’ai découvert l’atiéké. C’était au moment où les occupant se faisaient expulser du lieu qui était désafecté et à ce moment là des étudiants de Paris 8 s’en sont mêlés et les ont aidé à occuper des lieux pour leur permettre de retrouver un espace. Y’a un groupe assez militant qui s’est formé et j’ai découvert les activités et la diversité du lieu. J’y suis beaucoup allée.
+
+Comment tu te tenais au courant des événements artistiques et autres auquels tu te rendait ?
+Ben surtout par le réseau Paris 8. Il y a toujours des affiches, pour les festivals de cinéma notamment, il se passe pas mal de choses cool au cinéma pas loin de la Basilique.
+Après mon copain il participe aussi à un lieu qui a été crée par des habitants de Saint-Denis où il y a un jardin. Il y a Martine Bodineau là bas, ce serait interessant que tu la rencontre, elle est très active ici et bavarde ! Et elle habite Saint-Denis depuis longtemps. Ils ont une petite association à côté de la Maison Jaune. 
+
+Donc pour toi ça se fait surtout par Bouche à Oreille ?
+Oui complètement, on se tiens au courant quand on se rencontre ou on s’envoie quelques messages, des mails ou des sms mais on a pas tellement investis les plate-formes styles Facebook avec les événements etc. Ca se fait plus discrètement peut être mais ça fonctionne très bien.
+On utilise beaucoup l’affiche dans les quartiers donc ça a tendance à rester très local et à fédérer des groupes d’habitants. 
+Pour la Kabane on a utilisé pas mal les outils Framasoft, des framapad etc, on a essayé d’éviter les google docs et compagnie au sein du collectif car la question du logiciel libre et de l’outil collaboratif était important pour nous. Pour faire le lien avec d’autres étudiant on utilisait surtout les listes de mails.
+On a beaucoup de gens qui ne sont pas sur Facebook dans nos contacts et on essaye aussi de s’émanciper car on a tendance à s’enfermer dans les infos qui s’y trouves et à ne plus chercher l’information ailleurs.
+
+Et en parlant de Bouche à Oreille, vers qui tu pourrais m’envoyer pour la suite ?
+Je pourrais te donner le contact de Martine Bodineau dont je te parlais tout à l’heure, et de Marie qui est une personne très active et qui connais beaucoup de monde. Je vais te renvoyer leur contacts par mail.
+
+

BIN
user/pages/03._images/_rue-marie-w/DSC04024.JPG


+ 8 - 0
user/pages/03._images/_rue-marie-w/image.md

@@ -0,0 +1,8 @@
+---
+title: 'Rue Marie.W'
+media_order: DSC04024.JPG
+image_align: left
+id: img_ruemariew
+---
+
+![](DSC04024.JPG)

BIN
user/pages/03._images/_taxiphone/DSC04028.JPG


Some files were not shown because too many files changed in this diff