diff --git a/src/mibew/libs/classes/Mibew/Controller/Settings/CommonController.php b/src/mibew/libs/classes/Mibew/Controller/Settings/CommonController.php
new file mode 100644
index 00000000..8417eb53
--- /dev/null
+++ b/src/mibew/libs/classes/Mibew/Controller/Settings/CommonController.php
@@ -0,0 +1,218 @@
+<?php
+/*
+ * Copyright 2005-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Mibew\Controller\Settings;
+
+use Mibew\Controller\AbstractController;
+use Mibew\Http\Exception\BadRequestException;
+use Mibew\Settings;
+use Mibew\Style\ChatStyle;
+use Mibew\Style\InvitationStyle;
+use Mibew\Style\PageStyle;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Contains actions which are related with common system settings.
+ */
+class CommonController extends AbstractController
+{
+    /**
+     * Builds a page with form for common system settings.
+     *
+     * @param Request $request Incoming request.
+     * @return string Rendered page content.
+     */
+    public function showFormAction(Request $request)
+    {
+        set_csrf_token();
+
+        $operator = $request->attributes->get('_operator');
+
+        $page = array(
+            'agentId' => '',
+            // Use errors list stored in the request. We need to do so to have
+            // an ability to pass the request from the "submitForm" action.
+            'errors' => $request->attributes->get('errors', array()),
+        );
+
+        // Load settings values from the database
+        $options = array(
+            'email',
+            'title',
+            'logo',
+            'hosturl',
+            'usernamepattern',
+            'chattitle',
+            'geolink',
+            'geolinkparams',
+            'sendmessagekey',
+            'cron_key',
+            'left_messages_locale',
+        );
+
+        $params = array();
+        foreach ($options as $opt) {
+            $params[$opt] = Settings::get($opt);
+        }
+
+        // Set form values
+        $form = $request->request;
+
+        $page['formemail'] = $form->get('email', $params['email']);
+        $page['formleftmessageslocale'] = $form->get('leftmessageslocale', $params['left_messages_locale']);
+        $page['formtitle'] = $form->get('title', $params['title']);
+        $page['formlogo'] = $form->get('logo', $params['logo']);
+        $page['formhosturl'] = $form->get('hosturl', $params['hosturl']);
+        $page['formgeolink'] = $form->get('geolink', $params['geolink']);
+        $page['formgeolinkparams'] = $form->get('geolinkparams', $params['geolinkparams']);
+        $page['formusernamepattern'] = $form->get('usernamepattern', $params['usernamepattern']);
+        $page['formchatstyle'] = $form->get('chatstyle', ChatStyle::getDefaultStyle());
+        $page['formpagestyle'] = $form->get('pagestyle', PageStyle::getDefaultStyle());
+        $page['formchattitle'] = $form->get('chattitle', $params['chattitle']);
+        $page['formsendmessagekey'] = $form->get('sendmessagekey', $params['sendmessagekey']);
+        $page['formcronkey'] = $form->get('cronkey', $params['cron_key']);
+
+        if (Settings::get('enabletracking')) {
+            $page['forminvitationstyle'] = $form->get('invitationstyle', InvitationStyle::getDefaultStyle());
+            $page['availableInvitationStyles'] = InvitationStyle::getAvailableStyles();
+        }
+
+        $page['availableLocales'] = get_available_locales();
+        $page['availableChatStyles'] = ChatStyle::getAvailableStyles();
+        $page['availablePageStyles'] = PageStyle::getAvailableStyles();
+        $page['stored'] = $request->query->has('stored');
+        $page['enabletracking'] = Settings::get('enabletracking');
+        $page['cron_path'] = cron_get_uri($params['cron_key']);
+        $page['title'] = getlocal('settings.title');
+        $page['menuid'] = 'settings';
+
+        $page = array_merge($page, prepare_menu($operator));
+        $page['tabs'] = setup_settings_tabs(0);
+
+        return $this->render('settings_common', $page);
+    }
+
+    /**
+     * Processes submitting of the form which is generated in
+     * {@link \Mibew\Controller\Settings\CommonController::showFormAction()}
+     * method.
+     *
+     * @param Request $request Incoming request.
+     * @return string Rendered page content.
+     * @throws BadRequestException If one or more parameters of the request have
+     *   wrong format.
+     */
+    public function submitFormAction(Request $request)
+    {
+        csrf_check_token($request);
+
+        $errors = array();
+        $params = array();
+
+        $params['email'] = $request->request->get('email');
+        $params['title'] = $request->request->get('title');
+        $params['logo'] = $request->request->get('logo');
+        $params['hosturl'] = $request->request->get('hosturl');
+        $params['usernamepattern'] = $request->request->get('usernamepattern');
+        $params['chattitle'] = $request->request->get('chattitle');
+        $params['geolink'] = $request->request->get('geolink');
+        $params['geolinkparams'] = $request->request->get('geolinkparams');
+        $params['cron_key'] = $request->request->get('cronkey');
+
+        $send_key = $request->request->get('sendmessagekey');
+        if (!preg_match("/^c?enter$/", $send_key)) {
+            throw new BadRequestException('Wrong format of "sendmessagekey" field.');
+        }
+        $params['sendmessagekey'] = $send_key;
+
+        $params['left_messages_locale'] = $request->request->get('leftmessageslocale');
+        if (!in_array($params['left_messages_locale'], get_available_locales())) {
+            $params['left_messages_locale'] = HOME_LOCALE;
+        }
+
+        if ($params['email'] && !is_valid_email($params['email'])) {
+            $errors[] = getlocal('settings.wrong.email');
+        }
+
+        if ($params['geolinkparams']) {
+            foreach (explode(',', $params['geolinkparams']) as $one_param) {
+                $wrong_param = !preg_match(
+                    "/^\s*(toolbar|scrollbars|location|status|menubar|width|height|resizable)=\d{1,4}$/",
+                    $one_param
+                );
+                if ($wrong_param) {
+                    $errors[] = "Wrong link parameter: \"$one_param\", "
+                        . "should be one of 'toolbar, scrollbars, location, "
+                        . "status, menubar, width, height or resizable'";
+                }
+            }
+        }
+
+        if (preg_match("/^[0-9A-Za-z]*$/", $params['cron_key']) == 0) {
+            $errors[] = getlocal('settings.wrong.cronkey');
+        }
+
+        // Load styles configs
+        $chat_style = $request->request->get('chat_style', ChatStyle::getDefaultStyle());
+        $chat_style_list = ChatStyle::getAvailableStyles();
+        if (!in_array($chat_style, $chat_style_list)) {
+            $chat_style = $chat_style_list[0];
+        }
+
+        $page_style = $request->request->get('page_style', PageStyle::getDefaultStyle());
+        $page_style_list = PageStyle::getAvailableStyles();
+        if (!in_array($page_style, $page_style_list)) {
+            $page_style = $page_style_list[0];
+        }
+
+        if (Settings::get('enabletracking')) {
+            $invitation_style = $request->request->get(
+                'invitation_style',
+                InvitationStyle::getDefaultStyle()
+            );
+            $invitation_style_list = InvitationStyle::getAvailableStyles();
+            if (!in_array($invitation_style, $invitation_style_list)) {
+                $invitation_style = $invitation_style_list[0];
+            }
+        }
+
+        if (count($errors) != 0) {
+            $request->attributes->set('errors', $errors);
+
+            // The form should be rebuild. Invoke appropriate action.
+            return $this->showFormAction($request);
+        }
+
+        // Update system settings
+        foreach ($params as $key => $value) {
+            Settings::set($key, $value);
+        }
+        Settings::update();
+
+        // Update styles params
+        ChatStyle::setDefaultStyle($chat_style);
+        PageStyle::setDefaultStyle($page_style);
+        if (Settings::get('enabletracking')) {
+            InvitationStyle::setDefaultStyle($invitation_style);
+        }
+
+        // Redirect the user to the same page using GET method
+        $redirect_to = $this->generateUrl('settings_common', array('stored' => true));
+
+        return $this->redirect($redirect_to);
+    }
+}
diff --git a/src/mibew/libs/common/constants.php b/src/mibew/libs/common/constants.php
index 18bf768b..e645486f 100644
--- a/src/mibew/libs/common/constants.php
+++ b/src/mibew/libs/common/constants.php
@@ -38,7 +38,7 @@ define('SESSION_PREFIX', md5($mysqlhost . '##' . $mysqldb . '##' . $mysqlprefix)
 
 /**
  * Default value for cron security key.
- * Another value can be set at operator/settings.php page.
+ * Another value can be set at operator/settings page.
  */
 define('DEFAULT_CRON_KEY', md5(
     $mysqlhost . '##' . $mysqldb . '##' . $mysqllogin . '##'
diff --git a/src/mibew/libs/routing.yml b/src/mibew/libs/routing.yml
index 86d24b23..ecec8c19 100644
--- a/src/mibew/libs/routing.yml
+++ b/src/mibew/libs/routing.yml
@@ -360,6 +360,23 @@ password_recovery_reset:
     defaults:
         _controller: Mibew\Controller\PasswordRecoveryController::resetAction
 
+## Settings
+settings_common:
+    path: /operator/settings
+    defaults:
+        _controller: Mibew\Controller\Settings\CommonController::showFormAction
+        _access_check: Mibew\AccessControl\Check\PermissionsCheck
+        _access_permissions: [CAN_ADMINISTRATE]
+    methods: [GET]
+
+settings_common_save:
+    path: /operator/settings
+    defaults:
+        _controller: Mibew\Controller\Settings\CommonController::submitFormAction
+        _access_check: Mibew\AccessControl\Check\PermissionsCheck
+        _access_permissions: [CAN_ADMINISTRATE]
+    methods: [POST]
+
 ## Statistics
 statistics:
     path: /operator/statistics/{type}
diff --git a/src/mibew/libs/settings.php b/src/mibew/libs/settings.php
index 4e873186..4cfcfdd0 100644
--- a/src/mibew/libs/settings.php
+++ b/src/mibew/libs/settings.php
@@ -29,7 +29,7 @@ function setup_settings_tabs($active)
 {
     $tabs = array(
         getlocal("page_settings.tab.main") => ($active != 0
-            ? (MIBEW_WEB_ROOT . "/operator/settings.php")
+            ? (MIBEW_WEB_ROOT . "/operator/settings")
             : ""),
         getlocal("page_settings.tab.features") => ($active != 1
             ? (MIBEW_WEB_ROOT . "/operator/features.php")
diff --git a/src/mibew/operator/settings.php b/src/mibew/operator/settings.php
deleted file mode 100644
index dc2124ec..00000000
--- a/src/mibew/operator/settings.php
+++ /dev/null
@@ -1,186 +0,0 @@
-<?php
-/*
- * Copyright 2005-2014 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// Import namespaces and classes of the core
-use Mibew\Settings;
-use Mibew\Style\ChatStyle;
-use Mibew\Style\InvitationStyle;
-use Mibew\Style\PageStyle;
-
-// Initialize libraries
-require_once(dirname(dirname(__FILE__)) . '/libs/init.php');
-
-$operator = check_login();
-force_password($operator);
-csrf_check_token();
-
-$page = array(
-    'agentId' => '',
-    'errors' => array(),
-);
-
-// Load system configs
-$options = array(
-    'email',
-    'title',
-    'logo',
-    'hosturl',
-    'usernamepattern',
-    'chattitle',
-    'geolink',
-    'geolinkparams',
-    'sendmessagekey',
-    'cron_key',
-    'left_messages_locale',
-);
-
-$params = array();
-foreach ($options as $opt) {
-    $params[$opt] = Settings::get($opt);
-}
-
-// Load styles configs
-$styles_params = array(
-    'chat_style' => ChatStyle::getDefaultStyle(),
-    'page_style' => PageStyle::getDefaultStyle(),
-);
-
-$chat_style_list = ChatStyle::getAvailableStyles();
-$page_style_list = PageStyle::getAvailableStyles();
-
-if (Settings::get('enabletracking')) {
-    $styles_params['invitation_style'] = InvitationStyle::getDefaultStyle();
-    $invitation_style_list = InvitationStyle::getAvailableStyles();
-}
-
-$locales_list = get_available_locales();
-
-if (isset($_POST['email']) && isset($_POST['title']) && isset($_POST['logo'])) {
-    $params['email'] = get_param('email');
-    $params['title'] = get_param('title');
-    $params['logo'] = get_param('logo');
-    $params['hosturl'] = get_param('hosturl');
-    $params['usernamepattern'] = get_param('usernamepattern');
-    $params['chattitle'] = get_param('chattitle');
-    $params['geolink'] = get_param('geolink');
-    $params['geolinkparams'] = get_param('geolinkparams');
-    $params['sendmessagekey'] = verify_param('sendmessagekey', "/^c?enter$/");
-    $params['cron_key'] = get_param('cronkey');
-
-    $params['left_messages_locale'] = verify_param("leftmessageslocale", "/^[\w-]{2,5}$/", $params['left_messages_locale']);
-    if (!in_array($params['left_messages_locale'], $locales_list)) {
-        $params['left_messages_locale'] = HOME_LOCALE;
-    }
-
-    $styles_params['chat_style'] = verify_param("chat_style", "/^\w+$/", $styles_params['chat_style']);
-    if (!in_array($styles_params['chat_style'], $chat_style_list)) {
-        $styles_params['chat_style'] = $chat_style_list[0];
-    }
-
-    $styles_params['page_style'] = verify_param("page_style", "/^\w+$/", $styles_params['page_style']);
-    if (!in_array($styles_params['page_style'], $page_style_list)) {
-        $styles_params['page_style'] = $page_style_list[0];
-    }
-
-    if (Settings::get('enabletracking')) {
-        $styles_params['invitation_style'] = verify_param(
-            "invitation_style",
-            "/^\w+$/",
-            $styles_params['invitation_style']
-        );
-        if (!in_array($styles_params['invitation_style'], $invitation_style_list)) {
-            $styles_params['invitation_style'] = $invitation_style_list[0];
-        }
-    }
-
-    if ($params['email'] && !is_valid_email($params['email'])) {
-        $page['errors'][] = getlocal("settings.wrong.email");
-    }
-
-    if ($params['geolinkparams']) {
-        foreach (preg_split("/,/", $params['geolinkparams']) as $one_param) {
-            $wrong_param = !preg_match(
-                "/^\s*(toolbar|scrollbars|location|status|menubar|width|height|resizable)=\d{1,4}$/",
-                $one_param
-            );
-            if ($wrong_param) {
-                $page['errors'][] = "Wrong link parameter: \"$one_param\", "
-                    . "should be one of 'toolbar, scrollbars, location, "
-                    . "status, menubar, width, height or resizable'";
-            }
-        }
-    }
-
-    if (preg_match("/^[0-9A-z]*$/", $params['cron_key']) == 0) {
-        $page['errors'][] = getlocal("settings.wrong.cronkey");
-    }
-
-    if (count($page['errors']) == 0) {
-        // Update system settings
-        foreach ($options as $opt) {
-            Settings::set($opt, $params[$opt]);
-        }
-        Settings::update();
-
-        // Update styles params
-        ChatStyle::setDefaultStyle($styles_params['chat_style']);
-        PageStyle::setDefaultStyle($styles_params['page_style']);
-        if (Settings::get('enabletracking')) {
-            InvitationStyle::setDefaultStyle($styles_params['invitation_style']);
-        }
-
-        // Redirect the user
-        header("Location: " . MIBEW_WEB_ROOT . "/operator/settings.php?stored");
-        exit;
-    }
-}
-
-$page['formemail'] = $params['email'];
-$page['formleftmessageslocale'] = $params['left_messages_locale'];
-$page['availableLocales'] = $locales_list;
-$page['formtitle'] = $params['title'];
-$page['formlogo'] = $params['logo'];
-$page['formhosturl'] = $params['hosturl'];
-$page['formgeolink'] = $params['geolink'];
-$page['formgeolinkparams'] = $params['geolinkparams'];
-$page['formusernamepattern'] = $params['usernamepattern'];
-$page['formpagestyle'] = $styles_params['page_style'];
-$page['availablePageStyles'] = $page_style_list;
-$page['formchatstyle'] = $styles_params['chat_style'];
-$page['formchattitle'] = $params['chattitle'];
-$page['formsendmessagekey'] = $params['sendmessagekey'];
-$page['availableChatStyles'] = $chat_style_list;
-$page['stored'] = isset($_GET['stored']);
-$page['enabletracking'] = Settings::get('enabletracking');
-$page['formcronkey'] = $params['cron_key'];
-
-$page['cron_path'] = cron_get_uri($params['cron_key']);
-
-$page['title'] = getlocal("settings.title");
-$page['menuid'] = "settings";
-
-if (Settings::get('enabletracking')) {
-    $page['forminvitationstyle'] = $styles_params['invitation_style'];
-    $page['availableInvitationStyles'] = $invitation_style_list;
-}
-
-$page = array_merge($page, prepare_menu($operator));
-
-$page['tabs'] = setup_settings_tabs(0);
-
-$page_style = new PageStyle(PageStyle::getCurrentStyle());
-$page_style->render('settings', $page);
diff --git a/src/mibew/styles/pages/default/templates_src/server_side/_menu.handlebars b/src/mibew/styles/pages/default/templates_src/server_side/_menu.handlebars
index bde3371a..77d4348c 100644
--- a/src/mibew/styles/pages/default/templates_src/server_side/_menu.handlebars
+++ b/src/mibew/styles/pages/default/templates_src/server_side/_menu.handlebars
@@ -31,7 +31,7 @@
                     <li{{#ifEqual menuid "getcode"}} class="active"{{/ifEqual}}><a href="{{mibewRoot}}/operator/getcode.php">{{l10n "leftMenu.client_gen_button"}}</a></li>
                     <li{{#ifEqual menuid "operators"}} class="active"{{/ifEqual}}><a href="{{mibewRoot}}/operator/operator">{{l10n "leftMenu.client_agents"}}</a></li>
                     <li{{#ifEqual menuid "groups"}} class="active"{{/ifEqual}}><a href="{{mibewRoot}}/operator/group">{{l10n "menu.groups"}}</a></li>
-                    <li{{#ifEqual menuid "settings"}} class="active"{{/ifEqual}}><a href="{{mibewRoot}}/operator/settings.php">{{l10n "leftMenu.client_settings"}}</a></li>
+                    <li{{#ifEqual menuid "settings"}} class="active"{{/ifEqual}}><a href="{{mibewRoot}}/operator/settings">{{l10n "leftMenu.client_settings"}}</a></li>
                     <li{{#ifEqual menuid "translation"}} class="active"{{/ifEqual}}><a href="{{mibewRoot}}/operator/translation">{{l10n "menu.translate"}}</a></li>
                     <li{{#ifEqual menuid "updates"}} class="active"{{/ifEqual}}><a href="{{mibewRoot}}/operator/updates">{{l10n "menu.updates"}}</a></li>
                 {{/if}}
diff --git a/src/mibew/styles/pages/default/templates_src/server_side/index.handlebars b/src/mibew/styles/pages/default/templates_src/server_side/index.handlebars
index 353b4781..1dcb1e58 100644
--- a/src/mibew/styles/pages/default/templates_src/server_side/index.handlebars
+++ b/src/mibew/styles/pages/default/templates_src/server_side/index.handlebars
@@ -114,7 +114,7 @@
                 <div class="dashitem">
                     <div class="dashitem-content">
                         <img src="{{stylePath}}/images/dash/settings.gif" alt=""/>
-                        <a href="{{mibewRoot}}/operator/settings.php">
+                        <a href="{{mibewRoot}}/operator/settings">
                             {{l10n "leftMenu.client_settings"}}
                         </a>
                         {{l10n "admin.content.client_settings"}}
diff --git a/src/mibew/styles/pages/default/templates_src/server_side/settings.handlebars b/src/mibew/styles/pages/default/templates_src/server_side/settings_common.handlebars
similarity index 99%
rename from src/mibew/styles/pages/default/templates_src/server_side/settings.handlebars
rename to src/mibew/styles/pages/default/templates_src/server_side/settings_common.handlebars
index f60681e2..be2742dd 100644
--- a/src/mibew/styles/pages/default/templates_src/server_side/settings.handlebars
+++ b/src/mibew/styles/pages/default/templates_src/server_side/settings_common.handlebars
@@ -13,7 +13,7 @@
             <div id="formmessage">{{l10n "settings.saved"}}</div>
         {{/if}}
 
-        <form name="settings" method="post" action="{{mibewRoot}}/operator/settings.php">
+        <form name="settings" method="post" action="{{mibewRoot}}/operator/settings">
             {{csrfTokenInput}}
 
             <div>