sfOutputEscaper.class.php 6.32 KB
Newer Older
Игорь's avatar
init    
Игорь committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<?php

/*
 * This file is part of the symfony package.
 * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Abstract class that provides an interface for escaping of output.
 *
 * @package    symfony
 * @subpackage view
 * @author     Mike Squire <mike@somosis.co.uk>
 * @version    SVN: $Id: sfOutputEscaper.class.php 21908 2009-09-11 12:06:21Z fabien $
 */
abstract class sfOutputEscaper
{
  /**
   * The value that is to be escaped.
   *
   * @var mixed
   */
  protected $value;

  /**
   * The escaping method that is going to be applied to the value and its
   * children. This is actually the name of a PHP callable.
   *
   * @var string
   */
  protected $escapingMethod;

  static protected $safeClasses = array();

  /**
   * Constructor stores the escaping method and value.
   *
   * Since sfOutputEscaper is an abstract class, instances cannot be created
   * directly but the constructor will be inherited by sub-classes.
   *
   * @param string $escapingMethod  Escaping method
   * @param string $value           Escaping value
   */
  public function __construct($escapingMethod, $value)
  {
    $this->value          = $value;
    $this->escapingMethod = $escapingMethod;
  }

  /**
   * Decorates a PHP variable with something that will escape any data obtained
   * from it.
   *
   * The following cases are dealt with:
   *
   *    - The value is null or false: null or false is returned.
   *    - The value is scalar: the result of applying the escaping method is
   *      returned.
   *    - The value is an array or an object that implements the ArrayAccess
   *      interface: the array is decorated such that accesses to elements yield
   *      an escaped value.
   *    - The value implements the Traversable interface (either an Iterator, an
   *      IteratorAggregate or an internal PHP class that implements
   *      Traversable): decorated much like the array.
   *    - The value is another type of object: decorated such that the result of
   *      method calls is escaped.
   *
   * The escaping method is actually the name of a PHP callable. There are a set
   * of standard escaping methods listed in the escaping helper
   * (EscapingHelper.php).
   *
   * @param  string $escapingMethod  The escaping method (a PHP callable) to apply to the value
   * @param  mixed  $value           The value to escape
   *
   * @return mixed Escaping value
   *
   * @throws InvalidArgumentException If the escaping fails
   */
  public static function escape($escapingMethod, $value)
  {
    if (null === $value)
    {
      return $value;
    }

    // Scalars are anything other than arrays, objects and resources.
    if (is_scalar($value))
    {
      return call_user_func($escapingMethod, $value);
    }

    if (is_array($value))
    {
      return new sfOutputEscaperArrayDecorator($escapingMethod, $value);
    }

    if (is_object($value))
    {
      if ($value instanceof sfOutputEscaper)
      {
        // avoid double decoration
        $copy = clone $value;

        $copy->escapingMethod = $escapingMethod;

        return $copy;
      }
      else if (self::isClassMarkedAsSafe(get_class($value)))
      {
        // the class or one of its children is marked as safe
        // return the unescaped object
        return $value;
      }
      else if ($value instanceof sfOutputEscaperSafe)
      {
        // do not escape objects marked as safe
        // return the original object
        return $value->getValue();
      }
      else if ($value instanceof Traversable)
      {
        return new sfOutputEscaperIteratorDecorator($escapingMethod, $value);
      }
      else
      {
        return new sfOutputEscaperObjectDecorator($escapingMethod, $value);
      }
    }

    // it must be a resource; cannot escape that.
    throw new InvalidArgumentException(sprintf('Unable to escape value "%s".', var_export($value, true)));
  }

  /**
   * Unescapes a value that has been escaped previously with the escape() method.
   *
   * @param  mixed $value The value to unescape
   *
   * @return mixed Unescaped value
   *
   * @throws InvalidArgumentException If the escaping fails
   */
  static public function unescape($value)
  {
    if (null === $value || is_bool($value))
    {
      return $value;
    }

    if (is_scalar($value))
    {
      return html_entity_decode($value, ENT_QUOTES, sfConfig::get('sf_charset'));
    }
    elseif (is_array($value))
    {
      foreach ($value as $name => $v)
      {
        $value[$name] = self::unescape($v);
      }

      return $value;
    }
    elseif (is_object($value))
    {
      return $value instanceof sfOutputEscaper ? $value->getRawValue() : $value;
    }

    return $value;
  }

  /**
   * Returns true if the class if marked as safe.
   *
   * @param  string  $class  A class name
   *
   * @return bool true if the class if safe, false otherwise
   */
  static public function isClassMarkedAsSafe($class)
  {
    if (in_array($class, self::$safeClasses))
    {
      return true;
    }

    foreach (self::$safeClasses as $safeClass)
    {
      if (is_subclass_of($class, $safeClass))
      {
        return true;
      }
    }

    return false;
  }

  /**
   * Marks an array of classes (and all its children) as being safe for output.
   *
   * @param array $classes  An array of class names
   */
  static public function markClassesAsSafe(array $classes)
  {
    self::$safeClasses = array_unique(array_merge(self::$safeClasses, $classes));
  }

  /**
   * Marks a class (and all its children) as being safe for output.
   *
   * @param string $class  A class name
   */
  static public function markClassAsSafe($class)
  {
    self::markClassesAsSafe(array($class));
  }

  /**
   * Returns the raw value associated with this instance.
   *
   * Concrete instances of sfOutputEscaper classes decorate a value which is
   * stored by the constructor. This returns that original, unescaped, value.
   *
   * @return mixed The original value used to construct the decorator
   */
  public function getRawValue()
  {
    return $this->value;
  }

  /**
   * Gets a value from the escaper.
   *
   * @param  string $var  Value to get
   *
   * @return mixed Value
   */
  public function __get($var)
  {
    return $this->escape($this->escapingMethod, $this->value->$var);
  }
}