Add basic Horde_Url class.
authorJan Schneider <jan@horde.org>
Thu, 3 Dec 2009 14:06:46 +0000 (15:06 +0100)
committerJan Schneider <jan@horde.org>
Thu, 3 Dec 2009 14:06:46 +0000 (15:06 +0100)
framework/Url/lib/Horde/Url.php [new file with mode: 0644]
framework/Url/package.xml [new file with mode: 0644]
framework/Url/test/Horde/Url/AddTest.php [new file with mode: 0644]
framework/Url/test/Horde/Url/AllTests.php [new file with mode: 0644]
framework/Url/test/Horde/Url/RemoveTest.php [new file with mode: 0644]
framework/Util/lib/Horde/Util.php
framework/Util/test/Horde/Util/addParameter.phpt
framework/Util/test/Horde/Util/removeParameter.phpt

diff --git a/framework/Url/lib/Horde/Url.php b/framework/Url/lib/Horde/Url.php
new file mode 100644 (file)
index 0000000..c2716c5
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+/**
+ * This file contains the Horde_Url class for manipulating URLs.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Jan Schneider <jan@horde.org>
+ * @author   Michael Slusarz <slusarz@curecanti.org>
+ * @category Horde
+ * @package  Horde_Url
+ */
+
+/**
+ * The Horde_Url class represents a single URL and provides methods for
+ * manipulating URLs.
+ *
+ * @author   Jan Schneider <jan@horde.org>
+ * @author   Michael Slusarz <slusarz@curecanti.org>
+ * @category Horde
+ * @package  Horde_Url
+ */
+class Horde_Url
+{
+    /**
+     * The basic URL, without query parameters.
+     *
+     * @var string
+     */
+    public $url;
+
+    /**
+     * The query parameters.
+     *
+     * The keys are paramter names, the values parameter values. Array values
+     * will be added to the URL using name[]=value notation.
+     *
+     * @var array
+     */
+    public $parameters;
+
+    /**
+     * Whether to output the URL in the raw URL format or HTML-encoded.
+     *
+     * @var boolean
+     */
+    public $raw;
+
+    /**
+     * Constructor.
+     *
+     * @param string $url   The basic URL, with or without query parameters.
+     * @param boolean $raw  Whether to output the URL in the raw URL format or
+     *                      HTML-encoded.
+     */
+    public function __construct($url, $raw = false)
+    {
+        if (strpos($url, '?') !== false) {
+            list($url, $query) = explode('?', $url);
+
+            /* Check if the argument separator has been already
+             * htmlentities-ized in the URL. */
+            if (preg_match('/=.*?&amp;.*?=/', $query)) {
+                $query = html_entity_decode($query);
+                $raw = false;
+            } elseif (preg_match('/=.*?&.*?=/', $query)) {
+                $raw = true;
+            }
+            $pairs = explode('&', $query);
+            foreach ($pairs as $pair) {
+                @list($parameter, $value) = explode('=', urldecode($pair), 2);
+                $this->add($parameter, $value);
+            }
+        }
+
+        $this->url = $url;
+        $this->raw = $raw;
+    }
+
+    /**
+     * Adds one or more query parameters.
+     *
+     * @param mixed $parameters  Either the name value or an array of
+     *                           name/value pairs.
+     * @param string $value      If specified, the value part ($parameters is
+     *                           then assumed to just be the parameter name).
+     *
+     * @return Horde_Url  This (modified) object, to allow chaining.
+     */
+    public function add($parameters, $value = null)
+    {
+        if (!is_array($parameters)) {
+            $parameters = array($parameters => $value);
+        }
+
+        foreach ($parameters as $parameter => $value) {
+            if (substr($parameter, -2) == '[]') {
+                $parameter = substr($parameter, 0, -2);
+                if (!isset($this->parameters[$parameter])) {
+                    $this->parameters[$parameter] = array();
+                }
+                $this->parameters[$parameter][] = $value;
+            } else {
+                $this->parameters[$parameter] = $value;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Removes one ore more parameters.
+     *
+     * @param mixed $remove  Either a single parameter to remove or an array
+     *                       of parameters to remove.
+     *
+     * @return Horde_Url  This (modified) object, to allow chaining.
+     */
+    public function remove($parameters)
+    {
+        if (!is_array($parameters)) {
+            $parameters = array($parameters);
+        }
+
+        foreach ($parameters as $parameter) {
+            unset($this->parameters[$parameter]);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Creates the full URL string.
+     *
+     * @return string  The string representation of this object.
+     */
+    public function __toString()
+    {
+        $url_params = array();
+        foreach ($this->parameters as $parameter => $value) {
+            if (is_array($value)) {
+                foreach ($value as $val) {
+                    $url_params[] = rawurlencode($parameter) . '[]=' . rawurlencode($val);
+                }
+            } else {
+                if (strlen($value)) {
+                    $url_params[] = rawurlencode($parameter) . '=' . rawurlencode($value);
+                } else {
+                    $url_params[] = rawurlencode($parameter);
+                }
+            }
+        }
+
+        return count($url_params)
+            ? $this->url . '?' . implode($this->raw ? '&' : '&amp;', $url_params)
+            : $this->url;
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Url/package.xml b/framework/Url/package.xml
new file mode 100644 (file)
index 0000000..37705e6
--- /dev/null
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Url</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Url class</summary>
+ <description>This class represents a single URL and provides methods for manipulating URLs.</description>
+ <lead>
+  <name>Jan Schneider</name>
+  <user>jan</user>
+  <email>jan@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Michael Slusarz</name>
+  <user>slusarz</user>
+  <email>slusarz@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2009-12-03</date>
+ <version>
+  <release>0.1.0</release>
+  <api>0.1.0</api>
+ </version>
+ <stability>
+  <release>beta</release>
+  <api>beta</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial package.
+ </notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <file name="Url.php" role="php" />
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.5.4</min>
+   </pearinstaller>
+  </required>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Url.php" as="Horde/Url.php" />
+  </filelist>
+ </phprelease>
+ <changelog/>
+</package>
diff --git a/framework/Url/test/Horde/Url/AddTest.php b/framework/Url/test/Horde/Url/AddTest.php
new file mode 100644 (file)
index 0000000..33416e6
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @author     Jan Schneider <jan@horde.org>
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @category   Horde
+ * @package    Horde_Url
+ * @subpackage UnitTests
+ */
+
+class Horde_Url_AddTest extends PHPUnit_Framework_TestCase
+{
+    public function testAddSimple()
+    {
+        $url = new Horde_Url('test');
+        $url->add('foo', 1);
+        $this->assertEquals('test?foo=1', (string)$url);
+        $url->add('bar', 2);
+        $this->assertEquals('test?foo=1&amp;bar=2', (string)$url);
+        $url->add('baz', 3);
+        $this->assertEquals('test?foo=1&amp;bar=2&amp;baz=3', (string)$url);
+    }
+
+    public function testAddArray()
+    {
+        $url = new Horde_Url('test');
+        $url->add(array('foo' => 1, 'bar' => 2));
+        $this->assertEquals('test?foo=1&amp;bar=2', (string)$url);
+
+        $url = new Horde_Url('test?foo=1');
+        $url->add(array('bar' => 2, 'baz' => 3));
+        $this->assertEquals('test?foo=1&amp;bar=2&amp;baz=3', (string)$url);
+    }
+
+    public function testAddToExistingUrl()
+    {
+        $url = new Horde_Url('test?foo=1&bar=2');
+        $url->add(array('baz' => 3));
+        $this->assertEquals('test?foo=1&bar=2&baz=3', (string)$url);
+
+        $url = new Horde_Url('test?foo=1&bar=2');
+        $url->add(array('foo' => 1, 'bar' => 3));
+        $this->assertEquals('test?foo=1&bar=3', (string)$url);
+
+        $url = new Horde_Url('test?foo=1&amp;bar=2');
+        $url->add('baz', 3);
+        $this->assertEquals('test?foo=1&amp;bar=2&amp;baz=3', (string)$url);
+    }
+
+    public function testAddRaw()
+    {
+        $url = new Horde_Url('test');
+        $url->add('foo', 'bar&baz');
+        $this->assertEquals('test?foo=bar%26baz', (string)$url);
+        $url->add('x', 'y');
+        $this->assertEquals('test?foo=bar%26baz&amp;x=y', (string)$url);
+        $url->raw = true;
+        $url->add('x', 'y');
+        $this->assertEquals('test?foo=bar%26baz&x=y', (string)$url);
+
+        $url = new Horde_Url('test');
+        $url->add('x', 'y')
+            ->add('foo', 'bar&baz');
+        $this->assertEquals('test?x=y&amp;foo=bar%26baz', (string)$url);
+    }
+
+    public function testAddChaining()
+    {
+        $url = new Horde_Url('test');
+        $url->add('foo', 1)
+            ->add('bar', 2)
+            ->add('baz', 3);
+        $this->assertEquals('test?foo=1&amp;bar=2&amp;baz=3', (string)$url);
+    }
+}
diff --git a/framework/Url/test/Horde/Url/AllTests.php b/framework/Url/test/Horde/Url/AllTests.php
new file mode 100644 (file)
index 0000000..3a34b34
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Horde_Url test suite
+ *
+ * @author     Jan Schneider <jan@horde.org>
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @category   Horde
+ * @package    Horde_Url
+ * @subpackage UnitTests
+ */
+
+/**
+ * Define the main method
+ */
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Horde_Url_AllTests::main');
+}
+
+/**
+ * Prepare the test setup.
+ */
+require_once 'Horde/Test/AllTests.php';
+
+/**
+ * @package    Horde_Url
+ * @subpackage UnitTests
+ */
+class Horde_Url_AllTests extends Horde_Test_AllTests
+{
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Url_AllTests::main') {
+    Horde_Url_AllTests::main('Horde_Url', __FILE__);
+}
diff --git a/framework/Url/test/Horde/Url/RemoveTest.php b/framework/Url/test/Horde/Url/RemoveTest.php
new file mode 100644 (file)
index 0000000..9b06af7
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @author     Jan Schneider <jan@horde.org>
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @category   Horde
+ * @package    Horde_Url
+ * @subpackage UnitTests
+ */
+
+class Horde_Url_RemoveTest extends PHPUnit_Framework_TestCase
+{
+    public function testRemoveRaw()
+    {
+        $url = new Horde_Url('test?foo=1&bar=2');
+        $this->assertEquals('test?bar=2', (string)$url->remove('foo'));
+
+        $url = new Horde_Url('test?foo=1&bar=2');
+        $this->assertEquals('test?foo=1', (string)$url->remove('bar'));
+
+        $url = new Horde_Url('test?foo=1&bar=2');
+        $this->assertEquals('test', (string)$url->remove(array('foo', 'bar')));
+
+        $url = new Horde_Url('test?foo=1&bar=2&baz=3');
+        $this->assertEquals('test?bar=2&baz=3', (string)$url->remove('foo'));
+    }
+
+    public function testRemoveEncoded()
+    {
+        $url = new Horde_Url('test?foo=1&amp;bar=2');
+        $this->assertEquals('test?bar=2', (string)$url->remove('foo'));
+
+        $url = new Horde_Url('test?foo=1&amp;bar=2');
+        $this->assertEquals('test?foo=1', (string)$url->remove('bar'));
+
+        $url = new Horde_Url('test?foo=1&amp;bar=2');
+        $this->assertEquals('test', (string)$url->remove(array('foo', 'bar')));
+
+        $url = new Horde_Url('test?foo=1&amp;bar=2&amp;baz=3');
+        $this->assertEquals('test?bar=2&amp;baz=3', (string)$url->remove('foo'));
+    }
+
+    public function testRemoveChaining()
+    {
+        $url = new Horde_Url('test?foo=1&bar=2');
+        $this->assertEquals('test', (string)$url->remove('foo')->remove('bar'));
+    }
+}
index e07b9a3..e130fe4 100644 (file)
@@ -233,56 +233,13 @@ class Horde_Util
             return $url;
         }
 
-        $add = array();
-        $arg = $encode ? '&amp;' : '&';
-
-        if (strpos($url, '?') !== false) {
-            list($url, $query) = explode('?', $url);
-
-            /* Check if the argument separator has been already
-             * htmlentities-ized in the URL. */
-            if (preg_match('/=.*?&amp;.*?=/', $query)) {
-                $query = html_entity_decode($query);
-                $arg = '&amp;';
-            } elseif (preg_match('/=.*?&.*?=/', $query)) {
-                $arg = '&';
-            }
-            $pairs = explode('&', $query);
-            foreach ($pairs as $pair) {
-                $pair = explode('=', urldecode($pair), 2);
-                $pair_val = (count($pair) == 2) ? $pair[1] : '';
-                if (substr($pair[0], -2) == '[]') {
-                    $name = substr($pair[0], 0, -2);
-                    if (!isset($add[$name])) {
-                        $add[$name] = array();
-                    }
-                    $add[$name][] = $pair_val;
-                } else {
-                    $add[$pair[0]] = $pair_val;
-                }
-            }
-        }
-
-        if (is_array($parameter)) {
-            $add = array_merge($add, $parameter);
-        } else {
-            $add[$parameter] = $value;
-        }
-
-        $url_params = array();
-        foreach ($add as $parameter => $value) {
-            if (is_array($value)) {
-                foreach ($value as $val) {
-                    $url_params[] = rawurlencode($parameter) . '[]=' . rawurlencode($val);
-                }
-            } else {
-                $url_params[] = rawurlencode($parameter) . '=' . rawurlencode($value);
-            }
+        if ($url instanceof Horde_Url) {
+            $url->raw = !$encode;
+            return $url->add($parameter, $value);
         }
 
-        return count($url_params)
-            ? $url . '?' . implode($arg, $url_params)
-            : $url;
+        $horde_url = new Horde_Url($url, !$encode);
+        return $horde_url->add($parameter, $value);
     }
 
     /**
@@ -296,61 +253,13 @@ class Horde_Util
      */
     static public function removeParameter($url, $remove)
     {
-        if (!is_array($remove)) {
-            $remove = array($remove);
-        }
-
-        /* Return immediately if there are no parameters to remove. */
-        if (($pos = strpos($url, '?')) === false) {
-            return $url;
-        }
-
-        $entities = false;
-        list($url, $query) = explode('?', $url, 2);
-
-        /* Check if the argument separator has been already
-         * htmlentities-ized in the URL. */
-        if (preg_match('/=.*?&amp;.*?=/', $query)) {
-            $entities = true;
-            $query = html_entity_decode($query);
-        }
-
-        /* Get the list of parameters. */
-        $pairs = explode('&', $query);
-        $params = array();
-        foreach ($pairs as $pair) {
-            $pair = explode('=', $pair, 2);
-            $params[$pair[0]] = count($pair) == 2 ? $pair[1] : '';
-        }
-
-        /* Remove the parameters. */
-        foreach ($remove as $param) {
-            unset($params[$param]);
-        }
-
-        if (!count($params)) {
-            return $url;
-        }
-
-        /* Flatten arrays.
-         * FIXME: should handle more than one array level somehow. */
-        $add = array();
-        foreach ($params as $key => $val) {
-            if (is_array($val)) {
-                foreach ($val as $v) {
-                    $add[] = $key . '[]=' . $v;
-                }
-            } else {
-                $add[] = $key . '=' . $val;
-            }
-        }
+        $horde_url = new Horde_Url($url);
 
-        $query = implode('&', $add);
-        if ($entities) {
-            $query = htmlentities($query);
+        if ($url instanceof Horde_Url) {
+            return $url->remove($parameter);
         }
 
-        return $url . '?' . $query;
+        return $horde_url->remove($remove);
     }
 
     /**
index 3bc4fb9..b769040 100644 (file)
@@ -3,6 +3,7 @@ Horde_Util::addParameter() tests
 --FILE--
 <?php
 
+require_once 'Horde/Url.php';
 require_once dirname(__FILE__) . '/../../../lib/Horde/Util.php';
 
 $url = 'test';
index bb69080..ef98e37 100644 (file)
@@ -3,6 +3,7 @@ Horde_Util::removeParameter() tests
 --FILE--
 <?php
 
+require_once 'Horde/Url.php';
 require_once dirname(__FILE__) . '/../../../lib/Horde/Util.php';
 
 $url = 'test?foo=1&bar=2';