├── .gitignore
├── .htaccess
├── LICENSE
├── README.md
├── composer.json
├── docker
├── image-php-7.2
│ └── Dockerfile
└── image-php-7.4.1
│ └── Dockerfile
├── include-example.php
├── index.php
├── src
└── Steampixel
│ └── Route.php
└── web.config
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS files
2 | .DS_Store
3 | ._*
4 |
5 | # Compser files
6 | /vendor/
7 |
8 | # local test environment
9 | /test/
10 | docker-build.sh
11 | docker-bash.sh
12 | docker-run.sh
13 | docker-rm.sh
14 | release.sh
15 |
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | DirectoryIndex index.php
2 |
3 | # enable apache rewrite engine
4 | RewriteEngine on
5 |
6 | # set your rewrite base
7 | # Edit this in your init method too if you script lives in a subfolder
8 | RewriteBase /
9 |
10 | # Deliver the folder or file directly if it exists on the server
11 | RewriteCond %{REQUEST_FILENAME} !-f
12 | RewriteCond %{REQUEST_FILENAME} !-d
13 |
14 | # Push every request to index.php
15 | RewriteRule ^(.*)$ index.php [QSA]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 - 2020 SteamPixel and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple PHP Router ⇄
2 |
3 | Hey! This is a simple and small single class PHP router that can handle the whole URL routing for your project.
4 | It utilizes RegExp and PHP's anonymous functions to create a lightweight and fast routing system.
5 | The router supports dynamic path parameters, special 404 and 405 routes as well as verification of request methods like GET, POST, PUT, DELETE, etc.
6 | The codebase is very small and very easy to understand. So you can use it as a boilerplate for a more complex router.
7 |
8 | Take a look at the index.php file. As you can see the `Route::add()` method is used to add new routes to your project.
9 | The first argument takes the path segment. You can also use RegExp in there to parse out variables.
10 | All matching variables will be pushed to the handler method defined in the second argument.
11 | The third argument will match the request method. The default method is 'get'.
12 |
13 | ## 📋 Simple example:
14 | ```php
15 | // Require the class
16 | include 'src\Steampixel\Route.php';
17 |
18 | // Use this namespace
19 | use Steampixel\Route;
20 |
21 | // Add the first route
22 | Route::add('/user/([0-9]*)/edit', function($id) {
23 | echo 'Edit user with id '.$id.'
';
24 | }, 'get');
25 |
26 | // Run the router
27 | Route::run('/');
28 | ```
29 |
30 | You will find a more complex example with a build in navigation in the index.php file.
31 |
32 | ## 🎶 Installation using Composer
33 | Just run `composer require steampixel/simple-php-router`
34 | Than add the autoloader to your project like this:
35 | ```php
36 | // Autoload files using composer
37 | require_once __DIR__ . '/vendor/autoload.php';
38 |
39 | // Use this namespace
40 | use Steampixel\Route;
41 |
42 | // Add your first route
43 | Route::add('/', function() {
44 | echo 'Welcome :-)';
45 | });
46 |
47 | // Run the router
48 | Route::run('/');
49 | ```
50 |
51 | ## ⛺ Use a different basepath
52 | If your script lives in a subfolder (e.g. /api/v1) set this basepath in your run method:
53 |
54 | ```php
55 | Route::run('/api/v1');
56 | ```
57 |
58 | Do not forget to edit the basepath in .htaccess too if you are on Apache2.
59 |
60 | ## ⏎ Use return instead of echo
61 | You don't have to use `echo` to output your content. You can also use the `return` statement. Everything that gets returned is echoed automatically.
62 |
63 | ```php
64 | // Add your first route
65 | Route::add('/', function() {
66 | return 'Welcome :-)';
67 | });
68 | ```
69 |
70 | ## ⇒ Use arrow functions
71 | Since PHP 7.4 you can also use arrow functions to output your content. So you can easily use variables from outside and you can write shorter code.
72 | Please be aware that an Arrow function must always return a value. Therefore you cannot use `echo` directly in here.
73 | You can find an example in index.php. However, this is deactivated, as it only works from PHP 7.4.
74 |
75 | ```php
76 | Route::add('/arrow/([a-z-0-9-]*)', fn($foo) => 'This is a working arrow function example. Parameter: '.$foo );
77 | ```
78 |
79 | ## 📖 Return all known routes
80 | This is useful, for example, to automatically generate test routes or help pages.
81 |
82 | ```php
83 | $routes = Route::getAll();
84 | foreach($routes as $route) {
85 | echo $route['expression'].' ('.$route['method'].')';
86 | }
87 | ```
88 |
89 | On top of that you could use a library like https://github.com/hoaproject/Regex to generate working example links for the different expressions.
90 |
91 | ## 🧰 Enable case sensitive routes, trailing slashes and multi match mode
92 | The second, third and fourth parameters of `Route::run('/', false, false, false);` are set to false by default.
93 | Using this parameters you can switch on and off several options:
94 | * Second parameter: You can enable case sensitive mode by setting the second parameter to true.
95 | * Third parameter: By default the router will ignore trailing slashes. Set this parameter to true to avoid this.
96 | * Fourth parameter: By default the router will only execute the first matching route. Set this parameter to true to enable multi match mode.
97 |
98 | ## ⁉ Something does not work?
99 | * Don't forget to set the correct basepath as the first argument in your `run()` method and in your .htaccess file.
100 | * Enable mod_rewrite in your Apache2 settings, in case you're using Apache2: `a2enmod apache2`
101 | * Does Apache2 even load the .htaccess file? Check whether the `AllowOverride All` option is set in the Apache2 configuration like in this example:
102 | ```
103 |
104 | ServerName mysite.com
105 | DocumentRoot /var/www/html/mysite.com
106 |
107 | AllowOverride All
108 |
109 |
110 | ```
111 |
112 | ## 🚀 Pages, Templates, Themes, Components
113 | This is a simple router. So there is no templating at all. But it works perfectly together with [simplePHPComponents](https://github.com/steampixel/simplePHPComponents) and [simplePHPPortals](https://github.com/steampixel/simplePHPPortals). There is a complete boilerplate project including these dependencies and this router called [simplePHPPages](https://github.com/steampixel/simplePHPPages). You can use it for you next project.
114 |
115 | ## 🎓 What skills do you need?
116 | Please be aware that for this router you need a basic understanding of PHP. Many problems stem from people lacking basic programming knowledge. You should therefore have the following skills:
117 | * Basic PHP Knowledge
118 | * Basic understanding of RegExp in PHP: https://www.guru99.com/php-regular-expressions.html
119 | * Basic understanding of anonymous functions and how to push data inside it: https://www.php.net/manual/en/functions.anonymous.php
120 | * Basic understanding of including and requiring files and how to push data to them: https://stackoverflow.com/questions/4315271/how-to-pass-arguments-to-an-included-file/5503326
121 | * Windows Only - Setup IIS and PHP: https://docs.microsoft.com/en-us/iis/application-frameworks/scenario-build-a-php-website-on-iis/configuring-step-1-install-iis-and-php.
122 | * Windows Only - Creating Websites in IIS: https://docs.microsoft.com/en-us/iis/get-started/getting-started-with-iis/create-a-web-site.
123 |
124 | Please note that we are happy to help you if you have problems with this router. Unfortunately, we don't have a lot of time, so we can't help you learn PHP basics.
125 |
126 | ## 🚢 Test setup with Docker
127 | I have created a little Docker test setup.
128 |
129 | 1. Build the image: `docker build -t simplephprouter docker/image-php-7.2`
130 |
131 | 2. Spin up a container
132 | * On Linux / Mac or Windows Powershell use: `docker run -d -p 80:80 -v $(pwd):/var/www/html --name simplephprouter simplephprouter`
133 | * On Windows CMD use `docker run -d -p 80:80 -v %cd%:/var/www/html --name simplephprouter simplephprouter`
134 |
135 | 3. Open your browser and navigate to http://localhost
136 |
137 | ## 🪟 Test Setup in Windows using IIS
138 | With IIS now fully supporting PHP, this example can be run using the included web.config. The web.config has a rewrite rule, similar to the .htaccess rewrite rule, but specifically for IIS. The rewrite rule will send all incoming requests to index.php in your root. The rest is done by the simple php router.
139 | ### Setup
140 | _This setup tutorial assumes you have the knowledge to create sites in IIS and set up bindings for http/https and custom DNS. If you need more information, this [article](https://docs.microsoft.com/en-us/iis/get-started/getting-started-with-iis/create-a-web-site) will help you with that part._
141 | 1. If you haven't done so yet, install php on windows. This article [Install IIS and PHP | Microsoft Docs ](https://docs.microsoft.com/en-us/iis/application-frameworks/scenario-build-a-php-website-on-iis/configuring-step-1-install-iis-and-php) will guide you to install the required php dependencies on your windows machine.
142 | 2. In IIS Manager, create a site and point the physical location to root of the simplePHPRouter folder. It is recommended you connect to the the physical location with an account that has "Read/Write" rights to that folder.
143 | 3. (Optional) Create a DNS entry in your hosts file pointing 127.0.0.1 to the domain name you have used to set up the site.
144 | 4. Browse to the newly created website and the sample site should display now.
145 |
146 | ## ✅ Todo
147 | * Create demo configuration for nginx
148 |
149 | ## 📃 License
150 | This project is licensed under the MIT License. See LICENSE for further information.
151 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "steampixel/simple-php-router",
3 | "description": "This is a simple and small PHP router that can handle the whole url routing for your project.",
4 | "type": "library",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Steampixel",
9 | "email": "info@steampixel.de",
10 | "homepage": "https://steampixel.de/"
11 | },
12 | {
13 | "name": "Maximilian Götz",
14 | "email": "contact@maxbits.net",
15 | "homepage": "https://www.maxbits.net/"
16 | }
17 | ],
18 | "minimum-stability": "dev",
19 | "require": {},
20 | "autoload": {
21 | "psr-0": {
22 | "Steampixel": "src/"
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/docker/image-php-7.2/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.2-apache
2 |
3 | # Enable rewrite
4 | RUN a2enmod rewrite
5 |
6 | # Install composer
7 | # https://www.hostinger.com/tutorials/how-to-install-composer
8 | RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
9 | RUN php composer-setup.php --install-dir=/usr/local/bin --filename=composer
10 | RUN php -r "unlink('composer-setup.php');"
11 |
12 | # Update
13 | RUN apt-get update -y
14 |
15 | # Install git
16 | RUN apt-get install git -y
17 |
18 | # Install zip
19 | RUN apt-get install zip -y
20 |
--------------------------------------------------------------------------------
/docker/image-php-7.4.1/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.4.1-apache
2 |
3 | RUN a2enmod rewrite
4 |
5 | # Install composer
6 | # https://www.hostinger.com/tutorials/how-to-install-composer
7 | RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
8 | RUN php composer-setup.php --install-dir=/usr/local/bin --filename=composer
9 | RUN php -r "unlink('composer-setup.php');"
10 |
11 | # Update
12 | RUN apt-get update -y
13 |
14 | # Install git
15 | RUN apt-get install git -y
16 |
17 | # Install zip
18 | RUN apt-get install zip -y
19 |
--------------------------------------------------------------------------------
/include-example.php:
--------------------------------------------------------------------------------
1 |
2 |
Blog
3 |
4 |
5 | Hey! Read my new blog post with this cool slug: "=$slug ?>"
6 |
7 |
8 |
9 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
10 |
11 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | 'Article 1', 'article-2' => 'Article 2', 'article-3' => 'Article 3', 'article-4' => 'Article 4', 'article-5' => 'Article 5'];
19 |
20 | // This function just renders a simple navigation
21 | function navi() {
22 | global $slugs;
23 | $navi = '
24 | Navigation:
25 | ';
55 | echo $navi;
56 | }
57 |
58 | // Add base route (startpage)
59 | Route::add('/', function() {
60 | navi();
61 | echo 'Welcome :-)';
62 | });
63 |
64 | // Another base route example
65 | Route::add('/index.php', function() {
66 | navi();
67 | echo 'You are not really on index.php ;-)';
68 | });
69 |
70 | // Simple test route that simulates static html file
71 | Route::add('/test.html', function() {
72 | navi();
73 | echo 'Hello from test.html';
74 | });
75 |
76 | // This example shows how to include files and how to push data to them
77 | // Hint: If you want to use this router for building websites with different nice looking HTML pages please visit https://github.com/steampixel/simplePHPPages
78 | // There you will find a complete website example that bases on this router including themes, pages, layouts and content blocks.
79 | Route::add('/blog/([a-z-0-9-]*)', function($slug) {
80 | navi();
81 | include('include-example.php');
82 | });
83 |
84 | // This route is for debugging only
85 | // It simply prints out some php infos
86 | // Do not use this route on production systems!
87 | Route::add('/phpinfo', function() {
88 | navi();
89 | phpinfo();
90 | });
91 |
92 | // Get route example
93 | Route::add('/contact-form', function() {
94 | navi();
95 | echo '';
96 | }, 'get');
97 |
98 | // Post route example
99 | Route::add('/contact-form', function() {
100 | navi();
101 | echo 'Hey! The form has been sent:
';
102 | print_r($_POST);
103 | }, 'post');
104 |
105 | // Get and Post route example
106 | Route::add('/get-post-sample', function() {
107 | navi();
108 | echo 'You can GET this page and also POST this form back to it';
109 | echo '';
110 | if (isset($_POST['input'])) {
111 | echo 'I also received a POST with this data:
';
112 | print_r($_POST);
113 | }
114 | }, ['get','post']);
115 |
116 | // Route with regexp parameter
117 | // Be aware that (.*) will match / (slash) too. For example: /user/foo/bar/edit
118 | // Also users could inject SQL statements or other untrusted data if you use (.*)
119 | // You should better use a saver expression like /user/([0-9]*)/edit or /user/([A-Za-z]*)/edit
120 | Route::add('/user/(.*)/edit', function($id) {
121 | navi();
122 | echo 'Edit user with id '.$id.'
';
123 | });
124 |
125 | // Accept only numbers as parameter. Other characters will result in a 404 error
126 | Route::add('/foo/([0-9]*)/bar', function($var1) {
127 | navi();
128 | echo $var1.' is a great number!';
129 | });
130 |
131 | // Crazy route with parameters
132 | Route::add('/(.*)/(.*)/(.*)/(.*)', function($var1,$var2,$var3,$var4) {
133 | navi();
134 | echo 'This is the first match: '.$var1.' / '.$var2.' / '.$var3.' / '.$var4.'
';
135 | });
136 |
137 | // Long route example
138 | // By default this route gets never triggered because the route before matches too
139 | Route::add('/foo/bar/foo/bar', function() {
140 | echo 'This is the second match (This route should only work in multi match mode)
';
141 | });
142 |
143 | // Route with non english letters: german example
144 | Route::add('/äöü', function() {
145 | navi();
146 | echo 'German example. Non english letters should work too
';
147 | });
148 |
149 | // Route with non english letters: arabic example
150 | Route::add('/الرقص-العربي', function() {
151 | navi();
152 | echo 'Arabic example. Non english letters should work too
';
153 | });
154 |
155 | // Auto generate dynamic routes from a database or from another source
156 | // For this example we will just use a predefined array
157 | foreach($slugs as $slug => $entry) {
158 | Route::add('/my-blog-articles/'.$slug, function() use($entry) {
159 | navi();
160 | echo 'You are here: '.$entry;
161 | });
162 | }
163 |
164 | // Use variables from global scope
165 | // You can use for example use() to inject variables to local scope
166 | // You can use global to register the variable in local scope
167 | $foo = 'foo';
168 | $bar = 'bar';
169 | Route::add('/global/([a-z-0-9-]*)', function($param) use($foo) {
170 | global $bar;
171 | navi();
172 | echo 'The param is '.$param.'
';
173 | echo 'Foo is '.$foo.'
';
174 | echo 'Bar is '.$bar.'
';
175 | });
176 |
177 | // Return example
178 | // Returned data gets printed
179 | Route::add('/return', function() {
180 | navi();
181 | return 'This text gets returned by the add method';
182 | });
183 |
184 | // Arrow function example
185 | // Note: You can use this example only if you are on PHP 7.4 or higher
186 | // $bar = 'bar';
187 | // Route::add('/arrow/([a-z-0-9-]*)', fn($foo) => navi().'This is a working arrow function example.
Parameter: '.$foo. '
Variable from global scope: '.$bar );
188 |
189 | // Trailing slash example
190 | Route::add('/aTrailingSlashDoesNotMatter', function() {
191 | navi();
192 | echo 'a trailing slash does not matter
';
193 | });
194 |
195 | // Case example
196 | Route::add('/theCaseDoesNotMatter',function() {
197 | navi();
198 | echo 'the case does not matter
';
199 | });
200 |
201 | // 405 test
202 | Route::add('/this-route-is-defined', function() {
203 | navi();
204 | echo 'You need to patch this route to see this content';
205 | }, 'patch');
206 |
207 | // Add a 404 not found route
208 | Route::pathNotFound(function($path) {
209 | // Do not forget to send a status header back to the client
210 | // The router will not send any headers by default
211 | // So you will have the full flexibility to handle this case
212 | header('HTTP/1.0 404 Not Found');
213 | navi();
214 | echo 'Error 404 :-(
';
215 | echo 'The requested path "'.$path.'" was not found!';
216 | });
217 |
218 | // Add a 405 method not allowed route
219 | Route::methodNotAllowed(function($path, $method) {
220 | // Do not forget to send a status header back to the client
221 | // The router will not send any headers by default
222 | // So you will have the full flexibility to handle this case
223 | header('HTTP/1.0 405 Method Not Allowed');
224 | navi();
225 | echo 'Error 405 :-(
';
226 | echo 'The requested path "'.$path.'" exists. But the request method "'.$method.'" is not allowed on this path!';
227 | });
228 |
229 | // Return all known routes
230 | Route::add('/known-routes', function() {
231 | navi();
232 | $routes = Route::getAll();
233 | echo '';
234 | foreach($routes as $route) {
235 | echo '- '.$route['expression'].' ('.$route['method'].')
';
236 | }
237 | echo '
';
238 | });
239 |
240 | // Run the Router with the given Basepath
241 | Route::run(BASEPATH);
242 |
243 | // Enable case sensitive mode, trailing slashes and multi match mode by setting the params to true
244 | // Route::run(BASEPATH, true, true, true);
245 |
--------------------------------------------------------------------------------
/src/Steampixel/Route.php:
--------------------------------------------------------------------------------
1 | $expression,
21 | 'function' => $function,
22 | 'method' => $method
23 | ));
24 | }
25 |
26 | public static function getAll(){
27 | return self::$routes;
28 | }
29 |
30 | public static function pathNotFound($function) {
31 | self::$pathNotFound = $function;
32 | }
33 |
34 | public static function methodNotAllowed($function) {
35 | self::$methodNotAllowed = $function;
36 | }
37 |
38 | public static function run($basepath = '', $case_matters = false, $trailing_slash_matters = false, $multimatch = false) {
39 |
40 | // The basepath never needs a trailing slash
41 | // Because the trailing slash will be added using the route expressions
42 | $basepath = rtrim($basepath, '/');
43 |
44 | // Parse current URL
45 | $parsed_url = parse_url($_SERVER['REQUEST_URI']);
46 |
47 | $path = '/';
48 |
49 | // If there is a path available
50 | if (isset($parsed_url['path'])) {
51 | // If the trailing slash matters
52 | if ($trailing_slash_matters) {
53 | $path = $parsed_url['path'];
54 | } else {
55 | // If the path is not equal to the base path (including a trailing slash)
56 | if($basepath.'/'!=$parsed_url['path']) {
57 | // Cut the trailing slash away because it does not matters
58 | $path = rtrim($parsed_url['path'], '/');
59 | } else {
60 | $path = $parsed_url['path'];
61 | }
62 | }
63 | }
64 |
65 | $path = urldecode($path);
66 |
67 | // Get current request method
68 | $method = $_SERVER['REQUEST_METHOD'];
69 |
70 | $path_match_found = false;
71 |
72 | $route_match_found = false;
73 |
74 | foreach (self::$routes as $route) {
75 |
76 | // If the method matches check the path
77 |
78 | // Add basepath to matching string
79 | if ($basepath != '' && $basepath != '/') {
80 | $route['expression'] = '('.$basepath.')'.$route['expression'];
81 | }
82 |
83 | // Add 'find string start' automatically
84 | $route['expression'] = '^'.$route['expression'];
85 |
86 | // Add 'find string end' automatically
87 | $route['expression'] = $route['expression'].'$';
88 |
89 | // Check path match
90 | if (preg_match('#'.$route['expression'].'#'.($case_matters ? '' : 'i').'u', $path, $matches)) {
91 | $path_match_found = true;
92 |
93 | // Cast allowed method to array if it's not one already, then run through all methods
94 | foreach ((array)$route['method'] as $allowedMethod) {
95 | // Check method match
96 | if (strtolower($method) == strtolower($allowedMethod)) {
97 | array_shift($matches); // Always remove first element. This contains the whole string
98 |
99 | if ($basepath != '' && $basepath != '/') {
100 | array_shift($matches); // Remove basepath
101 | }
102 |
103 | if($return_value = call_user_func_array($route['function'], $matches)) {
104 | echo $return_value;
105 | }
106 |
107 | $route_match_found = true;
108 |
109 | // Do not check other routes
110 | break;
111 | }
112 | }
113 | }
114 |
115 | // Break the loop if the first found route is a match
116 | if($route_match_found&&!$multimatch) {
117 | break;
118 | }
119 |
120 | }
121 |
122 | // No matching route was found
123 | if (!$route_match_found) {
124 | // But a matching path exists
125 | if ($path_match_found) {
126 | if (self::$methodNotAllowed) {
127 | if($return_value = call_user_func_array(self::$methodNotAllowed, Array($path,$method))){
128 | echo $return_value;
129 | }
130 | }
131 | } else {
132 | if (self::$pathNotFound) {
133 | if($return_value = call_user_func_array(self::$pathNotFound, Array($path))){
134 | echo $return_value;
135 | }
136 | }
137 | }
138 |
139 | }
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------