From fbc8b01d7efac662f9e986aea62c6dc30299b4bc Mon Sep 17 00:00:00 2001
From: "Frederic G. MARAND" <fgm@osinet.fr>
Date: Mon, 24 Mar 2014 19:29:42 +0100
Subject: [PATCH] Working url_alias overview/edit/submit functionality.

- Apply on top of the Path core patch #2209145
---
 lib/Drupal/mongodb/Path.php                  |  119 ++++++++++++++++++++++++--
 lib/Drupal/mongodb/Routing/RouteProvider.php |   72 ++++++++++++++--
 mongodb.services.yml                         |   12 +--
 3 files changed, 183 insertions(+), 20 deletions(-)

diff --git a/lib/Drupal/mongodb/Path.php b/lib/Drupal/mongodb/Path.php
index ee4142a..c30dcf9 100644
--- a/lib/Drupal/mongodb/Path.php
+++ b/lib/Drupal/mongodb/Path.php
@@ -9,16 +9,17 @@ namespace Drupal\mongodb;
 
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Path\PathInterface;
 
 /**
  * Defines a class for CRUD operations on path aliases in MongoDB.
  */
-class Path {
+class Path implements PathInterface {
 
   /**
    * The object wrapping the MongoDB database object.
    *
-   * @var MongoCollectionFactory
+   * @var \Drupal\mongodb\MongoCollectionFactory
    */
   protected $mongo;
 
@@ -52,6 +53,14 @@ class Path {
   }
 
   /**
+   * @return \MongoCollection
+   */
+  public function urlAlias() {
+    $ret = $this->mongo->get($this->mongo_collection);
+    return $ret;
+  }
+
+  /**
    * Saves a path alias to the database.
    *
    * @param string $source
@@ -108,11 +117,16 @@ class Path {
    * {@inheritdoc}
    */
   public function load($conditions) {
-    $result = $this->mongo->get($this->mongo_collection)->findOne($conditions);
+    if (isset($conditions['pid'])) {
+      $conditions['_id'] = intval($conditions['pid']);
+      unset($conditions['pid']);
+    }
+    $result = $this->urlAlias()->findOne($conditions);
     // $result will be NULL on failure, but PathInterface::load requires FALSE.
-    if (!issert($result)) {
+    if (!isset($result)) {
       $result = FALSE;
     }
+    $result['pid'] = $result['_id'];
     return $result;
   }
 
@@ -124,7 +138,11 @@ class Path {
    */
   public function delete($conditions) {
     $path = $this->load($conditions);
-    $response = $this->mongo->get($this->mongo_collection)->remove($conditions);
+    if (isset($conditions['pid'])) {
+      $conditions['_id'] = $conditions['pid'];
+      unset($conditions['pid']);
+    }
+    $response = $this->urlAlias()->remove($conditions);
     $this->module_handler->invokeAll('path_delete', $path);
     return $response['n'];
   }
@@ -242,4 +260,95 @@ class Path {
 
     return FALSE;
   }
+
+  /**
+   * @param $path_prefix
+   *
+   * @return bool
+   *
+   * TODO check if this method shouldn't be part of PathInterface
+   */
+  public function pathHasMatchingAlias($path_prefix) {
+    return FALSE;
+  }
+
+  /**
+   * Checks if alias already exists.
+   *
+   * @param string $alias
+   *   Alias to check against.
+   * @param string $langcode
+   *   Language of the alias.
+   * @param string $source
+   *   Path that alias is to be assigned to (optional).
+   * @return boolean
+   *   TRUE if alias already exists and FALSE otherwise.
+   */
+  public function aliasExists($alias, $langcode, $source = NULL) {
+    $criteria = array(
+      'alias' => $alias,
+      'langcode' => $langcode,
+    );
+    if (!empty($source)) {
+      $criteria['source'] = array('$ne' => array($source, '<>'));
+    }
+    return (bool) $this->urlAlias()->count($criteria);
+  }
+
+  /**
+   * Checks if there are any aliases with language defined.
+   *
+   * @return bool
+   *   TRUE if aliases with language exist.
+   */
+  public function languageAliasExists() {
+    $criteria = array(
+      'langcode' => Language::LANGCODE_NOT_SPECIFIED,
+    );
+    return (bool) $this->urlAlias()->count($criteria);
+  }
+
+  /**
+   * Loads aliases for admin listing.
+   *
+   * @param array $header
+   *   Table header.
+   * @param string $keys
+   *   Search keys.
+   * @return array
+   *   Array of items to be displayed on the current page.
+   */
+  public function adminListing($header, $keys = NULL) {
+    if ($keys) {
+      $criteria = array(
+        // Replace wildcards with PCRE wildcards.
+        // TODO check escaping reliability. Is should be more complex than this.
+        // @see http://www.rgagnon.com/javadetails/java-0515.html
+        'alias' => preg_replace('!\*+!', '.*', $keys),
+      );
+    }
+    else {
+      $criteria = array();
+    }
+    // TODO PagerSelectExtender
+    // TODO TableSortExtender: should be shorter than doing this every time.
+    $order = array();
+    foreach ($header as $order_element) {
+      // This happens on Operations column.
+      if (!is_array($order_element)) {
+        continue;
+      }
+      $direction = (isset($order_element['sort']) && strtolower($order_element['sort']) === 'desc') ? -1 : 1;
+      $order[$order_element['field']] = $direction;
+    }
+    $alias_arrays = $this->urlAlias()->find($criteria)->limit(50)->sort($order);
+    $alias_objects = array();
+    foreach ($alias_arrays as $alias_array) {
+      $alias_array['pid'] = $alias_array['_id'];
+      $alias_objects[] = (object) $alias_array;
+    }
+
+    return $alias_objects;
+  }
+
 }
diff --git a/lib/Drupal/mongodb/Routing/RouteProvider.php b/lib/Drupal/mongodb/Routing/RouteProvider.php
index 900f347..9e83373 100644
--- a/lib/Drupal/mongodb/Routing/RouteProvider.php
+++ b/lib/Drupal/mongodb/Routing/RouteProvider.php
@@ -2,33 +2,74 @@
 
 /**
  * @file
- * Definition of Drupal\mongodb\MongoKeyValueFactory.
+ * Contains Drupal\mongodb\Routing\RouteProvider.
  */
 
-namespace Drupal\mongodb\routing;
+namespace Drupal\mongodb\Routing;
 
+use Drupal\Core\KeyValueStore\StateInterface;
 use Drupal\Core\Routing\RouteBuilderInterface;
 use Drupal\Core\Routing\RouteProvider as SqlRouterProvider;
 use Drupal\mongodb\MongoCollectionFactory;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
 
+/**
+ * A Route Provider front-end for all Drupal-stored routes.
+ */
 class RouteProvider extends SqlRouterProvider {
 
   /**
+   * The database connection from which to read route information.
+   *
    * @var \Drupal\mongoDb\MongoCollectionFactory $mongo
    */
   protected $mongo;
 
   /**
    * @param \Drupal\mongoDb\MongoCollectionFactory $mongo
+   * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
+   *   The route builder.
+   * @param \Drupal\Core\KeyValueStore\StateInterface $state
+   *   The state.
+   * @param string $table
+   *   The table in the database to use for matching.
    */
-  function __construct(MongoCollectionFactory $mongo, RouteBuilderInterface $route_builder, $table = 'router') {
+  public function __construct(MongoCollectionFactory $mongo, RouteBuilderInterface $route_builder, StateInterface $state, $table = 'router') {
+    $this->mongo = $mongo;
     $this->routeBuilder = $route_builder;
+    $this->state = $state;
     $this->tableName = $table;
-    $this->mongo = $mongo;
   }
 
+  /**
+   * Helper: provide direct collection access to the routing collection.
+   *
+   * @return \MongoCollection
+   */
+  public function routing() {
+    return $this->mongo->get('routing');
+  }
+
+  /**
+   * Find many routes by their names using the provided list of names.
+   *
+   * Note that this method may not throw an exception if some of the routes
+   * are not found. It will just return the list of those routes it found.
+   *
+   * This method exists in order to allow performance optimizations. The
+   * simple implementation could be to just repeatedly call
+   * $this->getRouteByName().
+   *
+   * @param array $names
+   *   The list of names to retrieve.
+   * @param array $parameters
+   *   The parameters as they are passed to the UrlGeneratorInterface::generate
+   *   call. (Only one array, not one for each entry in $names).
+   *
+   * @return \Symfony\Component\Routing\Route[]
+   *   Iterable thing with the keys the names of the $names argument.
+   */
   public function getRoutesByNames($names, $parameters = []) {
 
     if (empty($names)) {
@@ -38,9 +79,10 @@ class RouteProvider extends SqlRouterProvider {
     $routes_to_load = array_diff($names, array_keys($this->routes));
 
     if ($routes_to_load) {
-      $result = $this->mongo->get('routing')
+      $routes  = $this->routing()
         ->find(array('_id' => array('$in' => $routes_to_load)));
-      foreach ($result as $name => $route_array) {
+
+      foreach ($routes as $name => $route_array) {
         $this->routes[$name] = $this->getRouteFromArray($route_array);
       }
     }
@@ -48,6 +90,15 @@ class RouteProvider extends SqlRouterProvider {
     return array_intersect_key($this->routes, array_flip($names));
   }
 
+  /**
+   * Get all routes which match a certain pattern.
+   *
+   * @param string $path
+   *   The route pattern to search for (contains % as placeholders).
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   Returns a route collection of matching routes.
+   */
   protected function getRoutesByPath($path) {
     // Filter out each empty value, though allow '0' and 0, which would be
     // filtered out by empty().
@@ -57,11 +108,12 @@ class RouteProvider extends SqlRouterProvider {
 
     $ancestors = $this->getCandidateOutlines($parts);
 
-    $result = $this->mongo->get('routing')
+    $routes = $this->routing()
       ->find(array('pattern_outline' => array('$in' => $ancestors)))
       ->sort(array('fit' => -1, '_id' => 1));
     $collection = new RouteCollection();
-    foreach ($result as $name => $route_array) {
+
+    foreach ($routes as $name => $route_array) {
       $route = $this->getRouteFromArray($route_array);
       if (preg_match($route->compile()->getRegex(), $path, $matches)) {
         $collection->add($name, $route);
@@ -75,6 +127,7 @@ class RouteProvider extends SqlRouterProvider {
    * Creates a Route object from an array.
    *
    * @param array $r
+   *
    * @return Route
    */
   protected function getRouteFromArray(array $r) {
@@ -88,7 +141,8 @@ class RouteProvider extends SqlRouterProvider {
       'condition' => '',
       'path' => $r['pattern_outline'],
     );
-    return new Route($r['path'], $r['defaults'], $r['requirements'], $r['options'], $r['host'], $r['schemes'], $r['methods'], $r['condition']);
+    return new Route($r['path'], $r['defaults'], $r['requirements'],
+      $r['options'], $r['host'], $r['schemes'], $r['methods'], $r['condition']);
   }
 
 }
diff --git a/mongodb.services.yml b/mongodb.services.yml
index 68aaeb5..7686ba7 100644
--- a/mongodb.services.yml
+++ b/mongodb.services.yml
@@ -33,11 +33,11 @@ services:
     arguments: ['@mongo', snapshot]
     tags:
       - { name: mongodb.override }
-  #mongodb.path.crud:
-  #  class: Drupal\mongodb\Path
-  #  arguments: ['@mongo', '@module_handler']
-  #  tags:
-  #    - { name: mongodb.override }
+  mongodb.path.crud:
+    class: Drupal\mongodb\Path
+    arguments: ['@mongo', '@module_handler']
+    tags:
+      - { name: mongodb.override }
   mongodb.router.dumper:
     class: Drupal\mongodb\Routing\MatcherDumper
     arguments: ['@mongo']
@@ -45,7 +45,7 @@ services:
       - { name: mongodb.override }
   mongodb.router.route_provider:
     class: Drupal\mongodb\Routing\RouteProvider
-    arguments: ['@mongo', '@router.builder']
+    arguments: ['@mongo', '@router.builder', '@state']
     tags:
       - { name: mongodb.override }
       - { name: event_subscriber }
-- 
1.7.9.5

