1: <?php namespace Laravel\Routing;
2:
3: use Closure;
4: use Laravel\Bundle;
5: use Laravel\Request;
6:
7: class Filter {
8:
9: /**
10: * The route filters for the application.
11: *
12: * @var array
13: */
14: public static $filters = array();
15:
16: /**
17: * The route filters that are based on a pattern.
18: *
19: * @var array
20: */
21: public static $patterns = array();
22:
23: /**
24: * All of the registered filter aliases.
25: *
26: * @var array
27: */
28: public static $aliases = array();
29:
30: /**
31: * Register a filter for the application.
32: *
33: * <code>
34: * // Register a closure as a filter
35: * Filter::register('before', function() {});
36: *
37: * // Register a class callback as a filter
38: * Filter::register('before', array('Class', 'method'));
39: * </code>
40: *
41: * @param string $name
42: * @param mixed $callback
43: * @return void
44: */
45: public static function register($name, $callback)
46: {
47: if (isset(static::$aliases[$name])) $name = static::$aliases[$name];
48:
49: // If the filter starts with "pattern: ", the filter is being setup to match on
50: // all requests that match a given pattern. This is nice for defining filters
51: // that handle all URIs beginning with "admin" for example.
52: if (starts_with($name, 'pattern: '))
53: {
54: foreach (explode(', ', substr($name, 9)) as $pattern)
55: {
56: static::$patterns[$pattern] = $callback;
57: }
58: }
59: else
60: {
61: static::$filters[$name] = $callback;
62: }
63: }
64:
65: /**
66: * Alias a filter so it can be used by another name.
67: *
68: * This is convenient for shortening filters that are registered by bundles.
69: *
70: * @param string $filter
71: * @param string $alias
72: * @return void
73: */
74: public static function alias($filter, $alias)
75: {
76: static::$aliases[$alias] = $filter;
77: }
78:
79: /**
80: * Parse a filter definition into an array of filters.
81: *
82: * @param string|array $filters
83: * @return array
84: */
85: public static function parse($filters)
86: {
87: return (is_string($filters)) ? explode('|', $filters) : (array) $filters;
88: }
89:
90: /**
91: * Call a filter or set of filters.
92: *
93: * @param array $collections
94: * @param array $pass
95: * @param bool $override
96: * @return mixed
97: */
98: public static function run($collections, $pass = array(), $override = false)
99: {
100: foreach ($collections as $collection)
101: {
102: foreach ($collection->filters as $filter)
103: {
104: list($filter, $parameters) = $collection->get($filter);
105:
106: // We will also go ahead and start the bundle for the developer. This allows
107: // the developer to specify bundle filters on routes without starting the
108: // bundle manually, and performance is improved by lazy-loading.
109: Bundle::start(Bundle::name($filter));
110:
111: if ( ! isset(static::$filters[$filter])) continue;
112:
113: $callback = static::$filters[$filter];
114:
115: // Parameters may be passed into filters by specifying the list of parameters
116: // as an array, or by registering a Closure which will return the array of
117: // parameters. If parameters are present, we will merge them with the
118: // parameters that were given to the method.
119: $response = call_user_func_array($callback, array_merge($pass, $parameters));
120:
121: // "Before" filters may override the request cycle. For example, an auth
122: // filter may redirect a user to a login view if they are not logged in.
123: // Because of this, we will return the first filter response if
124: // overriding is enabled for the filter collections
125: if ( ! is_null($response) and $override)
126: {
127: return $response;
128: }
129: }
130: }
131: }
132:
133: }
134:
135: class Filter_Collection {
136:
137: /**
138: * The filters contained by the collection.
139: *
140: * @var string|array
141: */
142: public $filters = array();
143:
144: /**
145: * The parameters specified for the filter.
146: *
147: * @var mixed
148: */
149: public $parameters;
150:
151: /**
152: * The included controller methods.
153: *
154: * @var array
155: */
156: public $only = array();
157:
158: /**
159: * The excluded controller methods.
160: *
161: * @var array
162: */
163: public $except = array();
164:
165: /**
166: * The HTTP methods for which the filter applies.
167: *
168: * @var array
169: */
170: public $methods = array();
171:
172: /**
173: * Create a new filter collection instance.
174: *
175: * @param string|array $filters
176: * @param mixed $parameters
177: * @return void
178: */
179: public function __construct($filters, $parameters = null)
180: {
181: $this->parameters = $parameters;
182: $this->filters = Filter::parse($filters);
183: }
184:
185: /**
186: * Parse the filter string, returning the filter name and parameters.
187: *
188: * @param string $filter
189: * @return array
190: */
191: public function get($filter)
192: {
193: // If the parameters were specified by passing an array into the collection,
194: // then we will simply return those parameters. Combining passed parameters
195: // with parameters specified directly in the filter attachment is not
196: // currently supported by the framework.
197: if ( ! is_null($this->parameters))
198: {
199: return array($filter, $this->parameters());
200: }
201:
202: // If no parameters were specified when the collection was created, we will
203: // check the filter string itself to see if the parameters were injected
204: // into the string as raw values, such as "role:admin".
205: if (($colon = strpos(Bundle::element($filter), ':')) !== false)
206: {
207: $parameters = explode(',', substr(Bundle::element($filter), $colon + 1));
208:
209: // If the filter belongs to a bundle, we need to re-calculate the position
210: // of the parameter colon, since we originally calculated it without the
211: // bundle identifier because the identifier uses colons as well.
212: if (($bundle = Bundle::name($filter)) !== DEFAULT_BUNDLE)
213: {
214: $colon = strlen($bundle.'::') + $colon;
215: }
216:
217: return array(substr($filter, 0, $colon), $parameters);
218: }
219:
220: // If no parameters were specified when the collection was created or
221: // in the filter string, we will just return the filter name as is
222: // and give back an empty array of parameters.
223: return array($filter, array());
224: }
225:
226: /**
227: * Evaluate the collection's parameters and return a parameters array.
228: *
229: * @return array
230: */
231: protected function parameters()
232: {
233: if ($this->parameters instanceof Closure)
234: {
235: $this->parameters = call_user_func($this->parameters);
236: }
237:
238: return $this->parameters;
239: }
240:
241: /**
242: * Determine if this collection's filters apply to a given method.
243: *
244: * @param string $method
245: * @return bool
246: */
247: public function applies($method)
248: {
249: if (count($this->only) > 0 and ! in_array($method, $this->only))
250: {
251: return false;
252: }
253:
254: if (count($this->except) > 0 and in_array($method, $this->except))
255: {
256: return false;
257: }
258:
259: $request = strtolower(Request::method());
260:
261: if (count($this->methods) > 0 and ! in_array($request, $this->methods))
262: {
263: return false;
264: }
265:
266: return true;
267: }
268:
269: /**
270: * Set the excluded controller methods.
271: *
272: * <code>
273: * // Specify a filter for all methods except "index"
274: * $this->filter('before', 'auth')->except('index');
275: *
276: * // Specify a filter for all methods except "index" and "home"
277: * $this->filter('before', 'auth')->except(array('index', 'home'));
278: * </code>
279: *
280: * @param array $methods
281: * @return Filter_Collection
282: */
283: public function except($methods)
284: {
285: $this->except = (array) $methods;
286: return $this;
287: }
288:
289: /**
290: * Set the included controller methods.
291: *
292: * <code>
293: * // Specify a filter for only the "index" method
294: * $this->filter('before', 'auth')->only('index');
295: *
296: * // Specify a filter for only the "index" and "home" methods
297: * $this->filter('before', 'auth')->only(array('index', 'home'));
298: * </code>
299: *
300: * @param array $methods
301: * @return Filter_Collection
302: */
303: public function only($methods)
304: {
305: $this->only = (array) $methods;
306: return $this;
307: }
308:
309: /**
310: * Set the HTTP methods for which the filter applies.
311: *
312: * <code>
313: * // Specify that a filter only applies on POST requests
314: * $this->filter('before', 'csrf')->on('post');
315: *
316: * // Specify that a filter applies for multiple HTTP request methods
317: * $this->filter('before', 'csrf')->on(array('post', 'put'));
318: * </code>
319: *
320: * @param array $methods
321: * @return Filter_Collection
322: */
323: public function on($methods)
324: {
325: $this->methods = array_map('strtolower', (array) $methods);
326: return $this;
327: }
328:
329: }