Просмотр исходного кода

Upgrade script from 9.0.0.RC11 to 9.0.0.RC12

Alex Trofimov 8 лет назад
Родитель
Сommit
960d4e2b2c
28 измененных файлов с 12284 добавлено и 0 удалено
  1. 9 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/check.php
  2. 1730 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolForm.php
  3. 429 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolLanguages.php
  4. 110 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolLog.php
  5. 361 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolScore.php
  6. 197 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolView.php
  7. 1722 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/utils.inc.php
  8. 12 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/version.inc.php
  9. 320 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/connect/classes/BxBaseModConnectModule.php
  10. 1552 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/general/classes/BxBaseModGeneralModule.php
  11. 683 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/groups/classes/BxBaseModGroupsModule.php
  12. 124 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/payment/classes/BxBaseModPaymentGridOrders.php
  13. 199 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/profile/classes/BxBaseModProfileMenuSnippetMeta.php
  14. 157 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/classes/BxDolStudioFormsField.php
  15. 707 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/classes/BxDolStudioFormsQuery.php
  16. 664 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/classes/BxDolStudioInstallerUtils.php
  17. 66 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/classes/BxDolStudioPermissionsLevels.php
  18. 1708 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/template/scripts/BxBaseStudioFormsField.php
  19. 599 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/template/scripts/BxBaseStudioPermissionsLevels.php
  20. 180 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/css/media-phone.css
  21. 166 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/form_field_location.html
  22. 72 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseCalendar.php
  23. 94 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseMenuFooter.php
  24. 85 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseMenuSwitchLanguage.php
  25. 252 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseSearchExtended.php
  26. 74 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseSearchExtendedServices.php
  27. 6 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/script.php
  28. 6 0
      upgrade/files/9.0.0.RC11-9.0.0.RC12/sql.sql

+ 9 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/check.php

@@ -0,0 +1,9 @@
+<?php
+
+$mixCheckResult = 'Update can not be applied';
+
+$sVer = $this->oDb->getOne("SELECT `version` FROM `sys_modules` WHERE `name` = 'system'");
+if ('9.0.0.RC11' == $sVer || '9.0.0-RC11' == $sVer)
+    $mixCheckResult = true;
+
+return $mixCheckResult;

+ 1730 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolForm.php

@@ -0,0 +1,1730 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_FORM_METHOD_GET', 'get');
+define('BX_DOL_FORM_METHOD_POST', 'post');
+define('BX_DOL_FORM_METHOD_SPECIFIC', 'specific');
+
+define('BX_DATA_LISTS_KEY_PREFIX', '#!');
+define('BX_DATA_VALUES_DEFAULT', 'LKey'); ///< Use default values for data items, @see BxDolForm::getDataItems
+define('BX_DATA_VALUES_ADDITIONAL', 'LKey2'); ///< Use additional values for data items, @see BxDolForm::getDataItems
+
+/**
+ * Forms allows to display forms from data stored in DB tables, before it was possible to display forms from PHP arrays only.
+ *
+ *
+ * The Form Objects have the following main advantages:
+ * - Minimal coding is needed to create different forms
+ * - Easy forms alterations
+ * - Multiple representations of the same form
+ * - Automated data inserting into database
+ * - Automated data updating
+ * - Automated data checking
+ * - Automatic security checking
+ * - Automatic spam filter
+ *
+ *
+ * Forms API uses several definitions:
+ * - Form or Form Object - record in sys_objects_form table, or instance of Form class.
+ * - Form Display - set of some form inputs in particular order, displayed for some purpose; one form can have several displays, for example add and edit displays.
+ * - Form Input or Form Field - form input field, like textarea, checkbox or set of radio buttons.
+ *
+ *
+ * @section form_creating_object Creating the Form Object
+ *
+ * 1. Create Form Object, add record to sys_objects_form table:
+ * - object - name of the Form Object, in the format: vendor prefix, underscore, module prefix, underscore, internal identifier or nothing; for example: bx_group - for group data processing, like group adding or editing.
+ * - title - Form Object title to display in studio forms builder.
+ * - action - url to submit form to, if url is not full and not empty, then site url is added automatically.
+ * - form_attrs - serialized array of additional form attributes.
+ * - submit_name - name of form field with submit button, it is used to determine if form is submitted.
+ * - table - DB table name (for automatic saving/updating).
+ * - key - DB table field with unique ID (for automatic updating).
+ * - uri - DB table field with URI (for automatic URI generation, aling with uri_title).
+ * - uri_title - DB table field with data title (for automatic URI generation, aling with uri).
+ * - params - serialized array of additional form parameters:
+ * - checker_helper - name of custom BxDolFormCheckerHelper class.
+ * - csrf - array of Cross-site request forgery attack prevention parameters, for now only one boolean parameter is supported - disabled, so it can be disabled for particular form.
+ * - active - 1 or 0, if form is inactive then it can not be used anywhere.
+ * - override_class_name - user defined class name which is derived from BxTemplFormView.
+ * - override_class_file - the location of the user defined class, leave it empty if class is located in system folders.
+ *
+ *
+ * 2. Create Form Displays, add records to sys_form_displays table:
+ * - display_name - name of the Form Display, in the format:
+ *                  form object name, underscore, internal identifier or nothing;
+ *                  for example: bx_group_add - for displaying group adding form, bx_group_edit - for displaying group editing form
+ * - module - module name this display belongs to, it must be associated with name field in sys_modules table.
+ * - object - form object name from sys_objects_form table this Form Display belongs to.
+ * - title - Form Display title to display in studio forms builder.
+ * - view_mode - display form as read-only.
+ *
+ *
+ * 3. Create Form Fields, add records to sys_form_inputs table:
+ * - object - form object name from sys_objects_form table this Form Field belongs to.
+ * - module - module name this field belongs to.
+ * - name - unique Form Field name in particular From Object.
+ * - value - default value, or empty if there is no default value.
+ * - values - possible values, for certain form field types.
+ * - checked - 0 or 1, it determines if form field is checked, for certain form field types.
+ * - type - form field type, for now the following types are supported:
+ *      - text - text input field.
+ *      - password - password input field.
+ *      - textarea - multiline input field.
+ *      - number - number input field.
+ *      - select - select one from all available values.
+ *      - select_multiple - select one, multiple or all items from all available values.
+ *      - switcher - on/off switcher.
+ *      - checkbox - one checkbox.
+ *      - checkbox_set - set of checkboxes.
+ *      - radio_set - set of radio buttons.
+ *      - slider - select some numeric value within the range using slider control.
+ *      - doublerange - select range values within the range using slider control.
+ *      - datepicker - date selection control.
+ *      - datetime - date/time selection control.
+ *      - captcha - image captcha.
+ *      - hidden - hidden input field.
+ *      - file - file upload input.
+ *      - button - button control.
+ *      - image - form image button.
+ *      - reset - form reset button.
+ *      - submit - form submit button.
+ *      - value - just displaying value without any crontol.
+ *      - block_header - start group of field.
+ *      - custom - custom control.
+ *      - input_set - set of other form controls.
+ *      detailed description of every type will be described below.
+ * - caption - input title.
+ * - info - some info to help user to input data into the field, it's better to specify format and limits here.
+ * - required - indicate that the input is required by displaying asterisk near the field,
+ *              please note that this field don't perform any checking automatically,
+ *              since you mark field as required you need to specify checked function which will check entered value.
+ * - collapsed - display section as collapsed by default, for block_header field type only.
+ * - html - display visual editor of certain type, for textarea field type only.
+ *      - 0 - no visual editor, leave textarea field as it is.
+ *      - 1 - standard(default) visual editor, @see BxDolEditor.
+ *      - 2 - full visual editor, @see BxDolEditor.
+ *      - 3 - mini visual editor, @see BxDolEditor.
+ * - attrs - serialized array of additional input attributes.
+ * - attrs_tr - serialized array of additional attributes for the whole input row.
+ * - attrs_wrapper - serialized array of additional attributes for input wrapper.
+ * - checker_func - checked function, if you marked field as required in textarea field you need to point one of the following checked functions:
+ *      - Length - check value length, additional params must contain min and/or max values for checking.
+ *      - Date - check if date is entered correctly.
+ *      - DateTime - check if datetime is entered correctly.
+ *      - Preg - check value with provided regular expression in checker_params field.
+ *      - Avail - just check if value isn't 0 or empty string, additional function parameters are not used.
+ *      - Email - check if value is written in valid email format.
+ *      - Captcha - check if captcha is entered correctly, for captcha field type only.
+ *      You can inherit BxDolFormCheckerHelper class and add own checker functions, you will need to point your custom class in Form Object params array.
+ * - checker_params - serialized array of checker_func parameters.
+ * - checker_error - error message to show in case of checking function returns false.
+ * - db_pass - function to pass value through before saving to database and after restoring from database (for example when date need to be converted from/to timestamp value),
+ *              available values are the following:
+ *      - Int - convert value to integer.
+ *      - Float - convert value to floating point number.
+ *      - Date,DateUtc - convert value to timestamp value before saving to database, and convert from timespamp value after restoring from database.
+ *      - DateTime,DateTimeUtc - convert value to timestamp value before saving to database, and convert from timespamp value after restoring from database.
+ *      - Xss - it warns you that this text can contain XSS vulnerabilities and you need to be extra careful with this, and always use Forms engine to output string to the browser or use bx_process_output if going to output text manually.
+ *      - XssHtml - this text cam have HTML tags, so perform XSS vulnerabilies cleaning before saving to database.
+ *      - All - do not perform any conversion and pass text as it is, be careful with this, use it only when no other function can be used, and make all necessary security checking by yourself.
+ *      - Preg - perform regular expression on the text before saving data to database, regular expression can be provided in db_params field.
+ *      - Boolean - this is used for checkboxes with 'on' value which need to be converted into boolean value.
+ *      - Set - convert set of values into bit integer before saving to database, and restore bit integer into array of values upon restoration from database, it can be used for select_multiple and checkbox_set field types.
+ *      Please note that values for this field must be 1,2,4,8,... (values of power of 2); the max number of values are 31 for 32bit hardware and 63 for 64bit hardware.
+ *      You can inherit BxDolFormCheckerHelper class and add own pass functions, you will need to point your custom class in Form Object params array.
+ * - db_params - serialized array of db_pass parameters.
+ * - editable - allow to edit this field from admin forms builder.
+ * - deletable - allow to delete this field from admin forms builder.
+ *
+ *
+ * 4. Add Form Fields and Form Displays associations, add records to sys_form_display_inputs table:
+ * - display_name - name of the Form Display from sys_form_displays table.
+ * - input_name - name of the Form Field from sys_form_inputs table.
+ * - visible_for_levels - bit field with set of member level ids. To use member level id in bit field the level id minus 1 is used as power of 2, for example:
+ *      - user level id = 1 -> 2^(1-1) = 1
+ *      - user level id = 2 -> 2^(2-1) = 2
+ *      - user level id = 3 -> 2^(3-1) = 4
+ *      - user level id = 4 -> 2^(4-1) = 8
+ * - active - 1 - form field displayed on form, or 0 - isn't displayed.
+ * - order - fields are displayed in this order.
+ *
+ *
+ * @section form_field_types Form Field Types
+ *
+ * Detailed description of Form Field Types.
+ *
+ * Almost all fields have the following common parameters:
+ * - object
+ * - name
+ * - type
+ * - caption
+ * - info
+ * - required
+ * - attrs
+ * - attrs_tr
+ * - attrs_wrapper
+ *
+ * We will not describe above list of parameters in every type, since they work the same way for all types.
+ *
+ * The list below are field types with their unique parameters, which are designed especially for this field, or some parameters which work differently for the specified field type.
+ *
+ * text - text input field. It is displayed as regular single line text input.
+ *      Parameters:
+ *      - value - default value, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail, Email
+ *          Make no sense to use it here: Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Boolean, Set
+ *
+ * password - password input field. It is displayed as HTML input element with invisible input.
+ *      Parameters:
+ *      - value - default value, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail.
+ *          Make no sense to use it here: Date, DateTime, Captcha, Email.
+ *      - db_pass
+ *          Can be used here: Xss, All.
+ *          Make no sense to use it here: Int, Float, Date, DateTime, XssHtml, Boolean, Set, Preg.
+ *
+ * textarea - multiline input field. It can be displayed as regular textarea field or as visual HTML editor.
+ *      Parameters:
+ *      - value - default value, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - use visual editor or not.
+ *          - 0 - no visual editor, leave textarea field as it is.
+ *          - 1 - standard(default) visual editor, @see BxDolEditor.
+ *          - 2 - full visual editor, @see BxDolEditor.
+ *          - 3 - mini visual editor, @see BxDolEditor.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, XssHtml, All, Preg
+ *          Make no sense to use it here: Date, DateTime, Boolean, Set
+ *
+ * number - number input field. It is displayed as HTL text input, but with limited width. Also some browsers can add additional controls to this field.
+ *      Parameters:
+ *      - value - default value, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float
+ *          Make no sense to use it here: Xss, XssHtml, All, Preg, Date, DateTime, Boolean, Set
+ *
+ * select - select one from all available values. It is displayed as HTML combo-box.
+ *      Parameters:
+ *      - value - default value (array index of selected item from values array), or empty - if there is no default value.
+ *      - values - serialized array of available values, or reference to predefined set of values.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Boolean, Set
+ *
+ * select_multiple - select one, multiple or all items from all available values. It is displayed as HTML multiple selection input.
+ *      Parameters:
+ *      - value - default value (bit integer of array indexes of selected items from values array), or empty - if there is no default value.
+ *      - values - serialized array of available values, or reference to predefined set of values. Array index must be power of 2. Max number of values is 31 on 32bit hardware or 63 on 64bit hardware.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int
+ *          Make no sense to use it here: Float, Xss, All, Preg, Date, DateTime, XssHtml, Boolean, Set
+ *
+ * switcher - on/off switcher. It is displayed as custom HTML element with own styles, but on background it works as regular HTML checkbox element.
+ *      Parameters:
+ *      - value - the value which will be submitted if switcher is on, if switcher is off - nothing is submitted.
+ *      - values - not applicable here.
+ *      - checked - if set to 1 then switcher is on by default, 0 - it is off by default.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg, Boolean
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Set
+ *
+ * checkbox - one checkbox. Displayed as HTML checkbox input element.
+ *      Parameters:
+ *      - value - the value which will be submitted if checkbox is checked, if checkbox isn't checked - nothing is submitted.
+ *      - values - not applicable here.
+ *      - checked - if set to 1 then checkbox is checked by default, 0 - it is unchecked by default.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg, Boolean
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Set
+ *
+ * checkbox_set - set of checkboxes. It is displayed as set of checkboxes. It is displayed in one row if number of items is equal or less than 3 or every item is displayed on new line if there is more than 3 items in the set.
+ *      Parameters:
+ *      - value - default value (bit integer of array indexes of selected items from values array), or empty - if there is no default value.
+ *      - values - serialized array of available values, or reference to predefined set of values. Array index must be power of 2. Max number of values is 31 on 32bit hardware or 63 on 64bit hardware.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int
+ *          Make no sense to use it here: Float, Xss, All, Preg, Date, DateTime, XssHtml, Boolean, Set
+ *
+ * radio_set - set of radio buttons. It is displayed as set of radio buttons. It is displayed in one row if number of items is equal or less than 3 or every item is displayed on new line if there is more than 3 items in the set.
+ *      Parameters:
+ *      - value - default value (array index of selected radio button from values array), or empty - if there is no default value.
+ *      - values - serialized array of available values, or reference to predefined set of values.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg
+ *          Make no sense to use it here: Date, DateTime, XssHtml, Boolean, Set
+ *
+ * slider - select some numeric value within the range using slider control. It is displayed as jQuery UI HTML control, but on background it works as regular HTML text input element.
+ *      Parameters:
+ *      - value - default value in the format, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - attrs - the following additional attributes can be used here:
+ *      - min - minimal value, default is 0.
+ *      - max - maximal value, default is 100.
+ *      - step - value can be changed by this step only, default is 1.
+ *      - checker_func
+ *          Can be used here: Length, Avail
+ *          Make no sense to use it here: Preg, Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float
+ *          Make no sense to use it here: Xss, XssHtml, All, Preg, Date, DateTime, Boolean, Set
+ *
+ * doublerange - select range values within the range using slider control.
+ *      Parameters:
+ *      - value - default value in the format [min value]-[max value], for example 16-99, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - attrs - the following additional attributes can be used here:
+ *          - min - minimal value, default is 0.
+ *          - max - maximal value, default is 100.
+ *          - step - value can be changed by this step only, default is 1.
+ *      - checker_func
+ *          Can be used here: Length, Avail
+ *          Make no sense to use it here: Preg, Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Xss, All, Preg
+ *          Make no sense to use it here: Int, Float, XssHtml, Date, DateTime, Boolean, Set
+ *
+ * datepicker - date selection control. It is displayed as HTML text input control, when clicking on this input then popup with date selector control is appeared.
+ *      Parameters:
+ *      - value - default value, in the format YYYY-MMM-DD, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Date
+ *          Make no sense to use it here: Length, Preg, Avail, Email, DateTime, Captcha
+ *      - db_pass
+ *          Can be used here: Date, DateUtc
+ *          Make no sense to use it here: Int, Float, Xss, All, Preg, DateTime, XssHtml, Boolean, Set
+ *
+ * datetime - date/time selection control.
+ *      Parameters:
+ *      - value - default value, in the format YYYY-MMM-DD HH:MM:SS, or empty if there is no default value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: DateTime
+ *          Make no sense to use it here: Length, Preg, Avail, Email, Date, Captcha
+ *      - db_pass
+ *          Can be used here: DateTime, DateTimeUtc
+ *          Make no sense to use it here: Int, Float, Xss, All, Preg, Date, XssHtml, Boolean, Set
+ *
+ * captcha - image captcha. Displayed as image with some text along with HTML text input for entering displayed on the image text.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Captcha
+ *          Make no sense to use it here: Length, Preg, Avail, Email, Date, DateTime
+ *      - db_pass
+ *          Can be used here: Xss, All, Preg
+ *          Make no sense to use it here: Int, Float, Date, DateTime, XssHtml, Boolean, Set
+ *
+ * hidden - hidden input field. Displayed as hidden HTML input.
+ *      Parameters:
+ *      - value - hidden input value.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          Can be used here: Length, Preg, Avail, Email, Date, DateTime
+ *          Make no sense to use it here: Captcha
+ *      - db_pass
+ *          Can be used here: Int, Float, Xss, All, Preg, Date, DateUtc, DateTime, DateTimeUtc, XssHtml, Boolean
+ *          Make no sense to use it here: Set
+ *
+ * file - file upload input. Displayed as file upload HTML input.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func
+ *          File name is passed for checking.
+ *          Can be used here: Avail, Length, Preg
+ *          Make no sense to use it here: Email, Date, DateTime, Captcha
+ *      - db_pass
+ *          File can't be stored in the database, so this field isn't applicable here.
+ *
+ * files - files upload input. Displayed as complex uploading HTML control.
+ *          This control is too complex to describe it using default set of database fields, you need to use custom class to display this control.
+ *
+ * button - button control. Displayes as HTML button element.
+ *      Parameters:
+ *      - value - translatable button caption.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * image - form image button. It is displayed as HTML form image input element.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - attrs - the following mandatory attribute must be specified here:
+ *          - src - image URL.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * reset - form reset button. Displayed as HTML form reset input button. By clicking on this button the form is reset to its default state.
+ *      Parameters:
+ *      - value - translatable button caption.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * submit - form submit button. Displayed as HTML form submit input button. This button have the primary button style to distinguish it from other buttons. By clicking on this button the form is submitted.
+ *      Parameters:
+ *      - value - translatable button caption.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * value - just displaying value without any control.
+ *      Parameters:
+ *      - value - the value to display.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * block_header - start group of fields. Displayed as form fields divider with caption - then it can be collapsible or without caption - then it is just divider without any functionality.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - not applicable here.
+ *      - checked - not applicable here.
+ *      - collapsed - display group of field collapsed by default, 1 - the group is collapsed, 0 - expanded (default value).
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ * custom - custom control. You need custom class to display this control, so the exact used values are determined by particular realisation.
+ *
+ * input_set - set of other form controls.
+ *      Parameters:
+ *      - value - not applicable here.
+ *      - values - comma separated list of field names (by name field) of fields to display here.
+ *      - checked - not applicable here.
+ *      - collapsed - not applicable here.
+ *      - html - not applicable here.
+ *      - checker_func - not applicable here.
+ *      - db_pass - not applicable here.
+ *
+ *
+ * @section form_using_own_class Using own class for custom behavior
+ *
+ * It is possible to provide own class for displaying and processing the form.
+ * To do this you need to point it in override_class_name and override_class_file fields in sys_objects_form table.
+ * Your custom class must be inherited from BxTemplFormView class or its descendants.
+ *
+ *
+ * @section form_display_custom_control Displaying custom field control
+ *
+ * It is possible to leave form field with default caption and override only the form field control.
+ * To override some field you need to define the following function:
+ * @code
+ *      protected function genCustomInput[field name] ($aInput).
+ * @endcode
+ * Where [field name] is form field name.
+ * For example:
+ *
+ * @code
+ *     protected function genCustomInputCustom ($aInput) {
+ *         return
+ *         'r: <input type="text" size="2" value="'.(isset($aInput['value'][0]) ? $aInput['value'][0] : '').'" name="'.$aInput['name'].'[]" />' .
+ *         'g: <input type="text" size="2" value="'.(isset($aInput['value'][1]) ? $aInput['value'][1] : '').'" name="'.$aInput['name'].'[]" />' .
+ *         'b: <input type="text" size="2" value="'.(isset($aInput['value'][2]) ? $aInput['value'][2] : '').'" name="'.$aInput['name'].'[]" />';
+ *     }
+ * @endcode
+ *
+ *
+ * @section form_display_custom_row Displaying custom field row
+ *
+ * Form row consists of caption and control, by default it is displayed with default design and functionality.
+ * If you need to display some field with custom header and control you need to declare the following function:
+ * @code
+ *      protected function genCustomRow[field name] ($aInput).
+ * @endcode
+ * Where [field name] is form field name.
+ *
+ *
+ * @section example Example of usage
+ *
+ * Printing the form for adding new record to the database:
+ *
+ * @code
+ *      $oForm = BxDolForm::getObjectInstance('sample_form_objects', 'sample_form_objects_add'); // get form instance for specified form object and display
+ *      if (!$oForm)
+ *          die('"sample_form_objects_add" form object or "sample_form_objects_add" display is not defined');
+ *      $oForm->initChecker(); // init form checker witout any data - adding new record
+ *      if ($oForm->isSubmittedAndValid())
+ *          echo 'inserted id: ' . $oForm->insert (); // add new record to the database
+ *      echo $oForm->getCode(); // display form
+ * @endcode
+ *
+ * Printing the form for editing existing record in the database:
+ *
+ * @code
+ *      // $iEditId - ID of edited row, for example from _GET parameter
+ *      $oDb = BxDolDb::getInstance();
+ *      $sQuery = $oDb->prepare("SELECT * FROM `sample_input_types` WHERE id = ?", $iEditId);
+ *      $aRecord = $oDb->getRow();
+ *      if (!$aRecord)
+ *          die("$iEditId record wasn't found.");
+ *
+ *      $oForm = BxDolForm::getObjectInstance('sample_form_objects', 'sample_form_objects_edit'); // get form instance for specified form object and display
+ *      if (!$oForm)
+ *          die('"sample_form_objects_edit" form object or "sample_form_objects_edit" display is not defined');
+ *      $oForm->initChecker($aRecord); // init form checker with edited data
+ *      if ($oForm->isSubmittedAndValid())
+ *          echo 'updated: ' . $oForm->update ($iEditId); // update database
+ *      echo $oForm->getCode(); // display form
+ * @endcode
+ *
+ * Example of custom form class and custom checking helper class:
+ *
+ * @code
+ *      class BxSampleForm extends BxTemplFormView {
+ *
+ *          public function __construct ($aInfo, $oTemplate = false) {
+ *              parent::__construct ($aInfo, $oTemplate);
+ *          }
+ *
+ *
+ *          // display input with 'custom' name
+ *          protected function genCustomInputCustom ($aInput) {
+ *              return
+ *              'r: <input type="text" size="2" value="'.(isset($aInput['value'][0]) ? $aInput['value'][0] : '').'" name="'.$aInput['name'].'[]" />' .
+ *              'g: <input type="text" size="2" value="'.(isset($aInput['value'][1]) ? $aInput['value'][1] : '').'" name="'.$aInput['name'].'[]" />' .
+ *              'b: <input type="text" size="2" value="'.(isset($aInput['value'][2]) ? $aInput['value'][2] : '').'" name="'.$aInput['name'].'[]" />';
+ *          }
+ *
+ *      }
+ *
+ *      class BxSampleFormCheckerHelper extends BxDolFormCheckerHelper {
+ *
+ *          protected $_sDiv = ',';
+ *
+ *          // prepare RBG values to save to the DB
+ *          function passRgb ($s) {
+ *              if (!is_array($s))
+ *                  return false;
+ *
+ *              $sRet = '';
+ *              foreach ($s as $k => $v)
+ *                  $sRet .= (int)trim($v) . $this->_sDiv;
+ *
+ *              return trim($sRet, $this->_sDiv);
+ *          }
+ *
+ *          // prepare RGB values to output to the screen
+ *          function displayRgb ($s) {
+ *              return explode($this->_sDiv, $s);
+ *          }
+ *      }
+ * @endcode
+ *
+ * The recommended way is to define forms in database, if it is impossible for some reasons you can init form object from array, there is an example:
+ *
+ * @code
+ *      $aForm = array(
+ *            'form_attrs' => array(
+ *                'name'     => 'form_my',
+ *                'method'   => 'post',
+ *            ),
+ *
+ *            'params' => array (
+ *                'db' => array(
+ *                    'table' => 'table_name', // table name
+ *                    'key' => 'ID', // key field name
+ *                    'uri' => 'EntryUri', // uri field name
+ *                    'uri_title' => 'Title', // title field to generate uri from
+ *                    'submit_name' => 'submit_form', // some filed name with non empty value to determine if the for was submitted,
+ *                                                       in most cases it is submit button name
+ *                ),
+ *                'csrf' => array(
+ *                      'disable' => true, //if it wasn't set or has some other value then CSRF checking is enabled for current form, take a look at sys_security_form_token_enable to disable CSRF checking completely.
+ *                )
+ *              ),
+ *
+ *            'inputs' => array(
+ *
+ *                'Title' => array(
+ *                    'type' => 'text',
+ *                    'name' => 'Title', // the same as key and database field name
+ *                    'caption' => 'Some caption',
+ *                    'required' => true,
+ *
+ *                    // checker params
+ *                    'checker' => array (
+ *                        'func' => 'length', // see BxDolFormCheckerHelper class for all check* functions
+ *                        'params' => array(3,100),
+ *                        'error' => 'length must be from 3 to 100 characters',
+ *                    ),
+ *                    // database params
+ *                    'db' => array (
+ *                        'pass' => 'Xss',  // do XSS clear before getting this value, see BxDolFormCheckerHelper class for all pass* functions
+ *                    ),
+ *                ),
+ *
+ *                'Description' => array(
+ *                    'type' => 'textarea',
+ *                    'name' => 'Description', // the same as key and database field name
+ *                    'caption' => 'Some caption',
+ *                    'required' => true,
+ *
+ *                    // checker params
+ *                    'checker' => array (
+ *                        'func' => 'length',
+ *                        'error' => 'enter at least 3 characters',
+ *                        'params' => array(3,64000),
+ *                    ),
+ *                    'db' => array (
+ *                        'pass' => 'XssHtml',  // do XSS clear, but keep HTML before getting this value
+ *                    ),
+ *                ),
+ *            );
+ * @endcode
+ *
+ * Using of above array:
+ *
+ * @code
+ *        $oForm = new BxTemplFormView ($aForm);
+ *        $oForm->initChecker();
+ *
+ *        if ($oForm->isSubmittedAndValid ()) {
+ *
+ *            // add additional vars to database, in this case creation date field is added
+ *            $aValsAdd = array (
+ *                'Date' => time(),
+ *            );
+ *
+ *            echo 'insert last id: ' . $oForm->insert ($aValsAdd); // insert validated data to database
+ *
+ *        } else {
+ *
+ *            echo $oForm->getCode (); // show form
+ *
+ *        }
+ *
+ * @endcode
+ *
+ */
+class BxDolForm extends BxDol implements iBxDolReplaceable
+{
+    static $TYPES_SKIP = array('files' => 1);
+    static $TYPES_CHECKBOX = array('checkbox' => 1, 'switcher' => 1);
+    static $TYPES_TEXT = array('text' => 1, 'textarea' => 1);
+    static $TYPES_FILE = array('file' => 1);
+
+    static $FUNC_SKIP_DOMAIN_CHECK = array('email' => 1, 'emails' => 1, 'emailexist' => 1, 'emailuniq' => 1, 'hostdomain' => 1, 'hostdomainchat' => 1);
+
+    protected $_aMarkers = array ();
+
+    protected $oTemplate;
+
+    protected $_isValid = true;
+    protected $_sCheckerHelper;
+    protected $_aSpecificValues;
+
+    public $aFormAttrs; ///< form html element attributes
+    public $aInputs; ///< form inputs
+    public $aParams; ///< additional form parameters
+    public $id; ///< Form element id
+
+    public function __construct ($aInfo, $oTemplate)
+    {
+        parent::__construct();
+
+        if ($oTemplate)
+            $this->oTemplate = $oTemplate;
+        else
+            $this->oTemplate = BxDolTemplate::getInstance();
+
+        $this->aFormAttrs    = isset($aInfo['form_attrs'])   ? $aInfo['form_attrs']  : array();
+        $this->aInputs       = isset($aInfo['inputs'])       ? $aInfo['inputs']      : array();
+        $this->aParams       = isset($aInfo['params'])       ? $aInfo['params']      : array();
+
+        // get form element id
+        $this->id = $this->aFormAttrs['id'] = (!empty($this->aFormAttrs['id']) ? $this->aFormAttrs['id'] : (!empty($this->aFormAttrs['name']) ? $this->aFormAttrs['name'] : 'form_advanced'));
+
+        // set default method
+        if (!isset($this->aFormAttrs['method']))
+            $this->aFormAttrs['method'] = BX_DOL_FORM_METHOD_POST;
+
+        // set default action
+        if (!isset($this->aFormAttrs['action']))
+            $this->aFormAttrs['action'] = '';
+
+        $this->_sCheckerHelper = isset($this->aParams['checker_helper']) ? $this->aParams['checker_helper'] : '';
+    }
+
+    /**
+     * Get form object instance by object name
+     * @param $sObject object name
+     * @return object instance or false on error
+     */
+    static public function getObjectInstance($sObject, $sDisplayName, $oTemplate = false, $sParam = '')
+    {
+        $sKey = 'BxDolForm!'.$sObject.'!'.$sDisplayName.'!'.$sParam;
+
+        if (isset($GLOBALS['bxDolClasses'][$sKey]))
+            return $GLOBALS['bxDolClasses'][$sKey];
+
+        $aObject = BxDolFormQuery::getFormArray($sObject, $sDisplayName);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        $sClass = 'BxTemplFormView';
+        if (!empty($aObject['override_class_name'])) {
+            $sClass = $aObject['override_class_name'];
+            if (!empty($aObject['override_class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
+        }
+
+        $o = new $sClass($aObject, $oTemplate);
+
+        return ($GLOBALS['bxDolClasses'][$sKey] = $o);
+    }
+
+    /**
+     * Get data items array
+     * @param $sKey data items identifier
+     * @param $isUseForSet convert data items keys to use in set fields, make it power of 2 (1,2,4,8,16,etc).
+     * @param $sUseValues use default(BX_DATA_VALUES_DEFAULT) or additional(BX_DATA_VALUES_ADDITIONAL) value titles, if additinal value title is missing default title is used
+     * @return data items array
+     */
+    public static function getDataItems($sKey, $isUseForSet = false, $sUseValues = BX_DATA_VALUES_DEFAULT)
+    {
+        return BxDolFormQuery::getDataItems($sKey, $isUseForSet, $sUseValues);
+    }
+
+    function initChecker ($aValues = array (), $aSpecificValues = array())
+    {
+        $this->_aSpecificValues = $aSpecificValues;
+
+        $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $aSpecificValues);
+        $oChecker->setFormParams($this->aParams);
+
+        // init form with default values
+
+        $aValuesDef = array ();
+        foreach ($this->aInputs as $k => $a) {
+            if (!isset($a['value']) || !isset($a['db']['pass']) || isset(self::$TYPES_CHECKBOX[$a['type']]) || isset(self::$TYPES_SKIP[$a['type']]))
+                continue;
+            $aValuesDef[$k] = $a['value'];
+        }
+
+        $oChecker->fillWithValues($this->aInputs, $aValuesDef);
+
+        // init form with provided values
+
+        if ($aValues)
+            $oChecker->fillWithValues($this->aInputs, $aValues);
+
+
+        if ($this->isSubmitted ()) {
+
+            // init form with submitted data, overwrite prevously declared values
+
+            $oChecker->enableFormCsrfChecking(isset($this->aParams['csrf']['disable']) && $this->aParams['csrf']['disable'] === true ? false : true);
+
+            $this->_isValid = $oChecker->check($this->aInputs, $aValues);
+
+            if (!$this->_initCheckerNestedForms ())
+                $this->_isValid = false;
+
+        }
+    }
+
+    function getKeyValuesPair ()
+    {
+        $aRet = array ();
+        foreach ($this->aInputs as $k => $a) {
+            if (!isset($a['value']) || isset(self::$TYPES_CHECKBOX[$a['type']]) || isset(self::$TYPES_SKIP[$a['type']]))
+                continue;
+            $aRet[$k] = $a['value'];
+        }
+        return $aRet;
+    }
+
+    function insert ($aValsToAdd = array(), $isIgnore = false)
+    {
+        $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $this->_aSpecificValues);
+        $sSql = $oChecker->dbInsert($this->aParams['db'], $this->aInputs, $aValsToAdd, $isIgnore);
+        if (!$sSql)
+            return false;
+        $oDb = BxDolDb::getInstance();
+        if ($oDb->query($sSql))
+            return $oDb->lastId();
+        return false;
+    }
+
+    function update ($val, $aValsToAdd = array(), &$aTrackTextFieldsChanges = null)
+    {
+        $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $this->_aSpecificValues);
+        $sSql = $oChecker->dbUpdate($val, $this->aParams['db'], $this->aInputs, $aValsToAdd, $aTrackTextFieldsChanges);
+        if (!$sSql)
+            return false;
+        return BxDolDb::getInstance()->query($sSql);
+    }
+
+    function delete ($val)
+    {
+        $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $this->_aSpecificValues);
+        $sSql = $oChecker->dbDelete($val, $this->aParams['db'], $this->aInputs);
+        if (!$sSql)
+            return false;
+        return BxDolDb::getInstance()->query($sSql);
+    }
+
+    function generateUri ()
+    {
+        $f = &$this->aParams['db'];
+        $sUri = $this->getCleanValue ($f['uri_title']);
+        return uriGenerate($sUri, $f['table'], $f['uri']);
+    }
+
+    function getCleanValue ($sName)
+    {
+        $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
+        $oChecker->setFormMethod($this->aFormAttrs['method'], $this->_aSpecificValues);
+        $a = isset($this->aInputs[$sName]) ? $this->aInputs[$sName] : false;
+        if ($a && isset($a['db']['pass']))
+            return $oChecker->get ($a['name'], $a['db']['pass'], isset($a['db']['params']) && $a['db']['params'] ? $a['db']['params'] : array());
+        else
+           return $oChecker->get ($sName);
+    }
+
+    function isSubmitted ()
+    {
+        $aSubmitName = $this->aParams['db']['submit_name'];
+        if(!is_array($aSubmitName))
+            $aSubmitName = array($aSubmitName);
+
+        foreach($aSubmitName as $sSubmitName)
+            if(BxDolForm::getSubmittedValue($sSubmitName, $this->aFormAttrs['method'], $this->_aSpecificValues))
+                return true;
+        
+        return false;
+    }
+
+    function getId ()
+    {
+        return $this->id;
+    }
+
+    function setId ($sId)
+    {
+        $this->id = $sId;
+        $this->aFormAttrs['id'] = $sId;
+    }
+
+    function setName($sName)
+    {
+        $this->aFormAttrs['name'] = $sName;
+    }
+
+    function setValid ($isValid)
+    {
+        $this->_isValid = $isValid;
+    }
+
+    function isValid ()
+    {
+        return $this->_isValid;
+    }
+
+    function isSubmittedAndValid ()
+    {
+        return ($this->isSubmitted() && $this->isValid());
+    }
+
+    function getSpecificValues()
+    {
+        return $this->_aSpecificValues;
+    }
+
+    public static function getSubmittedValue($sKey, $sMethod, &$aSpecificValues = false)
+    {
+        $aData = array();
+        if($sMethod == BX_DOL_FORM_METHOD_GET) {
+            $aData = &$_GET;
+        } else if($sMethod == BX_DOL_FORM_METHOD_POST) {
+            $aData = &$_POST;
+        } else if($sMethod == BX_DOL_FORM_METHOD_SPECIFIC) {
+            $aData = &$aSpecificValues;
+        }
+
+        return isset($aData[$sKey]) ? $aData[$sKey] : false;
+    }
+
+    public static function setSubmittedValue($sKey, $mixedValue, $sMethod, &$aSpecificValues = false)
+    {
+        $aData = array();
+        if($sMethod == BX_DOL_FORM_METHOD_GET) {
+            $aData = &$_GET;
+        } else if($sMethod == BX_DOL_FORM_METHOD_POST) {
+            $aData = &$_POST;
+        } else if($sMethod == BX_DOL_FORM_METHOD_SPECIFIC) {
+            $aData = &$aSpecificValues;
+        }
+
+        return $aData[$sKey] = $mixedValue;
+    }
+
+    /**
+     * Check if field exists
+     * @param $sName field name 
+     * @param $bCheckAllFields true - check all fields; false - all fields except: password, captcha, hidden, file, button, image, reset, submit, block_header, input_set
+     * @return true if field exists or false otherwise
+     */
+    public function isFieldExist($sName, $bCheckAllFields = false)
+    {
+        if (!isset($this->aInputs[$sName]))
+            return false;
+        return $bCheckAllFields ? true : !in_array($this->aInputs[$sName]['type'], array('password', 'captcha', 'hidden', 'button', 'reset', 'submit', 'block_header', 'input_set', 'location'));
+    }
+
+
+    // Static Methods related to CSRF Tocken
+    public static function genCsrfToken($bReturn = false)
+    {
+        // TODO: remove
+    }
+    public static function getCsrfToken()
+    {
+        if (getParam('sys_security_form_token_enable') != 'on')
+            return false;
+
+        if (!($oKeys = BxDolKey::getInstance()))
+            return false;
+
+        return $oKeys->getNewKey (false, (int)getParam('sys_security_form_token_lifetime'));
+    }
+    public static function isCsrfTokenValid($s)
+    {
+        if (getParam('sys_security_form_token_enable') != 'on')
+            return true;
+
+        if (!($oKeys = BxDolKey::getInstance()))
+            return true;
+
+        if ($oKeys->isKeyExists($s)) {
+            $oKeys->removeKey($s);
+            return true;
+        }
+        return false;
+    }
+
+    function _initCheckerNestedForms ()
+    {
+        $isValid = true;
+
+        // process nested forms
+        foreach ($this->aInputs as $sKey => $aInput) {
+
+            if (!isset($aInput['ghost_template']))
+                continue;
+
+            if (!(is_array($aInput['ghost_template']) && isset($aInput['ghost_template']['inputs'])) && !(is_object($aInput['ghost_template']) && $aInput['ghost_template'] instanceof BxDolFormNested))
+                continue;
+
+            $sName = $aInput['name'];
+            $aIds = $this->getSubmittedValue($sName, $this->aFormAttrs['method']);
+            if (!$aIds)
+                continue;
+
+            $aNestedForms = array ();
+            foreach ($aIds as $i => $iFileId) {
+
+                // create separate form instance for each file
+                $oFormNested = false;
+                if (is_object($aInput['ghost_template'])) {
+                    $oFormNested = clone($aInput['ghost_template']);
+                } else {
+                    $oFormNested = new BxDolFormNested($aInput['name'], $aInput['ghost_template'], $this->aParams['db']['submit_name'], $this->oTemplate);
+                }
+                if ($iFileId && 0 != $iFileId)
+                    $aNestedForms[$iFileId] = $oFormNested;
+                else
+                    $aNestedForms[] = $oFormNested;
+
+                // collect nested form values
+                $aSpecificValues = array ();
+                if (isset($this->aParams['db']['submit_name']))
+                    $aSpecificValues = array ($this->aParams['db']['submit_name'] => 1);
+                foreach ($oFormNested->aInputs as $r) {
+                    $sName = str_replace('[]', '', $r['name']);
+                    $aValue = $this->getSubmittedValue($sName, $this->aFormAttrs['method']);
+                    $aSpecificValues[$sName] = $aValue[$i];
+                }
+                $oFormNested->initChecker(array(), $aSpecificValues);
+
+                // if nested form in invalid - then the whole worm is failed
+                if (!$oFormNested->isValid ())
+                    $isValid = false;
+            }
+
+            if ($aNestedForms)
+                $this->aInputs[$sKey]['ghost_template'] = $aNestedForms;
+
+        }
+
+        return $isValid;
+    }
+
+    /**
+     * Check if form field is visible.
+     * @param $aInput form field array
+     * @return boolean
+     */
+    static public function isVisible ($aInput)
+    {
+        return isset($aInput['visible_for_levels']) ? BxDolAcl::getInstance()->isMemberLevelInSet($aInput['visible_for_levels']) : true;
+    }
+
+    protected function _genMethodName ($s)
+    {
+        return bx_gen_method_name($s);
+    }
+
+    /**
+     * Add replace markers. Curently markers are replaced in action, form_attrs fields.
+     * @param $a array of markers as key => value
+     * @return true on success or false on error
+     */
+    public function addMarkers ($a)
+    {
+        if (empty($a) || !is_array($a))
+            return false;
+        $this->_aMarkers = array_merge ($this->_aMarkers, $a);
+        return true;
+    }
+
+    /**
+     * Replace provided markers in form array
+     * @param $a form description array
+     * @return array where markes are replaced with real values
+     */
+    protected function _replaceMarkers ($a)
+    {
+        return bx_replace_markers($a, $this->_aMarkers);
+    }
+}
+
+class BxDolFormChecker
+{
+    protected $_oChecker;
+    protected $_sFormMethod;
+    protected $_aFormParams;
+    protected $_bFormCsrfChecking;
+    protected $_aSpecificValues;
+
+    function __construct ($sHelper = '')
+    {
+        $this->_sFormMethod = BX_DOL_FORM_METHOD_GET;
+        $this->_bFormCsrfChecking = true;
+
+        $sCheckerName = !empty($sHelper) ? $sHelper : 'BxDolFormCheckerHelper';
+        $this->_oChecker = new $sCheckerName();
+    }
+
+    function setFormMethod($sMethod, $aSpecificValues = array())
+    {
+        $this->_sFormMethod = $sMethod;
+        $this->_aSpecificValues = $aSpecificValues;
+    }
+
+    function setFormParams($aParams)
+    {
+        $this->_aFormParams = $aParams;
+    }
+    
+    function enableFormCsrfChecking($bFormCsrfChecking)
+    {
+        $this->_bFormCsrfChecking = $bFormCsrfChecking;
+    }
+
+    // check function
+    function check (&$aInputs, $aValues = array())
+    {
+        $oChecker = $this->_oChecker;
+        $iErrors = 0;
+
+        $sSubmitName = false;
+
+        foreach ($aInputs as $k => $a) {
+            if (isset($a['visible_for_levels']) && !BxDolForm::isVisible($a))
+                continue;
+
+            if (empty($a['name']) || 'submit' == $a['type'] || 'reset' == $a['type'] || 'button' == $a['type'] || 'value' == $a['type']) {
+                if (isset($a['type']) && 'submit' == $a['type'])
+                    $sSubmitName = $k;
+                continue;
+            }
+
+            if ('input_set' == $a['type'])
+                foreach ($a as $r)
+                    if (isset($r['type']) && 'submit' == $r['type'])
+                        $sSubmitName = $k;
+
+            $a['name'] = str_replace('[]', '', $a['name']);
+            $val = BxDolForm::getSubmittedValue($a['name'], $this->_sFormMethod, $this->_aSpecificValues);
+            if (isset(BxDolForm::$TYPES_FILE[$a['type']]))
+                $val = isset($_FILES[$a['name']]['name']) ? $_FILES[$a['name']]['name'] : '';
+
+            // check for unique
+            if(isset($aInputs[$k]['unique']) && $aInputs[$k]['unique'] && !empty($this->_aFormParams['db']['key']) && !empty($this->_aFormParams['db']['table'])) {
+                $sKey = $this->_aFormParams['db']['key'];
+                $sTable = $this->_aFormParams['db']['table'];
+                $sError = _t('_sys_form_err_not_unique_value');
+
+                if(!empty($aValues[$sKey])) {
+                    $sValDb = BxDolFormQuery::fieldGetValue($sTable, $aInputs[$k]['name'], $sKey, $aValues[$sKey]);
+                    if($val != $sValDb && !BxDolFormQuery::fieldCheckUnique($sTable, $aInputs[$k]['name'], $val)) {
+                        ++$iErrors;
+                        $aInputs[$k]['error'] = $sError;
+                    }
+                }
+                else if(!BxDolFormQuery::fieldCheckUnique($sTable, $aInputs[$k]['name'], $val)) {
+                    ++$iErrors;
+                    $aInputs[$k]['error'] = $sError;
+                }
+            }
+
+            // check for links in text fields
+            $sCheckerFunc = isset($a['checker']['func']) ? strtolower($a['checker']['func']) : '';
+            if (
+                !isset(BxDolForm::$FUNC_SKIP_DOMAIN_CHECK[$sCheckerFunc]) && 
+                (!isset($a['skip_domain_check']) || false == $a['skip_domain_check']) && 
+                isset(BxDolForm::$TYPES_TEXT[$a['type']]) && 
+                bx_is_url_in_content($val) && 
+                !isAdmin())
+            {
+                $aCheck = checkActionModule(bx_get_logged_profile_id(), 'post links', 'system');
+                if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED) {
+                    ++$iErrors;
+                    $aInputs[$k]['error'] = $aCheck[CHECK_ACTION_MESSAGE];
+                }
+                else {
+                    checkActionModule(bx_get_logged_profile_id(), 'post links', 'system', true);
+                }
+            }
+
+            if (!isset ($a['checker']))  {
+                if (isset(BxDolForm::$TYPES_CHECKBOX[$a['type']]))
+                    $aInputs[$k]['checked'] = (isset($aInputs[$k]['value']) && $aInputs[$k]['value'] == $val);
+                elseif (!isset(BxDolForm::$TYPES_FILE[$a['type']]) && !isset(BxDolForm::$TYPES_SKIP[$a['type']]))
+                    $aInputs[$k]['value'] = bx_process_input($val);
+                continue;
+            }
+
+            // special check for location field
+            if ('location' == $aInputs[$k]['type'] && $val && !BxDolForm::getSubmittedValue($a['name'] . '_country', $this->_sFormMethod, $this->_aSpecificValues)) {
+                $aInputs[$k]['value'] = '';
+                $val = '';
+            }
+
+            $sCheckFunction = array($oChecker, 'check' . bx_gen_method_name($a['checker']['func']));
+
+            if (is_callable($sCheckFunction))
+                $bool = call_user_func_array ($sCheckFunction, !empty($a['checker']['params']) ? array_merge(array($val), $a['checker']['params']) : array ($val));
+            else
+                $bool = true;
+
+            if (is_string($bool)) {
+                ++$iErrors;
+                $aInputs[$k]['error'] = $bool;
+            } elseif (!$bool) {
+                ++$iErrors;
+				if (isset($a['checker']['error']))
+                	$aInputs[$k]['error'] = $a['checker']['error'];
+            }
+
+            if (isset(BxDolForm::$TYPES_CHECKBOX[$a['type']]))
+                $aInputs[$k]['checked'] = ($aInputs[$k]['value'] == $val);
+            elseif (!isset(BxDolForm::$TYPES_FILE[$a['type']]) && !isset(BxDolForm::$TYPES_SKIP[$a['type']]))
+                $aInputs[$k]['value'] = bx_process_input($val);
+        }
+
+        // check CSRF token if it's needed.
+        if (getParam('sys_security_form_token_enable') == 'on' && $this->_bFormCsrfChecking === true) {
+            $mixedCsrfTokenUsr = BxDolForm::getSubmittedValue('csrf_token', $this->_sFormMethod, $this->_aSpecificValues);
+            unset($aInputs['csrf_token']);
+
+            if ($mixedCsrfTokenUsr === false || !BxDolForm::isCsrfTokenValid($mixedCsrfTokenUsr)) {
+                $aInputs[$sSubmitName]['error'] = _t('_sys_txt_form_submission_error_csrf_expired');
+                return false;
+            }
+        }
+
+        // check for spam
+        if (!$iErrors) {
+
+            foreach ($aInputs as $k => $a) {
+
+                if ($a['type'] != 'textarea')
+                    continue;
+
+                $a['name'] = str_replace('[]', '', $a['name']);
+                $val = BxDolForm::getSubmittedValue($a['name'], $this->_sFormMethod, $this->_aSpecificValues);
+                if (!$val)
+                    continue;
+
+                if (!$oChecker->checkIsSpam($val))
+                    continue;
+
+                ++$iErrors;
+
+                $sErr = _t('_sys_spam_detected');
+                if (BxDolRequest::serviceExists('bx_contact', 'get_contact_page_url') && ($sUrl = BxDolService::call('bx_contact', 'get_contact_page_url')))
+                    $sErr = _t('_sys_spam_detected_contact', $sUrl);
+                $aInputs[$k]['error'] = $sErr;
+            }
+        }
+
+        // add error message near submit button
+        if ($iErrors && $sSubmitName)
+            $aInputs[$sSubmitName]['error'] = _t('_sys_txt_form_submission_error');
+
+        return $iErrors ? false : true;
+    }
+
+    // get clean value from GET/POST
+    function get ($sName, $sPass = 'Xss', $aParams = array())
+    {
+        if (!$sPass)
+            $sPass = 'Xss';
+        $this->_oChecker;
+        $val = BxDolForm::getSubmittedValue($sName, $this->_sFormMethod, $this->_aSpecificValues);
+        return call_user_func_array (array($this->_oChecker, 'pass'.ucfirst($sPass)), $aParams ? array_merge(array($val), $aParams) : array ($val));
+    }
+
+    // db functions
+    function serializeDbValues (&$aInputs, &$aValsToAdd, &$aTrackTextFieldsChanges = null, $aUnset = null)
+    {
+        $aValsToUpdate = array();
+
+        if (null !== $aTrackTextFieldsChanges && isset($aTrackTextFieldsChanges['data']))
+            $aTrackTextFieldsChanges['changed_fields'] = array();
+
+        // get values from form description array
+        foreach ($aInputs as $k => $a) {
+            if (!isset ($a['db']) || !BxDolForm::isVisible($a)) continue;
+            $sKey = str_replace('[]', '', $a['name']);
+            $valClean = $this->get ($sKey, $a['db']['pass'], !empty($a['db']['params']) ? $a['db']['params'] : array());
+            $aValsToUpdate[$sKey] = $valClean;
+            $aInputs[$k]['db']['value'] = $valClean;
+
+            if (null !== $aTrackTextFieldsChanges && isset(BxDolForm::$TYPES_TEXT[$aInputs[$k]['type']]) && isset($aTrackTextFieldsChanges['data'][$a['name']]) && $aTrackTextFieldsChanges['data'][$a['name']] != $valClean)
+                $aTrackTextFieldsChanges['changed_fields'][] = $a['name'];
+        }
+
+        // get values which are provided manually
+        foreach ($aValsToAdd as $k => $val) {
+            $aValsToUpdate[$k] = $val;
+        }
+
+        // don't update id field for update statement
+        if ($aUnset) {
+            foreach ($aUnset as $sKey) {
+                if (isset($aValsToUpdate[$sKey]))
+                    unset($aValsToUpdate[$sKey]);
+            }
+        }
+
+        // build SQL query part
+        return BxDolDb::getInstance()->arrayToSQL($aValsToUpdate);
+    }
+
+    function dbInsert (&$aDb, &$aInputs, $aValsToAdd = array(), $isIgnore = false)
+    {
+        if (!$aDb['table'])
+            return '';
+
+        $sFields = $this->serializeDbValues ($aInputs, $aValsToAdd);
+        if (!$sFields)
+            return '';
+
+        return "INSERT " . ($isIgnore ? 'IGNORE' : '') . " INTO `{$aDb['table']}` SET $sFields";
+    }
+
+    function dbUpdate ($val, &$aDb, &$aInputs, $aValsToAdd = array(), &$aTrackTextFieldsChanges = null)
+    {
+        if (!$aDb['table'] || !$aDb['key'])
+            return '';
+
+        $oDb = BxDolDb::getInstance();
+
+        if (null !== $aTrackTextFieldsChanges && !isset($aTrackTextFieldsChanges['data'])) {
+            // get row values to compare old and new values
+            $sQuery = $oDb->prepare("SELECT * FROM `{$aDb['table']}` WHERE `{$aDb['key']}` = ?", $val);
+            $aTrackTextFieldsChanges['data'] = $oDb->getRow($sQuery);
+        }
+
+        $sFields = $this->serializeDbValues ($aInputs, $aValsToAdd, $aTrackTextFieldsChanges, array($aDb['key']));
+        if (!$sFields)
+            return '';
+
+        return $oDb->prepare("UPDATE `{$aDb['table']}` SET $sFields WHERE `{$aDb['key']}` = ?", $val);
+    }
+
+    function dbDelete ($val, &$aDb, &$aInputs)
+    {
+        if (!$aDb['table'] || !$aDb['key'])
+            return '';
+
+        $oDb = BxDolDb::getInstance();
+
+        return $oDb->prepare("DELETE FROM `{$aDb['table']}` WHERE `{$aDb['key']}` = ?", $val);
+    }
+
+    function fillWithValues (&$aInputs, &$aValues)
+    {
+        foreach ($aInputs as $k => $a) {
+            if (!isset($aValues[$k]) || isset(BxDolForm::$TYPES_SKIP[$aInputs[$k]['type']])) 
+                continue;
+
+            if (isset(BxDolForm::$TYPES_CHECKBOX[$aInputs[$k]['type']])) {
+                $aInputs[$k]['checked'] = isset($aInputs[$k]['value']) ? ($aInputs[$k]['value'] == $aValues[$k]) : false;
+            } else {
+                $sMethod = 'display' . (isset($a['db']['pass']) ? ucfirst($a['db']['pass']) : 'Undefined');
+                if (method_exists($this->_oChecker, $sMethod))
+                    $aInputs[$k]['value'] = call_user_func_array (array($this->_oChecker, $sMethod), !empty($a['db']['params']) ? array_merge(array($aValues[$k]), $a['db']['params']) : array ($aValues[$k]));
+                else
+                    $aInputs[$k]['value'] = $aValues[$k];
+            }
+        }
+    }
+}
+
+class BxDolFormCheckerHelper
+{
+    // check functions - check values for limits or patterns
+
+    static public function checkLength ($s, $iLenMin, $iLenMax)
+    {
+        if (is_array($s)) {
+            foreach ($s as $k => $v) {
+                $iLen = get_mb_len ($v);
+                if ($iLen < $iLenMin || $iLen > $iLenMax)
+                    return false;
+            }
+            return true;
+        }
+        $iLen = get_mb_len ($s);
+        return $iLen >= $iLenMin && $iLen <= $iLenMax ? true : false;
+    }
+    static public function checkDate ($s)
+    {
+        return self::checkPreg ($s, '#^\d+\-\d+\-\d+$#');
+    }
+    static public function checkDateRange ($s, $iMin, $iMax, $bRequired = false)
+    {
+        if (!$bRequired && (empty($s) || '0000-00-00' == $s))
+            return true;   
+        if (self::checkPreg ($s, '#^\d+\-\d+\-\d+$#')){
+            $dDate = new DateTime($s);
+            $dDateMax = new DateTime("-" . $iMin . " years");
+            $dDateMin = new DateTime("-" . $iMax . " years");
+            if ($dDate > $dDateMin && $dDate < $dDateMax){
+                return true;
+            }
+        }
+        return false;
+    }
+    static public function checkDateTime ($s)
+    {
+        // remove unnecessary opera's input value;
+        $s = str_replace('T', ' ', $s);
+        $s = str_replace('Z', ':00', $s);
+
+        return self::checkPreg ($s, '#^\d+\-\d+\-\d+[\sT]{1}\d+:\d+[:\d+]$#');
+    }
+    static public function checkPreg ($s, $r)
+    {
+        if (is_array($s)) {
+            foreach ($s as $k => $v)
+                if (!preg_match($r, $v))
+                    return false;
+            return true;
+        }
+        return preg_match($r, $s) ? true : false;
+    }
+    static public function checkAvail ($s)
+    {
+        if (is_array($s)) {
+            return !self::_isEmptyArray($s);
+        }
+        return $s ? true : false;
+    }
+    static public function checkEmail($s)
+    {
+        if (false === strpos($s, '@')) // simple check
+            return false;
+
+        if (!preg_match("/^[\pL\pNd]/u", $s)) // must start with letter or number
+            return false;
+
+        if (!preg_match("/@[\pL\pNd\.\-]+$/u", $s)) // validate domain
+            return false;
+
+        $s = str_replace(array('@', '.', '-', '+', '_'), '', $s); // allowed symbols
+        if (preg_match("/[^\pL^\pNd]/u", $s)) // check for undesirable chars
+            return false;
+
+        return true;
+        // return self::checkPreg ($s, "/(([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?/");
+    }
+    static public function checkCaptcha($s)
+    {
+        $oCaptcha = BxDolCaptcha::getObjectInstance();
+        if (!$oCaptcha)
+            return true;
+        return $oCaptcha->check ();
+    }
+    static public function checkIsSpam($val)
+    {
+        $bSpam = false;
+        bx_alert('system', 'check_spam', 0, getLoggedId(), array('is_spam' => &$bSpam, 'content' => $val, 'where' => 'form'));
+        return $bSpam;
+    }
+
+    // pass functions, prepare values to insert to database
+    static public function passInt ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = (int)trim($v);
+            }
+            return $a;
+        }
+        return (int)$s;
+    }
+    static public function passFloat ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = (float)$v;
+            }
+            return $a;
+        }
+        return (float)$s;
+    }
+    static public function passDate ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATE);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATE);
+    }
+    static public function passDateTs ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATE_TS);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATE_TS);
+    }
+    static public function passDateUtc ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATE_TS_UTC);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATE_TS_UTC);
+    }
+    static public function passDateTime ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATETIME);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATETIME);
+    }
+    static public function passDateTimeTs ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATETIME_TS);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATETIME_TS);
+    }
+    static public function passDateTimeUtc ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passDate ($v, BX_DATA_DATETIME_TS_UTC);
+            }
+            return $a;
+        }
+        return self::_passDate ($s, BX_DATA_DATETIME_TS_UTC);
+    }
+    static public function _passDate ($s, $iFormat)
+    {
+        $iRet = bx_process_input ($s, $iFormat);
+        if (false === $iRet)
+            return 0;
+        return $iRet;
+    }    
+    static public function passXss ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = bx_process_input ($v, BX_DATA_TEXT); // "strip tags" option was here in 7.0
+            }
+            return $a;
+        }
+        return bx_process_input ($s, BX_DATA_TEXT); // "strip tags" option was here in 7.0
+    }
+
+	static public function passXssMultiline ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = bx_process_input ($v, BX_DATA_TEXT_MULTILINE);
+            }
+            return $a;
+        }
+        return bx_process_input ($s, BX_DATA_TEXT_MULTILINE);
+    }
+
+    static public function passXssHtml ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = bx_process_input ($v, BX_DATA_HTML);
+            }
+            return $a;
+        }
+        return bx_process_input ($s, BX_DATA_HTML);
+    }
+
+    static public function passAll ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = bx_process_input ($v);
+            }
+            return $a;
+        }
+        return bx_process_input ($s);
+    }
+
+    static public function passPreg ($s, $r)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = self::_passPreg ($v, $r);
+            }
+            return $a;
+        }
+        return self::_passPreg($s, $r);
+    }
+    static public function _passPreg ($s, $r)
+    {
+        if (preg_match ($r, $s, $m)) {
+            return $m[1];
+        }
+        return '';
+    }
+    static public function passBoolean ($s)
+    {
+        if (is_array($s)) {
+            $a = array ();
+            foreach ($s as $k => $v) {
+                $a[$k] = $v == 'on' ? true : false;
+            }
+            return $a;
+        }
+        return $s == 'on' ? true : false;
+    }
+
+    static public function passSet ($s)
+    {
+        if (is_array($s)) {
+            $i = 0;
+            foreach ($s as $v)
+                $i |= pow (2, $v - 1);
+            return $i;
+        }
+        return (int)$s;
+    }
+
+    // display functions, prepare values to output to the screen
+
+    static public function displayDate ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATE);
+    }
+    static public function displayDateTs ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATE_TS);
+    }
+    static public function displayDateUtc ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATE_TS_UTC);
+    }    
+    static public function displayDateTime ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATETIME);
+    }
+    static public function displayDateTimeTs ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATETIME_TS);
+    }
+    static public function displayDateTimeUtc ($i)
+    {
+        return bx_process_output ($i, BX_DATA_DATETIME_TS_UTC);
+    }    
+    static public function displaySet ($i)
+    {
+        $i = (int)$i;
+
+        $bit = 1;
+        $bitpos = 1;
+        $a = array();
+        while ($bit < BX_DOL_INT_MAX && $bit > 0) {
+            if ($bit & $i)
+                $a[] = $bitpos;
+            $bit <<= 1;
+            $bitpos++;
+        }
+        return $a;
+    }
+
+    // for internal usage only
+
+    static public function _isEmptyArray ($a)
+    {
+        return bx_is_empty_array($a);
+    }
+
+    static public function _isFullArray ($a)
+    {
+        return bx_is_full_array($a);
+    }
+}
+
+/** @} */

+ 429 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolLanguages.php

@@ -0,0 +1,429 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_LANGUAGE_DEFAULT', 'en');
+
+define('BX_DOL_LANGUAGE_DIRECTION_LTR', 'LTR');
+define('BX_DOL_LANGUAGE_DIRECTION_RTL', 'RTL');
+
+define('BX_DOL_LANGUAGE_CATEGORY_SYSTEM', 1);
+define('BX_DOL_LANGUAGE_CATEGORY_CUSTOM', 2);
+
+class BxDolLanguages extends BxDolFactory implements iBxDolSingleton
+{
+    protected $oDb;
+    protected $sCurrentLanguage;
+
+    protected function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->oDb = BxDolLanguagesQuery::getInstance();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    static function getInstance()
+    {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__])) {
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolLanguages();
+            $GLOBALS['bxDolClasses'][__CLASS__]->init();
+        }
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    function getCurrentLangName($bSetCookie = true)
+    {
+        $sLang = '';
+
+        if(!$sLang && !empty($_GET['lang']))
+            $sLang = $this->tryToGetLang($_GET['lang'], $bSetCookie);
+
+        if(!$sLang && !empty($_POST['lang']))
+            $sLang = $this->tryToGetLang($_POST['lang'], $bSetCookie);
+
+        if(!$sLang && !empty($_COOKIE['lang']))
+            $sLang = $this->tryToGetLang($_COOKIE['lang']);
+
+        if(!$sLang && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
+            $sLang = $this->tryToGetLang($_SERVER['HTTP_ACCEPT_LANGUAGE']);
+
+        if(!$sLang)
+            $sLang = $this->tryToGetLang(getParam('lang_default'));
+
+        if(!$sLang)
+            $sLang = $this->tryToGetLang(BX_DOL_LANGUAGE_DEFAULT);
+
+        $sLocale = $this->getLangCountryCode($sLang);
+        if (!$sLocale)
+            $sLocale = $sLang . '_' . strtoupper($sLang);
+        
+        setlocale(LC_TIME,
+            $sLocale . '.utf-8',
+            $sLocale . '.utf8',
+            $sLang . '.utf-8',
+            $sLang . '.utf8',
+            $sLang);
+
+        return $sLang;
+    }
+
+    function getCurrentLangId()
+    {
+        return $this->getLangId($this->getCurrentLanguage());
+    }
+
+    function getLangId($sLang)
+    {
+        return $this->oDb->getLanguageId($sLang);
+    }
+
+    function getLangName($iLangId)
+    {
+        return $this->oDb->getLanguageName($iLangId);
+    }
+
+    function getLangFlag($sLang = '')
+    {
+        if (!$sLang)
+            $sLang = $this->getCurrentLanguage();
+
+        return $this->oDb->getLanguageFlag($sLang);
+    }
+
+    function getLangDirection($sLang = '')
+    {
+        if (!$sLang)
+            $sLang = $this->getCurrentLanguage();
+
+        return strtoupper($this->oDb->getLanguageDirection($sLang));
+    }
+
+    function getLangCountryCode($sLang = '')
+    {
+        if (!$sLang)
+            $sLang = $this->getCurrentLanguage();
+
+        return $this->oDb->getLangCountryCode($sLang);
+    }
+    
+    function getDefaultLangName()
+    {
+        return getParam('lang_default');
+    }
+
+    function getLanguages($bIdAsKey = false, $bActiveOnly = false)
+    {
+        return $this->oDb->getLanguages($bIdAsKey, $bActiveOnly);
+    }
+
+    function getLanguageCategory($sName)
+    {
+        $iId = 0;
+        $this->oDb->getCategoriesBy(array('type' => 'id_by_name', 'value' => $sName), $iId);
+
+        return (int)$iId;
+    }
+
+    /**
+     * It tries to match current language with provided languages array, if nothing is found $sFallbackLanguage is returned.
+     * @param $aLangs - array of languages, example: array('ru' => 1, 'kg' => 1);
+     * @param $sFallbackLanguage - language to return of nothis is found
+     * @return language code string
+     */
+    function detectLanguageFromArray($aLangs, $sFallbackLanguage = 'en', $bLowercase = false)
+    {
+        if (isset($aLangs[$GLOBALS['sCurrentLanguage']])) {
+            return $GLOBALS['sCurrentLanguage'];
+        } 
+        elseif ($sLangCountry = $this->getLangCountryCode()) {
+            if ($bLowercase)
+                $sLangCountry = strtolower($sLangCountry);
+
+            if (isset($aLangs[$sLangCountry]))
+                return $sLangCountry;
+            
+            $sLangCountry = str_replace('_', '-', $sLangCountry);
+            if (isset($aLangs[$sLangCountry]))
+                return $sLangCountry;            
+        }
+
+        return $sFallbackLanguage;
+    }
+
+    /**
+     * Get current language.
+     */
+    function getCurrentLanguage()
+    {
+        return $GLOBALS['sCurrentLanguage'];
+    }
+
+    function _t()
+    {
+        global $LANG;
+
+        $key = func_get_arg(0);
+        if(isset($LANG[$key])) {
+            $str = $LANG[$key];
+
+            if(($iNumArgs = func_num_args()) > 1)
+                for($i = 1; $i < $iNumArgs; $i++)
+                    $str = str_replace('{' . ($i - 1) . '}', func_get_arg($i), $str);
+
+            return $str;
+        } else
+            return $key;
+    }
+
+    function _t_err()
+    {
+        return MsgBox(call_user_func_array(array($this, '_t'), func_get_args()));
+    }
+
+    function _t_action()
+    {
+        return MsgBox(call_user_func_array(array($this, '_t'), func_get_args()));
+    }
+
+    function _t_ext($key, $args)
+    {
+        global $LANG;
+
+        if(isset($LANG[$key])) {
+            $str = $LANG[$key];
+
+            if(!is_array($args))
+                return str_replace('{0}', $args, $str);
+
+            foreach ($args as $key => $val)
+                $str = str_replace('{'.$key.'}', $val, $str);
+
+            return $str;
+        } else
+            return $key;
+    }
+
+    function _t_format_size ($iSize)
+    {
+        $a = array (
+            1024 => '_sys_format_size_b',
+            1024*1024 => '_sys_format_size_kb',
+            1024*1024*1024 => '_sys_format_size_mb',
+            1024*1024*1024*1024 => '_sys_format_size_gb',
+            1024*1024*1024*1024*1024 => '_sys_format_size_tb',
+        );
+        foreach($a as $i => $sKey)
+            if($iSize < $i)
+                return $this->_t($sKey, round($iSize / ($i / 1024), 1));
+        return $this->_t('_sys_format_size_b', 0);
+    }
+
+    function _t_format_duration ($iTime)
+    {
+        $iTime = (int)$iTime;
+
+        $sFormat = '%M:%S';
+        if($iTime > 3600)
+            $sFormat = '%H:' . $sFormat;
+
+        return strftime($sFormat, $iTime);
+    }
+
+    function _t_format_currency ($fPrice)
+    {
+        return $this->_t('_sys_currency', getParam('currency_sign'), sprintf("%.2f", (float)$fPrice));
+    }
+
+    function _t_format_extensions ($mixedExtensions)
+    {
+        if (!is_array($mixedExtensions))
+            $a = explode(',', $mixedExtensions);
+        else
+            $a = $mixedExtensions;
+        if (!$a)
+            return '';
+        return '.' . implode(', .', $a);
+    }
+
+    protected function init()
+    {
+        /**
+         * Trying to initialize default language.
+         */
+        $GLOBALS['sCurrentLanguage'] = $GLOBALS['bxDolClasses'][__CLASS__]->getCurrentLangName(false);
+        if($GLOBALS['sCurrentLanguage'] != '') {
+            $sPath = BX_DIRECTORY_PATH_CACHE . 'lang-' . $GLOBALS['sCurrentLanguage'] . '.php';
+            if(!file_exists($sPath))
+                BxDolStudioLanguagesUtils::getInstance()->compileLanguage();
+
+            require($sPath);
+
+            if($this->getLangDirection($GLOBALS['sCurrentLanguage']) == BX_DOL_LANGUAGE_DIRECTION_RTL)
+                BxDolTemplate::getInstance()->addCss('rtl.css');
+        }
+
+        $GLOBALS['bxDolClasses'][__CLASS__]->getCurrentLangName(true);
+        if(isset($_GET['lang'])) {
+            if(BxDolPermalinks::getInstance()->redirectIfNecessary(array('lang')))
+                exit;
+        }
+    }
+
+    protected function tryToGetLang($sLangs, $bSetCookie = false)
+    {
+        $sLangs = trim($sLangs);
+        if(!$sLangs)
+            return '';
+
+        $sLangs = preg_replace( '/[^a-zA-Z0-9,;-]/m', '', $sLangs ); // we do not need 'q=0.3'. we are using live queue :)
+        $sLangs = strtolower($sLangs);
+
+        if(!$sLangs)
+            return '';
+
+        $aLangs = explode(',', $sLangs); // ru,en-us;q=0.7,en;q=0.3 => array( 'ru' , 'en-us;q=0.7' , 'en;q=0.3' );
+        foreach($aLangs as $sLang) {
+            if(!$sLang)
+                continue;
+
+            list($sLang) = explode(';', $sLang, 2); // en-us;q=0.7 => en-us
+            if(!$sLang)
+                continue;
+
+            // check with country
+            if($this->checkLangExists($sLang)) {
+                if( $bSetCookie && (!isset($_COOKIE['lang']) || $_COOKIE['lang'] != $sLang) && (!isset($GLOBALS['glLangSet']) || $GLOBALS['glLangSet'] != $sLang)) {
+                    $this->setLangCookie( $sLang );
+                    $GLOBALS['glLangSet'] = $sLang;
+                }
+                return $sLang;
+            }
+
+            //drop country
+            if(strpos($sLang, '-') === false)
+                continue;
+
+            list($sLang, $sCntr) = explode('-', $sLang, 2); // en-us => en
+            if(!$sLang or !$sCntr)
+                continue; //no lang or nothing changed
+
+            //check again. without country
+            if($this->checkLangExists($sLang)) {
+                if($bSetCookie)
+                    $this->setLangCookie($sLang);
+                return $sLang;
+            }
+        }
+        return '';
+    }
+    protected function checkLangExists( $sLang )
+    {
+        if(!preg_match('/^[A-Za-z0-9_]+$/', $sLang))
+            return false;
+
+        $iLangId = $this->oDb->getLanguageId($sLang, false);
+        if(!$iLangId)
+            return false;
+
+        if(file_exists( BX_DIRECTORY_PATH_CACHE . "lang-{$sLang}.php"))
+            return true;
+
+        if(BxDolStudioLanguagesUtils::getInstance()->compileLanguage($iLangId))
+            return true;
+
+        return false;
+    }
+
+    protected function setLangCookie( $sLang )
+    {
+        $sLang = bx_process_input($sLang);
+
+        if(isLogged()) {
+            $iLangId = $this->oDb->getLanguageId($sLang, false);
+            if(!$iLangId)
+                $iLangId = 0;
+
+            $iAccountId = getLoggedId();
+            $oAccountQuery = BxDolAccountQuery::getInstance();
+            $oAccountQuery->updateLanguage($iAccountId, $iLangId);
+        }
+
+        setcookie( 'lang', '',     time() - 60*60*24,    '/' );
+        setcookie( 'lang', $sLang, time() + 60*60*24*365, '/' );
+    }
+}
+
+if (!function_exists('_t')) {
+    function _t()
+    {
+        return call_user_func_array(array(BxDolLanguages::getInstance(), '_t'), func_get_args());
+    }
+}
+
+function _t_err()
+{
+    return call_user_func_array(array(BxDolLanguages::getInstance(), '_t_err'), func_get_args());
+}
+
+function _t_action()
+{
+    return call_user_func_array(array(BxDolLanguages::getInstance(), '_t_action'), func_get_args());
+}
+
+function _t_ext($key, $args)
+{
+    return BxDolLanguages::getInstance()->_t_ext($key, $args);
+}
+
+function _t_format_size($iSize)
+{
+    return BxDolLanguages::getInstance()->_t_format_size($iSize);
+}
+
+function _t_format_duration($iTime)
+{
+    return BxDolLanguages::getInstance()->_t_format_duration($iTime);
+}
+
+function _t_format_currency($fPrice)
+{
+    return BxDolLanguages::getInstance()->_t_format_currency($fPrice);
+}
+
+function _t_format_extensions($mixedExtensions)
+{
+    return BxDolLanguages::getInstance()->_t_format_extensions($mixedExtensions);
+}
+
+function bx_lang_name()
+{
+    return BxDolLanguages::getInstance()->getCurrentLanguage();
+}
+
+function bx_lang_direction($sLanguage = '')
+{
+    return BxDolLanguages::getInstance()->getLangDirection($sLanguage);
+}
+
+/** @} */

+ 110 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolLog.php

@@ -0,0 +1,110 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+class BxDolLog extends BxDolFactory implements iBxDolSingleton
+{
+    protected $_sFile;
+    protected $_sName;
+
+    protected function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if (!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolLog();
+
+        $GLOBALS['bxDolClasses'][__CLASS__]->reset();
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    /**
+     * Reset custom log file and log name 
+     * to be empty to ready for future usage.
+     */
+    public function reset()
+    {
+        $this->_sFile = '';
+        $this->_sName = '';
+    }
+
+    /**
+     * Set custom log file. The file should have necessary permissions.
+     * @param string $sFile - full path to custom log file.
+     */
+    public function setFile($sFile)
+    {
+        $this->_sFile = $sFile;
+    }
+
+    /**
+     * Set log name. Log file will be created\updated in standard log directory.
+     * @param string $sName - log file name without path and extension.
+     */
+    public function setName($sName)
+    {
+        $this->_sName =  $sName;
+    }
+
+    /**
+     * Writes content to a log file. Can get any number of arguments to be written 
+     * in one write session. Each argument can be a string, an array or an object.
+     */
+    public function write()
+    {
+        if(func_num_args() == 0)
+            return;
+
+        try {
+            $sFile = '';
+
+            if(!empty($this->_sFile))
+                $sFile = $this->_sFile;
+            else 
+                $sFile = BX_DIRECTORY_PATH_LOGS . '/' . $this->_sName . '.log';
+
+            if(empty($sFile))
+                return;
+
+            file_put_contents($sFile, date('m-d H:i:s') . ":\n", FILE_APPEND);
+
+            $aArgs = func_get_args();
+            foreach($aArgs as $mixedArg) {
+                if(is_array($mixedArg))
+                    $mixedArg = var_export($mixedArg, true);	
+                else if(is_object($mixedArg))
+                    $mixedArg = json_encode($mixedArg);
+
+                file_put_contents($sFile, $mixedArg . "\n", FILE_APPEND);
+            }
+        }
+        catch (Exception $oException) {
+            echo 'Error: ' . $oException->getMessage();
+        }
+    }
+}
+
+/** @} */

+ 361 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolScore.php

@@ -0,0 +1,361 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+bx_import('BxDolAcl');
+
+define('BX_DOL_SCORE_USAGE_BLOCK', 'block');
+define('BX_DOL_SCORE_USAGE_INLINE', 'inline');
+define('BX_DOL_SCORE_USAGE_DEFAULT', BX_DOL_SCORE_USAGE_BLOCK);
+
+define('BX_DOL_SCORE_DO_UP', 'up');
+define('BX_DOL_SCORE_DO_DOWN', 'down');
+
+/**
+ * Score for any content
+ *
+ * Related classes:
+ * - BxDolScoreQuery - vote database queries
+ * - BxBaseScore - vote base representation
+ * - BxTemplScore - custom template representation
+ *
+ * AJAX vote for any content. Stars and Plus based representations are supported.
+ *
+ * To add vote section to your feature you need to add a record to 'sys_objects_vote' table:
+ *
+ * - ID - autoincremented id for internal usage
+ * - Name - your unique module name, with vendor prefix, lowercase and spaces are underscored
+ * - TableMain - table name where summary votigs are stored
+ * - TableTrack - table name where each vote is stored
+ * - PostTimeout - number of seconds to not allow duplicate vote
+ * - MinValue - min vote value, 1 by default
+ * - MaxValue - max vote value, 5 by default
+ * - IsUndo - is Undo enabled for Plus based votes
+ * - IsOn - is this vote object enabled
+ * - TriggerTable - table to be updated upon each vote
+ * - TriggerFieldId - TriggerTable table field with unique record id, primary key
+ * - TriggerFieldRate - TriggerTable table field with average rate
+ * - TriggerFieldRateCount - TriggerTable table field with votes count
+ * - ClassName - your custom class name, if you overrride default class
+ * - ClassFile - your custom class path
+ *
+ * You can refer to BoonEx modules for sample record in this table.
+ *
+ *
+ *
+ * @section example Example of usage:
+ * To get Star based vote you need to have different values for MinValue and MaxValue (for example 1 and 5)
+ * and IsUndo should be equal to 0. To get Plus(Like) based vote you need to have equal values
+ * for MinValue and MaxValue (for example 1) and IsUndo should be equal to 1. After filling the other
+ * paramenters in the table you can show vote in any place, using the following code:
+ * @code
+ * $o = BxDolScore::getObjectInstance('system object name', $iYourEntryId);
+ * if (!$o->isEnabled()) return '';
+ *     echo $o->getElementBlock();
+ * @endcode
+ *
+ *
+ * @section acl Memberships/ACL:
+ * - vote
+ *
+ *
+ *
+ * @section alerts Alerts:
+ * Alerts type/unit - every module has own type/unit, it equals to ObjectName.
+ * The following alerts are rised:
+ *
+ * - rate - comment was posted
+ *      - $iObjectId - entry id
+ *      - $iSenderId - rater user id
+ *      - $aExtra['rate'] - rate
+ *
+ */
+
+class BxDolScore extends BxDolObject
+{
+    protected $_aElementDefaults;
+
+    protected function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+        if(empty($this->_sSystem))
+            return;
+
+        $this->_oQuery = new BxDolScoreQuery($this);
+    }
+
+    /**
+     * get votes object instanse
+     * @param $sSys vote object name
+     * @param $iId associated content id, where vote is available
+     * @param $iInit perform initialization
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstance($sSys, $iId, $iInit = true, $oTemplate = false)
+    {
+        if(isset($GLOBALS['bxDolClasses']['BxDolScore!' . $sSys . $iId]))
+            return $GLOBALS['bxDolClasses']['BxDolScore!' . $sSys . $iId];
+
+        $aSystems = self::getSystems();
+        if(!isset($aSystems[$sSys]))
+            return null;
+
+        $sClassName = 'BxTemplScore';
+        if(!empty($aSystems[$sSys]['class_name'])) {
+            $sClassName = $aSystems[$sSys]['class_name'];
+            if(!empty($aSystems[$sSys]['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aSystems[$sSys]['class_file']);
+        }
+
+        $o = new $sClassName($sSys, $iId, $iInit, $oTemplate);
+        return ($GLOBALS['bxDolClasses']['BxDolScore!' . $sSys . $iId] = $o);
+    }
+
+    public static function &getSystems()
+    {
+        $sKey = 'bx_dol_cache_memory_score_systems';
+
+        if(!isset($GLOBALS[$sKey]))
+            $GLOBALS[$sKey] = BxDolDb::getInstance()->fromCache('sys_objects_score', 'getAllWithKey', '
+                SELECT
+                    `id` as `id`,
+                    `name` AS `name`,
+                    `table_main` AS `table_main`,
+                    `table_track` AS `table_track`,
+                    `post_timeout` AS `post_timeout`,
+                    `is_on` AS `is_on`,
+                    `trigger_table` AS `trigger_table`,
+                    `trigger_field_id` AS `trigger_field_id`,
+                    `trigger_field_author` AS `trigger_field_author`,
+                    `trigger_field_score` AS `trigger_field_score`,
+                    `trigger_field_cup` AS `trigger_field_cup`,
+                    `trigger_field_cdown` AS `trigger_field_cdown`,
+                    `class_name` AS `class_name`,
+                    `class_file` AS `class_file`
+                FROM `sys_objects_score`', 'name');
+
+        return $GLOBALS[$sKey];
+    }
+
+    public static function onAuthorDelete ($iAuthorId)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem)
+            self::getObjectInstance($sSystem, 0)->getQueryObject()->deleteAuthorEntries($iAuthorId);
+
+        return true;
+    }
+
+    public function isPerformed($iObjectId, $iAuthorId, $iAuthorIp = 0)
+    {
+        return parent::isPerformed($iObjectId, $iAuthorId) && !$this->_oQuery->isPostTimeoutEnded($iObjectId, $iAuthorIp);        
+    }
+
+	public function getObjectAuthorId($iObjectId = 0)
+    {
+    	if(empty($this->_aSystem['trigger_field_author']))
+    		return 0;
+
+        return $this->_oQuery->getObjectAuthorId($iObjectId ? $iObjectId : $this->getId());
+    }
+
+
+    /**
+     * Interface functions for outer usage
+     */
+    public function getStatCounterUp()
+    {
+        $aScore = $this->_oQuery->getScore($this->getId());
+        return $aScore['count_up'];
+    }
+
+    public function getStatCounterDown()
+    {
+        $aScore = $this->_oQuery->getScore($this->getId());
+        return $aScore['count_down'];
+    }
+
+    public function getStatScore()
+    {
+        $aScore = $this->_oQuery->getScore($this->getId());
+        return $aScore['score'];
+    }
+
+
+    /**
+     * Actions functions
+     */
+    public function actionVoteUp()
+    {
+        return $this->_doVote(BX_DOL_SCORE_DO_UP);
+    }
+
+    public function actionVoteDown()
+    {
+        return $this->_doVote(BX_DOL_SCORE_DO_DOWN);
+    }
+
+    public function actionGetVotedBy()
+    {
+        if (!$this->isEnabled())
+           return '';
+
+        $aParams = $this->_decodeElementParams(bx_process_input(bx_get('element_params')));
+
+        return $this->_getVotedBy($aParams);
+    }
+
+
+    /**
+     * Permissions functions
+     */
+    public function isAllowedVote($isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        return $this->checkAction('vote', $isPerformAction);
+    }
+
+    public function msgErrAllowedVote()
+    {
+        return $this->checkActionErrorMsg('vote');
+    }
+
+
+    /**
+     * Internal functions
+     */
+    protected function _doVote($sType)
+    {
+        if(!$this->isEnabled())
+            return echoJson(array('code' => 1, 'message' => _t('_sys_score_err_not_enabled')));
+
+        if(!$this->isAllowedVote(true))
+            return echoJson(array('code' => 2, 'message' => $this->msgErrAllowedVote()));
+
+        $aParams = $this->_decodeElementParams(bx_process_input(bx_get('element_params')));
+
+        $iObjectId = $this->getId();
+        $iObjectAuthorId = $this->getObjectAuthorId($iObjectId);
+        $iAuthorId = $this->_getAuthorId();
+        $iAuthorIp = $this->_getAuthorIp();
+
+        $bVoted = $this->isPerformed($iObjectId, $iAuthorId, $iAuthorIp);
+        if($bVoted)
+            return echoJson(array('code' => 3, 'message' => _t('_sys_score_err_duplicate_vote')));
+
+		$iId = $this->_oQuery->putVote($iObjectId, $iAuthorId, $iAuthorIp, $sType);
+        if($iId === false)
+            return echoJson(array('code' => 5));
+
+        $this->_trigger();
+
+        $sTypeUc = ucfirst($sType);
+        bx_alert($this->_sSystem, 'doVote' . $sTypeUc, $iObjectId, $iAuthorId, array('score_id' => $iId, 'score_author_id' => $iAuthorId, 'object_author_id' => $iObjectAuthorId));
+        bx_alert('score', 'do' . $sTypeUc, $iId, $iAuthorId, array('object_system' => $this->_sSystem, 'object_id' => $iObjectId, 'object_author_id' => $iObjectAuthorId));
+
+        $aScore = $this->_oQuery->getScore($iObjectId);
+        $iCup = (int)$aScore['count_up'];
+        $iCdown = (int)$aScore['count_down'];
+        echoJson(array(
+            'code' => 0,
+            'type' => $sType,
+            'score' => $aScore['score'],
+        	'scoref' => $iCup > 0 || $iCdown > 0 ? $this->_getLabelCounter($aScore['score']) : '',
+            'cup' => $iCup,
+        	'cdown' => $iCdown,
+            'counter' => $this->getCounter($aParams),
+        	'label_icon' => $this->_getIconDo($sType),
+        	'label_title' => _t($this->_getTitleDo($sType)),
+        	'disabled' => !$bVoted,
+        ));
+    }
+
+    /**
+     * Note. By default image based controls aren't used.
+     * Therefore it can be overwritten in custom template.
+     */
+    protected function _getImageDo($sType)
+    {
+        $sResult = '';
+
+        switch($sType) {
+            case BX_DOL_SCORE_DO_UP:
+                $sResult = '';
+                break;
+            case BX_DOL_SCORE_DO_DOWN:
+                $sResult = '';
+                break;
+        }
+
+    	return $sResult;
+    }
+
+    protected function _getIconDo($sType)
+    {
+        $sResult = '';
+
+        switch($sType) {
+            case BX_DOL_SCORE_DO_UP:
+                $sResult = 'arrow-up';
+                break;
+            case BX_DOL_SCORE_DO_DOWN:
+                $sResult = 'arrow-down';
+                break;
+        }
+
+    	return $sResult;
+    }
+
+    protected function _getTitleDo($sType)
+    {
+        $sResult = '';
+
+        switch($sType) {
+            case BX_DOL_SCORE_DO_UP:
+                $sResult = '_sys_score_do_up';
+                break;
+            case BX_DOL_SCORE_DO_DOWN:
+                $sResult = '_sys_score_do_down';
+                break;
+        }
+
+    	return $sResult;
+    }
+
+    protected function _getTitleDoBy()
+    {
+    	return '_sys_score_do_by';
+    }
+
+    protected function _encodeElementParams($aParams)
+    {
+        if(empty($aParams) || !is_array($aParams))
+            return '';
+
+        return urlencode(base64_encode(serialize($aParams)));
+    }
+
+    protected function _decodeElementParams($sParams, $bMergeWithDefaults = true)
+    {
+        $aParams = array();
+        if(!empty($sParams))
+            $aParams = unserialize(base64_decode(urldecode($sParams)));
+
+        if(empty($aParams) || !is_array($aParams))
+            $aParams = array();
+
+        if($bMergeWithDefaults)
+            $aParams = array_merge($this->_aElementDefaults, $aParams);
+
+        return $aParams;
+    }
+}
+
+/** @} */

+ 197 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/classes/BxDolView.php

@@ -0,0 +1,197 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_VIEW_OLD_VIEWS', 3 * 86400); ///< views older than this number of seconds will be deleted automatically
+
+define('BX_DOL_VIEW_USAGE_BLOCK', 'block');
+define('BX_DOL_VIEW_USAGE_INLINE', 'inline');
+define('BX_DOL_VIEW_USAGE_DEFAULT', BX_DOL_VIEW_USAGE_BLOCK);
+
+/**
+ * Track any object views automatically
+ *
+ * Add record to sys_object_view table to track object views,
+ * to record view just create this class instance with your object id,
+ * for example:
+ * @code
+ *  BxDolView::getObjectInstance('my_system', 25); // 25 - is object id
+ * @endcode
+ *
+ * Description of sys_object_view table fields:
+ * @code
+ *  `name` - system name, it is better to use unique module prefix here, lowercase and all spaces are underscored
+ *  `table_track` - table to track views
+ *  `period` - period in secs to update next views, default is 86400(1 day)
+ *  `is_on` - is the system activated
+ *  `trigger_table` - table where you need to update views field
+ *  `trigger_field_id` - table field id to unique determine object
+ *  `trigger_field_count` - table field where total view number is stored
+ *  `class_name` - your custom class name, if you overrride default class
+ *  `class_file` - your custom class path
+ * @endcode
+ *
+ * Structure of the track table is the following:
+ * @code
+ *  CREATE TABLE `my_views_track` (
+ *      `object_id` int(11) NOT NULL default '0', -- this field type must be exact as your object id type
+ *      `viewer_id` int(11) NOT NULL default '0', -- viewer profile id
+ *      `viewer_nip` int(11) unsigned NOT NULL default '0', -- viewer ip address to track guest views
+ *      `date` int(11) NOT NULL default '0', -- timestamp of last recorded view
+ *      KEY `id` (`object_id`,`viewer_id`,`viewer_nip`)
+ *  ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+ * @endcode
+ *
+ */
+class BxDolView extends BxDolObject
+{
+    protected function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+        if(empty($this->_sSystem))
+            return;
+
+        $this->_oQuery = new BxDolViewQuery($this);
+    }
+
+   /**
+     * get votes object instanse
+     * @param $sSys view object name
+     * @param $iId associated content id, where vote is available
+     * @param $iInit perform initialization
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstance($sSys, $iId, $iInit = true)
+    {
+        if(isset($GLOBALS['bxDolClasses']['BxDolView!' . $sSys . $iId]))
+            return $GLOBALS['bxDolClasses']['BxDolView!' . $sSys . $iId];
+
+        $aSystems = self::getSystems();
+        if(!isset($aSystems[$sSys]))
+            return null;
+
+        $sClassName = 'BxTemplView';
+        if(!empty($aSystems[$sSys]['class_name'])) {
+            $sClassName = $aSystems[$sSys]['class_name'];
+            if(!empty($aSystems[$sSys]['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aSystems[$sSys]['class_file']);
+        }
+
+        $o = new $sClassName($sSys, $iId, $iInit);
+        return ($GLOBALS['bxDolClasses']['BxDolView!' . $sSys . $iId] = $o);
+    }
+
+    public static function &getSystems()
+    {
+        $sKey = 'bx_dol_cache_memory_view_systems';
+
+        if(!isset($GLOBALS[$sKey]))
+            $GLOBALS[$sKey] = BxDolDb::getInstance()->fromCache('sys_objects_view', 'getAllWithKey', '
+                SELECT
+                    `id` as `id`,
+                    `name` AS `name`,
+                    `module` AS `module`,
+                    `table_track` AS `table_track`,
+                    `period` AS `period`,
+                    `is_on` AS `is_on`,
+                    `trigger_table` AS `trigger_table`,
+                    `trigger_field_id` AS `trigger_field_id`,
+                    `trigger_field_author` AS `trigger_field_author`,
+                    `trigger_field_count` AS `trigger_field_count`,
+                    `class_name` AS `class_name`,
+                    `class_file` AS `class_file`
+                FROM `sys_objects_view`', 'name');
+
+        return $GLOBALS[$sKey];
+    }
+
+    public static function onAuthorDelete ($iAuthorId)
+    {
+        $aSystems = self::getSystems();
+        foreach($aSystems as $sSystem => $aSystem)
+            self::getObjectInstance($sSystem, 0)->getQueryObject()->deleteAuthorEntries($iAuthorId);
+
+        return true;
+    }
+
+    public function actionGetViewedBy()
+    {
+        if (!$this->isEnabled())
+           return '';
+
+        return $this->_getViewedBy();
+    }
+
+    public function doView()
+    {
+        if(!$this->isEnabled())
+            return false;
+
+        $iObjectId = $this->getId();
+        $iAuthorId = $this->_getAuthorId();
+        $sAuthorIp = $this->_getAuthorIp();
+
+        if($this->_oQuery->doView($iObjectId, $iAuthorId, $sAuthorIp)) {
+            $this->_trigger();
+
+            $oZ = new BxDolAlerts($this->_sSystem, 'view', $iObjectId, $iAuthorId);
+            $oZ->alert();
+
+            return true;
+        }
+
+        return false;
+    }
+
+	/**
+     * Permissions functions
+     * 
+     * Note. The 'view' action is performed automatically and therefore the manual action is not allowed.
+     */
+    public function isAllowedView($isPerformAction = false)
+    {
+        return false;
+    }
+
+    public function isAllowedViewView($isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        return $this->checkAction('view_view', $isPerformAction);
+    }
+
+    public function isAllowedViewViewViewers($isPerformAction = false)
+    {
+        $oAcl = BxDolAcl::getInstance();
+        if(isAdmin() || $oAcl->isMemberLevelInSet(array(MEMBERSHIP_ID_MODERATOR, MEMBERSHIP_ID_ADMINISTRATOR)))
+            return true;
+
+        if(!empty($this->_aSystem['module']) && BxDolRequest::serviceExists($this->_aSystem['module'], 'act_as_profile') && BxDolService::call($this->_aSystem['module'], 'act_as_profile') === true)
+            $iObjectAuthorId = BxDolProfile::getInstanceByContentAndType($this->_iId, $this->_aSystem['module'])->id();
+        else
+            $iObjectAuthorId = $this->_oQuery->getObjectAuthorId($this->_iId);
+
+        return $iObjectAuthorId != 0 && $iObjectAuthorId == $this->_getAuthorId() && $this->checkAction('view_view_viewers_own', $isPerformAction);
+    }
+
+	/**
+     * Internal functions
+     */
+    protected function _getIconDo()
+    {
+    	return 'eye';
+    }
+
+    protected function _getTitleDo()
+    {
+    	return '_view_do_view';
+    }
+}
+
+/** @} */

+ 1722 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/utils.inc.php

@@ -0,0 +1,1722 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_LINK_CLASS', 'bx-link'); ///< class to add to every link in user content
+
+define('BX_DATA_TEXT', 1); ///< regular text data type
+define('BX_DATA_TEXT_MULTILINE', 2); ///< regular multiline text data type
+define('BX_DATA_INT', 3); ///< integer data type
+define('BX_DATA_FLOAT', 4); ///< float data type
+define('BX_DATA_CHECKBOX', 5); ///< checkbox data type, 'on' or empty value
+define('BX_DATA_HTML', 6); ///< HTML data type
+define('BX_DATA_DATE', 7); ///< date data type stored as yyyy-mm-dd
+define('BX_DATA_DATETIME', 12); ///< date/time data type stored as yyyy-mm-dd hh:mm:ss
+define('BX_DATA_DATE_TS', 8); ///< date data type stored as unixtimestamp
+define('BX_DATA_DATETIME_TS', 9); ///< date/time data type stored as unixtimestamp
+define('BX_DATA_DATE_TS_UTC', 10); ///< date data type stored as unixtimestamp from UTC time
+define('BX_DATA_DATETIME_TS_UTC', 11); ///< date/time data type stored as unixtimestamp from UTC time
+
+define('BX_SLASHES_AUTO', 0);
+define('BX_SLASHES_ADD', 1);
+define('BX_SLASHES_STRIP', 2);
+define('BX_SLASHES_NO_ACTION', 3);
+
+define('BX_ESCAPE_STR_AUTO', 0); ///< turn apostropes and quote signs into html special chars, for use in @see bx_js_string and @see bx_html_attribute
+define('BX_ESCAPE_STR_APOS', 1); ///< escape apostrophes only, for js strings enclosed in apostrophes, for use in @see bx_js_string and @see bx_html_attribute
+define('BX_ESCAPE_STR_QUOTE', 2); ///< escape quotes only, for js strings enclosed in quotes, for use in @see bx_js_string and @see bx_html_attribute
+
+define('BX_EMAIL_SYSTEM', 0); ///< system email without unsubscribe link, like forgot password or email verification
+define('BX_EMAIL_NOTIFY', 1); ///< notification message, with unsubscribe link
+define('BX_EMAIL_MASS', 2); ///< mass email, one mesage send to manu users, with unsubscribe link
+
+define('BX_MAINTENANCE_FILE', '.bx_maintenance'); ///< file name to use as mantenance mode indicator
+
+/**
+ * The following two functions are needed to convert title to uri and back.
+ * It usefull when titles are used in URLs, like in Categories and Tags.
+ */
+function title2uri($sValue)
+{
+    return str_replace(
+        array('&', '/', '\\', '"', '+'),
+        array('[and]', '[slash]', '[backslash]', '[quote]', '[plus]'),
+        $sValue
+    );
+}
+function uri2title($sValue)
+{
+    return str_replace(
+        array('[and]', '[slash]', '[backslash]', '[quote]', '[plus]'),
+        array('&', '/', '\\', '"', '+'),
+        $sValue
+    );
+}
+
+/*
+ * functions for limiting maximal text length
+ */
+function strmaxtextlen($sInput, $iMaxLen = 60, $sEllipsisSign = '&#8230;')
+{
+    $sTail = '';
+    $s = trim(strip_tags($sInput));
+    if (mb_strlen($s) > $iMaxLen) {
+        $s = mb_substr($s, 0, $iMaxLen);
+        $sTail = $sEllipsisSign;
+    }
+    return htmlspecialchars_adv($s) . $sTail;
+}
+
+function html2txt($content, $tags = "")
+{
+    while($content != strip_tags($content, $tags)) {
+        $content = strip_tags($content, $tags);
+    }
+
+    return $content;
+}
+
+/**
+ * Functions to process user input.
+ * DON'T use to process data before passing to SQL query - use db prepare instead @see BxDolDb::prepare.
+ * It is ok to use bx_process_input and then db prepare.
+ * @param $mixedData data to process
+ * @param $iDataType how to handle data, possible valies:
+ *      @code
+ *          BX_DATA_INT - integer value
+ *          BX_DATA_FLOAT - float values
+ *          BX_DATA_CHECKBOX - 'on' or empty string
+ *          BX_DATA_TEXT - text data, single line (default)
+ *          BX_DATA_TEXT_MULTILINE - text data, multiple lines
+ *          BX_DATA_HTML - HTML data
+ *          BX_DATA_DATE - date data type stored as yyyy-mm-dd
+ *          BX_DATA_DATETIME - date/time data type stored as yyyy-mm-dd hh:mm:ss
+ *          BX_DATA_DATE_TS' -  date data type stored as unixtimestamp
+ *          BX_DATA_DATETIME_TS - date/time data type stored as unixtimestamp
+ *          BX_DATA_DATE_TS_UTC - date data type stored as unixtimestamp from UTC time
+ *          BX_DATA_DATETIME_TS_UTC - date/time data type stored as unixtimestamp from UTC time
+ *      @endcode
+ * @param $mixedParams optional parameters to pass for validation
+ * @param $isCheckMagicQuotes deprecated
+ * @return the filtered data, or FALSE if the filter fails.
+ */
+function bx_process_input ($mixedData, $iDataType = BX_DATA_TEXT, $mixedParams = false, $isCheckMagicQuotes = true)
+{
+    if (is_array($mixedData)) {
+        foreach ($mixedData as $k => $v)
+            $mixedData[$k] = bx_process_input($v, $iDataType, $mixedParams);
+        return $mixedData;
+    }
+
+    if (get_magic_quotes_gpc() && $isCheckMagicQuotes)
+        $mixedData = stripslashes($mixedData);
+
+    switch ($iDataType) {
+    case BX_DATA_INT:
+        return filter_var(trim($mixedData), FILTER_VALIDATE_INT);
+    case BX_DATA_FLOAT:
+        return filter_var(trim($mixedData), FILTER_VALIDATE_FLOAT);
+    case BX_DATA_CHECKBOX:
+        return 'on' == trim($mixedData) ? 'on' : '';
+
+    case BX_DATA_DATE:
+        // maybe consider using strtotime
+        $mixedData = trim($mixedData);
+        if (!preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $mixedData))
+            return false;
+        list($iYear, $iMonth, $iDay) = explode('-', $mixedData); // 1985-10-28
+        $iDay   = intval($iDay);
+        $iMonth = intval($iMonth);
+        $iYear  = intval($iYear);
+        return sprintf("%04d-%02d-%02d", $iYear, $iMonth, $iDay);
+    case BX_DATA_DATETIME:
+        // maybe consider using strtotime
+        $mixedData = trim($mixedData);
+        if (!preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+):(\d+)#', $mixedData, $m) && !preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+)#', $mixedData, $m))
+            return bx_process_input ($mixedData, BX_DATA_DATE, $mixedParams, $isCheckMagicQuotes);
+        $iDay   = intval($m[3]);
+        $iMonth = intval($m[2]);
+        $iYear  = intval($m[1]);
+        $iH = intval($m[4]);
+        $iM = intval($m[5]);
+        $iS = isset($m[6]) ? intval($m[6]) : 0;
+        return sprintf("%04d-%02d-%02d %02d:%02d:%02d", $iYear, $iMonth, $iDay, $iH, $iM, $iS); // 1985-10-28 00:59:35
+    case BX_DATA_DATE_TS:
+    case BX_DATA_DATE_TS_UTC:
+        $mixedData = trim($mixedData);
+        if (!preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $mixedData))
+            return false;
+        list($iYear, $iMonth, $iDay) = explode('-', $mixedData);
+        $iDay   = intval($iDay);
+        $iMonth = intval($iMonth);
+        $iYear  = intval($iYear);
+        $sFunc = BX_DATA_DATE_TS_UTC == $iDataType ? 'gmmktime' : 'mktime';
+        $iRet = $sFunc (0, 0, 0, $iMonth, $iDay, $iYear);
+        return $iRet > 0 ? $iRet : false;
+    case BX_DATA_DATETIME_TS:
+    case BX_DATA_DATETIME_TS_UTC:
+        if (!preg_match('#(\d+)\-(\d+)\-(\d+)[\s]{1}(\d+):(\d+):(\d+)[\s]{1}([+\-\d:Z]+)#', $mixedData, $m) && !preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+):(\d+)#', $mixedData, $m) && !preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+)#', $mixedData, $m))
+            return bx_process_input ($mixedData, BX_DATA_DATETIME_TS == $iDataType ? BX_DATA_DATE_TS : BX_DATA_DATE_TS_UTC, $mixedParams, $isCheckMagicQuotes);
+        $iDay   = $m[3];
+        $iMonth = $m[2];
+        $iYear  = $m[1];
+        $iH = $m[4];
+        $iM = $m[5];
+        $iS = isset($m[6]) ? $m[6] : 0;        
+        $iTimezoneOffset = 0;
+        if (isset($m[7])) {
+            $oTz = new DateTimeZone($m[7]);
+            $oUtc = new DateTime(str_replace($m[7], '', $mixedData), new DateTimeZone('UTC'));
+            if ($oTz && $oUtc)
+                $iTimezoneOffset = $oTz->getOffset($oUtc);
+        }
+        $sFunc = BX_DATA_DATETIME_TS_UTC == $iDataType ? 'gmmktime' : 'mktime';
+        $iRet = $sFunc ($iH, $iM, $iS, $iMonth, $iDay, $iYear) - $iTimezoneOffset;
+
+        return $iRet > 0 ? $iRet : false;
+
+    case BX_DATA_HTML:
+        return clear_xss($mixedData);
+    case BX_DATA_TEXT_MULTILINE:
+        return nl2br(htmlspecialchars_adv($mixedData));
+    case BX_DATA_TEXT:
+    default:
+        return $mixedData;
+    }
+}
+
+/*
+ * Functions to process user output.
+ * Always use this function before output data which was entered by user before.
+ * @param $mixedData string to process
+ * @param $iDataType how to handle data, possible valies the same as in bx_process_input function, see bx_process_input.
+ * @param $mixedParams optional parameters to pass for validation
+ * @return the filtered data, or FALSE if the filter fails.
+ */
+function bx_process_output ($mixedData, $iDataType = BX_DATA_TEXT, $mixedParams = false)
+{
+    if (is_array($mixedData)) {
+        foreach ($mixedData as $k => $v)
+            $mixedData[$k] = bx_process_output($v, $iDataType, $mixedParams);
+        return $mixedData;
+    }
+
+    switch ($iDataType) {
+    case BX_DATA_INT:
+        return filter_var($mixedData, FILTER_VALIDATE_INT);
+    case BX_DATA_FLOAT:
+        return filter_var($mixedData, FILTER_VALIDATE_FLOAT);
+    case BX_DATA_CHECKBOX:
+        return 'on' == trim($mixedData) ? 'on' : '';
+
+    case BX_DATA_DATE:
+    case BX_DATA_DATETIME:
+        return $mixedData;
+    case BX_DATA_DATE_TS:
+        return empty($mixedData) ? '' : date("Y-m-d", (int)$mixedData);
+    case BX_DATA_DATE_TS_UTC:
+        return empty($mixedData) ? '' : gmdate("Y-m-d", (int)$mixedData);
+    case BX_DATA_DATETIME_TS:
+        return empty($mixedData) ? '' : date("Y-m-d H:i", (int)$mixedData);
+    case BX_DATA_DATETIME_TS_UTC:
+        return empty($mixedData) ? '' : gmdate("Y-m-d H:i", (int)$mixedData);
+
+    case BX_DATA_HTML:
+        return bx_linkify_html($mixedData, 'class="' . BX_DOL_LINK_CLASS . '"');
+    case BX_DATA_TEXT_MULTILINE:
+        return $mixedData;
+    case BX_DATA_TEXT:
+    default:
+        return htmlspecialchars_adv($mixedData);
+    }
+}
+
+/*
+ * This function apply bx_process_input and then bx_process_output.
+ * Use this function to output data immediately after receiving, without saving to database.
+ * Patams are the same as bx_process_input function - @see bx_process_input
+ */
+function bx_process_pass ($mixedData, $iDataType = BX_DATA_TEXT, $mixedParams = false, $isCheckMagicQuotes = true)
+{
+    return bx_process_output(bx_process_input ($mixedData, $iDataType, $mixedParams, $isCheckMagicQuotes), $iDataType, $mixedParams);
+}
+
+/**
+ * DEPRECATED
+ * use bx_process_input + bx_process_output instead
+ * --------
+ * function for processing pass data
+ *
+ * This function cleans the GET/POST/COOKIE data if magic_quotes_gpc() is on
+ * for data which should be outputed immediately after submit
+ */
+/*
+function process_pass_data( $text, $strip_tags = 0 )
+{
+    if ( $strip_tags )
+        $text = strip_tags($text);
+
+    if ( !get_magic_quotes_gpc() )
+        return $text;
+    else
+        return stripslashes($text);
+}
+*/
+
+/*
+ * function for output data from database into html
+ */
+function htmlspecialchars_adv( $string )
+{
+    return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false);
+}
+
+/**
+ * Send mail to user by parsing email template
+ */
+function sendMailTemplate($sTemplateName, $iAccountId = 0, $iProfileId = 0, $aReplaceVars = array(), $iEmailType = BX_EMAIL_NOTIFY, $bAddToQueue = false)
+{
+    $oProfile = BxDolProfile::getInstance($iProfileId);
+
+    $oAccount = $iAccountId ? BxDolAccount::getInstance($iAccountId) : ($oProfile ? $oProfile->getAccountObject() : null);
+
+    $oEmailTemplates = BxDolEmailTemplates::getInstance();
+
+    if (!$oAccount || !$oProfile || !$oEmailTemplates)
+        return false;
+
+    $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, $aReplaceVars, $iAccountId, (int)$iProfileId);
+    if (!$aTemplate)
+        return false;
+
+    return sendMail($oAccount->getEmail(), $aTemplate['Subject'], $aTemplate['Body'], 0, array(), $iEmailType, 'html', false, array(), $bAddToQueue);
+}
+
+/**
+ * Send system email 
+ */
+function sendMailTemplateSystem($sTemplateName, $aReplaceVars = array(), $iEmailType = BX_EMAIL_SYSTEM, $bAddToQueue = false)
+{
+    $oEmailTemplates = BxDolEmailTemplates::getInstance();
+
+    if (!$oEmailTemplates)
+        return false;
+
+    $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, $aReplaceVars);
+    if (!$aTemplate)
+        return false;
+
+    return sendMail(getParam('site_email'), $aTemplate['Subject'], $aTemplate['Body'], 0, array(), $iEmailType, 'html', false, array(), $bAddToQueue);
+}
+/**
+ * Send email function
+ *
+ * @param $sRecipientEmail - Email where email should be send
+ * @param $sMailSubject - subject of the message
+ * @param $sMailBody - Body of the message
+ * @param $iRecipientID - ID of recipient profile
+ * @param $aPlus - Array of additional information
+ * @param $iEmailType - email message type: BX_EMAIL_SYSTEM, BX_EMAIL_NOTIFY or BX_EMAIL_MASS
+ * @param $sEmailFlag - use 'html' for HTML email message
+ * @param $isDisableAlert - disable alert
+ * @param $aCustomHeaders - custom email headers
+ * @param $bAddToQueue - add message to email queue
+ * @return true if message was send or false otherwise
+ */
+function sendMail($sRecipientEmail, $sMailSubject, $sMailBody, $iRecipientID = 0, $aPlus = array(), $iEmailType = BX_EMAIL_NOTIFY, $sEmailFlag = 'html', $isDisableAlert = false, $aCustomHeaders = array(), $bAddToQueue = false)
+{
+    // make sure that recipient's email is valid and message isn't empty
+    if (!$sMailBody || !$sRecipientEmail || preg_match('/\(2\)$/', $sRecipientEmail))
+        return false;
+
+    // get recipient account
+    $oAccount = !$isDisableAlert ? BxDolAccount::getInstance($sRecipientEmail) : null;
+    $aAccountInfo = $oAccount ? $oAccount->getInfo() : false;
+
+    // don't send bulk emails if user didn't subscribed to site news or email is unconfirmed
+    if ($aAccountInfo && BX_EMAIL_MASS == $iEmailType && (!$aAccountInfo['email_confirmed'] || !$aAccountInfo['receive_news']))
+        return false;
+
+    // don't send email notifications if user didn't subscribed to notifications or email is unconfirmed
+    if ($aAccountInfo && BX_EMAIL_NOTIFY == $iEmailType && (!$aAccountInfo['email_confirmed'] || !$aAccountInfo['receive_updates']))
+        return false;
+
+    if($bAddToQueue && BxDolQueueEmail::getInstance()->add($sRecipientEmail, $sMailSubject, $sMailBody, $iRecipientID, $aPlus, $iEmailType, $sEmailFlag, $isDisableAlert, $aCustomHeaders))
+        return true;
+
+    // if profile id is provided - get profile's info
+    $aRecipientInfo = false;
+    if ($iRecipientID) {
+        $oProfile = BxDolProfile::getInstance($iRecipientID);
+        if ($oProfile)
+            $aRecipientInfo = $oProfile->getInfo();
+    }
+
+    // get site vars
+    $sEmailNotify = !$isDisableAlert ? getParam('site_email_notify') : $sRecipientEmail;
+    $sSiteTitle = !$isDisableAlert ? getParam('site_title') : 'UNA ' . BX_DOL_VERSION;
+
+    // add unsubscribe link
+    if (!$isDisableAlert && empty($aPlus['unsubscribe'])) {
+        $aPlus['unsubscribe'] = '';
+        if ($oAccount && (BX_EMAIL_MASS == $iEmailType || BX_EMAIL_NOTIFY == $iEmailType))
+            $aPlus['unsubscribe'] = ($sLink = $oAccount->getUnsubscribeLink($iEmailType)) ? '<a href="' . BX_DOL_URL_ROOT . $sLink . '">' . _t('_sys_et_txt_unsubscribe') . '</a>' : '';
+    }
+
+    // parse template
+    if ($aPlus || $iRecipientID || $oAccount) {
+        if(!is_array($aPlus))
+            $aPlus = array();
+        $oEmailTemplates = BxDolEmailTemplates::getInstance();
+        $sMailSubject = $oEmailTemplates->parseContent($sMailSubject, $aPlus, !$iRecipientID && $oAccount ? $oAccount->id() : 0, $iRecipientID);
+        $sMailBody = $oEmailTemplates->parseContent($sMailBody, $aPlus, !$iRecipientID && $oAccount ? $oAccount->id() : 0, $iRecipientID);
+    }
+
+    // email message headers
+    $sMailHeader = '';
+    foreach ($aCustomHeaders as $sHeaderName => $sHeaderValue)
+        $sMailHeader = "$sHeaderName: $sHeaderValue\r\n" . $sMailHeader;
+
+    if (!isset($aCustomHeaders['From']))
+        $sMailHeader = "From: =?UTF-8?B?" . base64_encode( $sSiteTitle ) . "?= <{$sEmailNotify}>";
+
+    if (!isset($aCustomHeaders['MIME-Version']))
+        $sMailHeader = "MIME-Version: 1.0\r\n" . $sMailHeader;
+
+    $sMailParameters = isset($aCustomHeaders['Sender']) ? "-f{$aCustomHeaders['Sender']}" : "-f{$sEmailNotify}";
+
+    $sMailSubject = !isset($aCustomHeaders['Subject']) ? '=?UTF-8?B?' . base64_encode( $sMailSubject ) . '?=' : $aCustomHeaders['Subject'];
+
+    // build data for alert handler
+    $bResult = null;
+    $aAlert = array(
+        'email' => $sRecipientEmail,
+        'subject' => $sMailSubject,
+        'body' => $sMailBody,
+        'header' => $sMailHeader,
+        'params' => $sMailParameters,
+        'recipient' => $aRecipientInfo,
+        'html' => 'html' == $sEmailFlag ? true : false,
+        'custom_headers' => $aCustomHeaders,
+        'override_result' => &$bResult,
+    );
+
+    // system alert
+    if (!$isDisableAlert) {
+        bx_alert('system', 'before_send_mail', (isset($aRecipientInfo['ID']) ? $aRecipientInfo['ID'] : 0), '', $aAlert);
+        if ($bResult !== null)
+            return $bResult;
+        unset($aAlert['override_result']);
+    }
+
+    // prepare HTML/Plain message
+    if($sEmailFlag == 'html')
+        $sMailHeader = "Content-type: text/html; charset=UTF-8\r\n" . $sMailHeader;
+    else {
+        $sMailHeader = "Content-type: text/plain; charset=UTF-8\r\n" . $sMailHeader;
+        $sMailBody = html2txt($sMailBody);
+    }
+
+    // send mail or put it into queue
+    $bResult = mail($sRecipientEmail, $sMailSubject, $sMailBody, $sMailHeader, $sMailParameters);
+
+    // system alert
+    if (!$isDisableAlert)
+        bx_alert('system', 'send_mail', (isset($aRecipientInfo['ID']) ? $aRecipientInfo['ID'] : 0), '', $aAlert);
+
+    return $bResult;
+}
+
+/*
+ * Getting an array with Templates' Names
+ */
+function get_templates_array($bEnabledOnly = true, $bShortInfo = true)
+{
+    $oDb = BxDolDb::getInstance();
+
+    $sWhereAddon = $bEnabledOnly ? " AND `enabled`='1'" : "";
+
+    if($bShortInfo)
+        return $oDb->getPairs("SELECT `uri`, `title` FROM `sys_modules` WHERE 1 AND `type`='" . BX_DOL_MODULE_TYPE_TEMPLATE . "'" . $sWhereAddon, "uri", "title");
+    else
+        return $oDb->getAllWithKey("SELECT * FROM `sys_modules` WHERE 1 AND `type`='" . BX_DOL_MODULE_TYPE_TEMPLATE . "'" . $sWhereAddon, "uri");
+}
+
+function extFileExists( $sFileSrc )
+{
+    return (file_exists( $sFileSrc ) && is_file( $sFileSrc )) ? true : false;
+}
+
+function getVisitorIP()
+{
+    $ip = "0.0.0.0";
+    if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+    } elseif (isset( $_SERVER['HTTP_CLIENT_IP']) && !empty($_SERVER['HTTP_CLIENT_IP'])) {
+        $ip = explode(".",$_SERVER['HTTP_CLIENT_IP']);
+        $ip = $ip[3].".".$ip[2].".".$ip[1].".".$ip[0];
+    } elseif (!isset( $_SERVER['HTTP_X_FORWARDED_FOR']) || empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+        if (!isset( $_SERVER['HTTP_CLIENT_IP']) && empty($_SERVER['HTTP_CLIENT_IP']) && isset($_SERVER['REMOTE_ADDR']))
+            $ip = $_SERVER['REMOTE_ADDR'];
+    }
+    return $ip;
+}
+
+function genFlag($sLang = '', $oTemplate = null)
+{
+    if (!$oTemplate)
+        $oTemplate = BxDolTemplate::getInstance();
+    $oTemplate->addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'flag-icon-css/css/|flag-icon.min.css');
+    $sFlag = BxDolLanguages::getInstance()->getLangFlag($sLang);
+    return '<span title="' . $sFlag . '" class="flag-icon flag-icon-' . $sFlag . '"></span>';
+}
+
+// print debug information ( e.g. arrays )
+function echoDbg( $what, $desc = '' )
+{
+    if ( $desc )
+        echo "<b>$desc:</b> ";
+    echo "<pre>";
+        print_r( $what );
+    echo "</pre>\n";
+}
+
+function echoDbgLog($mWhat, $sDesc = '', $sFileName = 'debug.log')
+{
+    $sCont =
+        '--- ' . date('r') . ' (' . BX_DOL_START . ") ---\n" .
+        $sDesc . "\n" .
+        print_r($mWhat, true) . "\n\n\n";
+
+    $rFile = fopen(BX_DIRECTORY_PATH_LOGS . $sFileName, 'a');
+    fwrite($rFile, $sCont);
+    fclose($rFile);
+}
+
+function dbgTiming($sStartMicrotime)
+{
+    $i1 = explode(' ', microtime ());
+    $i2 = explode(' ', $sStartMicrotime);
+    $iTime = ($i1[0]+$i1[1]) - ($i2[0]+$i2[1]);
+    return round($iTime, 3) . ' sec';
+}
+
+function echoJson($a)
+{
+	header('Content-type: text/html; charset=utf-8');
+
+	echo json_encode($a);
+}
+
+function clear_xss($val)
+{
+    // HTML Purifier plugin
+    global $oHtmlPurifier;
+    if (!isset($oHtmlPurifier) && !$GLOBALS['logged']['admin']) {
+
+        require_once( BX_DIRECTORY_PATH_PLUGINS . 'htmlpurifier/HTMLPurifier.standalone.php' );
+
+        HTMLPurifier_Bootstrap::registerAutoload();
+
+        $oConfig = HTMLPurifier_Config::createDefault();
+
+        $oConfig->set('Cache.SerializerPath', rtrim(BX_DIRECTORY_PATH_CACHE, '/'));
+        $oConfig->set('Cache.SerializerPermissions', BX_DOL_DIR_RIGHTS);
+
+        $oConfig->set('HTML.SafeObject', 'true');
+        $oConfig->set('Output.FlashCompat', 'true');
+        $oConfig->set('HTML.FlashAllowFullScreen', 'true');
+        $oConfig->set('Attr.AllowedFrameTargets', array('_blank', '_self', '_parent', '_top'));
+
+        if (getParam('sys_add_nofollow')) {
+            $sHost = parse_url(BX_DOL_URL_ROOT, PHP_URL_HOST);
+            $oConfig->set('URI.Host', $sHost);
+            $oConfig->set('HTML.Nofollow', 'true');
+        }
+
+        $oConfig->set('Filter.Custom', array (new HTMLPurifier_Filter_YouTube(), new HTMLPurifier_Filter_YoutubeIframe(), new HTMLPurifier_Filter_AddBxLinksClass()));
+
+	    $oConfig->set('HTML.DefinitionID', 'html5-definitions');
+		$oConfig->set('HTML.DefinitionRev', 1);
+		if ($def = $oConfig->maybeGetRawHTMLDefinition()) {
+		    $def->addElement('section', 'Block', 'Flow', 'Common');
+		    $def->addElement('nav',     'Block', 'Flow', 'Common');
+		    $def->addElement('article', 'Block', 'Flow', 'Common');
+		    $def->addElement('aside',   'Block', 'Flow', 'Common');
+		    $def->addElement('header',  'Block', 'Flow', 'Common');
+		    $def->addElement('footer',  'Block', 'Flow', 'Common');
+		    $def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', array(
+		        'src' => 'URI',
+		        'type' => 'Text',
+		        'width' => 'Length',
+		        'height' => 'Length',
+		        'poster' => 'URI',
+		        'preload' => 'Enum#auto,metadata,none',
+		        'controls' => 'Bool',
+		    ));
+		    $def->addElement('source', 'Block', 'Flow', 'Common', array(
+		        'src' => 'URI',
+		        'type' => 'Text',
+            ));
+            $def->addAttribute('a', 'data-profile-id', 'Number');
+		}
+
+        $oHtmlPurifier = new HTMLPurifier($oConfig);
+    }
+
+    if (!$GLOBALS['logged']['admin'])
+        $val = $oHtmlPurifier->purify($val);
+
+    bx_alert('system', 'clear_xss', 0, 0, array('oHtmlPurifier' => $oHtmlPurifier, 'return_data' => &$val));
+
+    return $val;
+}
+
+//--------------------------------------- friendly permalinks --------------------------------------//
+//------------------------------------------- main functions ---------------------------------------//
+function uriGenerate ($s, $sTable, $sField, $sEmpty = '-')
+{
+    $s = uriFilter($s, $sEmpty);
+    if(uriCheckUniq($s, $sTable, $sField))
+        return $s;
+
+    // cut off redundant part
+    if(get_mb_len($s) > 240)
+        $s = get_mb_substr($s, 0, 240);
+
+    // try to add date
+    $s .= '-' . date('Y-m-d');
+    if(uriCheckUniq($s, $sTable, $sField))
+        return $s;
+
+    // try to add number
+    for($i = 0 ; $i < 999 ; ++$i)
+        if(uriCheckUniq($s . '-' . $i, $sTable, $sField))
+            return ($s . '-' . $i);
+
+    return rand(0, 999999999);
+}
+
+function uriFilter ($s, $sEmpty = '-')
+{
+    if (BxTemplConfig::getInstance()->bAllowUnicodeInPreg)
+        $s = get_mb_replace ('/[^\pL^\pN]+/u', '-', $s); // unicode characters
+    else
+        $s = get_mb_replace ('/([^\d^\w]+)/u', '-', $s); // latin characters only
+
+    $s = get_mb_replace ('/([-^]+)/', '-', $s);
+    $s = get_mb_replace ('/([-]+)$/', '', $s); // remove trailing dash
+    if (!$s) $s = $sEmpty;
+    return $s;
+}
+
+function uriCheckUniq ($s, $sTable, $sField)
+{
+    $oDb = BxDolDb::getInstance();
+
+    $sSql = $oDb->prepare("SELECT 1 FROM `$sTable` WHERE `$sField`=? LIMIT 1", $s);
+    return !$oDb->query($sSql);
+}
+
+function get_mb_replace ($sPattern, $sReplace, $s)
+{
+    return preg_replace ($sPattern, $sReplace, $s);
+}
+
+function get_mb_len ($s)
+{
+    return (function_exists('mb_strlen')) ? mb_strlen($s) : strlen($s);
+}
+
+function get_mb_substr ($s, $iStart, $iLen)
+{
+    return (function_exists('mb_substr')) ? mb_substr ($s, $iStart, $iLen) : substr ($s, $iStart, $iLen);
+}
+
+function bx_mb_substr_replace($s, $sReplace, $iPosStart, $iLength)
+{
+    return mb_substr($s, 0, $iPosStart) . $sReplace . mb_substr($s, $iPosStart + $iLength);
+}
+
+function bx_mb_strpos ($s, $sReplacement, $iStart = 0)
+{
+    return mb_strpos($s, $sReplacement, $iStart);
+}
+
+/**
+ * Import class file, it automatically detects class path by its prefix or module array/name
+ *
+ * @param $sClassName - full class name or class postfix(withoit prefix) in the case of module class
+ * @param $mixedModule - module array or module name in the case of module class
+ */
+function bx_import($sClassName, $mixedModule = array())
+{
+    if (class_exists($sClassName, false))
+        return;
+
+    $aModule = false;
+    if ($mixedModule) {
+        if (is_array($mixedModule)) {
+            $aModule = $mixedModule;
+        } elseif (is_string($mixedModule)) {
+            $o = BxDolModule::getInstance($mixedModule);
+            $aModule = $o->_aModule;
+        } elseif (is_bool($mixedModule) && true === $mixedModule) {
+            $aModule = $GLOBALS['aModule'];
+        }
+    }
+
+    if ($aModule) {
+        if (class_exists($aModule['class_prefix'] . $sClassName, false))
+            return;
+        require_once (BX_DIRECTORY_PATH_MODULES . $aModule['path'] . 'classes/' . $aModule['class_prefix'] . $sClassName . '.php');
+        return;
+    }
+
+    if (0 === strncmp($sClassName, 'BxDol', 5)) {
+        if (0 === strncmp($sClassName, 'BxDolStudio', 11))
+            require_once(BX_DOL_DIR_STUDIO_CLASSES . $sClassName . '.php');
+        else
+            require_once(BX_DIRECTORY_PATH_CLASSES . $sClassName . '.php');
+        return;
+    }
+
+    if (0 === strncmp($sClassName, 'BxBase', 6)) {
+        if (0 === strncmp($sClassName, 'BxBaseMod', 9)) {
+            $aMatches = array();
+            if (preg_match('/BxBaseMod([A-Z][a-z]+)/', $sClassName, $aMatches)) {
+                require_once(BX_DIRECTORY_PATH_MODULES . 'base/' . strtolower($aMatches[1]) . '/classes/' . $sClassName . '.php');
+                return;
+            }
+        } if (0 === strncmp($sClassName, 'BxBaseStudio', 12)) {
+            require_once(BX_DOL_DIR_STUDIO_BASE . 'scripts/' . $sClassName . '.php');
+            return;
+        } else {
+            require_once(BX_DIRECTORY_PATH_BASE . 'scripts/' . $sClassName . '.php');
+            return;
+        }
+    }
+
+    if (0 === strncmp($sClassName, 'BxTempl', 7)) {
+        if(0 === strncmp($sClassName, 'BxTemplStudio', 13)) {
+            $sPath = BX_DIRECTORY_PATH_MODULES . BxDolStudioTemplate::getInstance()->getPath() . 'data/template/studio/scripts/' . $sClassName . '.php';
+        } else {
+            $sPath = BX_DIRECTORY_PATH_MODULES . BxDolTemplate::getInstance()->getPath() . 'data/template/system/scripts/' . $sClassName . '.php';
+        }
+
+        if (file_exists($sPath)) {
+            require_once($sPath);
+            return;
+        }
+
+        trigger_error ("bx_import fatal error: class (" . $sClassName . ") wasn't found", E_USER_ERROR);
+    }
+}
+
+/**
+ * used in spl_autoload_register() function, so no need to call bx_import for system classes
+ */
+function bx_autoload($sClassName)
+{
+    if (0 === strncmp($sClassName, 'BxDol', 5) || 0 === strncmp($sClassName, 'BxBase', 6) || 0 === strncmp($sClassName, 'BxTempl', 7))
+        bx_import($sClassName);
+}
+
+/**
+ * Gets an instance of class pathing necessary parameters if it's necessary.
+ *
+ * @param string $sClassName class name.
+ * @param array $aParams an array of parameters to be pathed to the constructor of the class.
+ * @param array $mixedModule an array with module description. Is used when the requested class is located in some module.
+ * @return unknown
+ */
+function bx_instance($sClassName, $aParams = array(), $mixedModule = array())
+{
+    if(isset($GLOBALS['bxDolClasses'][$sClassName]))
+        return $GLOBALS['bxDolClasses'][$sClassName];
+
+    if ($mixedModule) {
+        if (!is_array($mixedModule)) {
+            $o = BxDolModule::getInstance($mixedModule);
+            $mixedModule = $o->_aModule;
+        }
+        $sClassName = bx_ltrim_str($sClassName, $mixedModule['class_prefix']);
+        bx_import($sClassName, $mixedModule);
+        $sClassName = $mixedModule['class_prefix'] . $sClassName;
+    }
+
+    $oClass = new ReflectionClass($sClassName);
+
+    $GLOBALS['bxDolClasses'][$sClassName] = empty($aParams) ? $oClass->newInstance() : $oClass->newInstanceArgs($aParams);
+
+    return $GLOBALS['bxDolClasses'][$sClassName];
+}
+
+
+/**
+ * Escapes string/array ready to pass to js script with filtered symbols like ', " etc
+ *
+ * @param $mixedInput - string/array which should be filtered
+ * @param $iQuoteType - string escaping method: BX_ESCAPE_STR_AUTO(default), BX_ESCAPE_STR_APOS or BX_ESCAPE_STR_QUOTE
+ * @return converted string / array
+ */
+function bx_js_string ($mixedInput, $iQuoteType = BX_ESCAPE_STR_AUTO)
+{
+    $aUnits = array(
+        "\n" => "\\n",
+        "\r" => "",
+    );
+    if (BX_ESCAPE_STR_APOS == $iQuoteType) {
+        $aUnits["'"] = "\\'";
+        $aUnits['<script'] = "<scr' + 'ipt";
+        $aUnits['</script>'] = "</scr' + 'ipt>";
+    } elseif (BX_ESCAPE_STR_QUOTE == $iQuoteType) {
+        $aUnits['"'] = '\\"';
+        $aUnits['<script'] = '<scr" + "ipt';
+        $aUnits['</script>'] = '</scr" + "ipt>';
+    } else {
+        $aUnits['"'] = '&quot;';
+        $aUnits["'"] = '&apos;';
+        $aUnits["<script>"] = '&lt;script&gt;';
+        $aUnits["</script>"] = '&lt;/script&gt;';
+    }
+    return str_replace(array_keys($aUnits), array_values($aUnits), $mixedInput);
+}
+
+/**
+ * Return input string/array ready to pass to html attribute with filtered symbols like ', " etc
+ *
+ * @param $mixedInput - string/array which should be filtered
+ * @param $iQuoteType - string escaping BX_ESCAPE_STR_AUTO, BX_ESCAPE_STR_APOS or BX_ESCAPE_STR_QUOTE
+ * @return converted string / array
+ */
+function bx_html_attribute ($mixedInput, $iQuoteType = BX_ESCAPE_STR_AUTO)
+{
+    $aUnits = array ();
+    if (BX_ESCAPE_STR_APOS == $iQuoteType)
+        $aUnits["'"] = "\\'";
+    elseif (BX_ESCAPE_STR_QUOTE == $iQuoteType)
+        $aUnits['"'] = '&quot;';
+    else
+        $aUnits = array("\"" => "&quot;", "'" => "&apos;");
+
+    return str_replace(array_keys($aUnits), array_values($aUnits), $mixedInput);
+}
+
+/**
+ * Escapes string/array ready to pass to php script with filtered symbols like ', " etc
+ *
+ * @param mixed $mixedInput - string/array which should be filtered
+ * @return converted string / array
+ */
+function bx_php_string_apos ($mixedInput)
+{
+    return str_replace("'", "\\'", $mixedInput);
+}
+function bx_php_string_quot ($mixedInput)
+{
+    return str_replace('"', '\\"', $mixedInput);
+}
+
+/**
+ * Gets file contents by URL.
+ *
+ * @param string $sFileUrl - file URL to be read.
+ * @param array $aParams - an array of parameters to be pathed with URL.
+ * @param string $sMethod - post or get.
+ * @param array $aHeaders - custom headers.
+ * @param string $sHttpCode - HTTP code to return
+ * @param array $aBasicAuth - array with 'user' and 'password' for Basic HTTP Auth
+ * @return string the file's contents.
+ */
+function bx_file_get_contents($sFileUrl, $aParams = array(), $sMethod = 'get', $aHeaders = array(), &$sHttpCode = null, $aBasicAuth = array(), $iTimeout = 0, $aCustomCurlParams = array())
+{
+    if ('post' != $sMethod)
+    	$sFileUrl = bx_append_url_params($sFileUrl, $aParams);
+
+    $sResult = '';
+    if(function_exists('curl_init')) {
+        $rConnect = curl_init();
+
+        curl_setopt($rConnect, CURLOPT_USERAGENT, 'UNA ' . BX_DOL_VERSION);
+        curl_setopt($rConnect, CURLOPT_TIMEOUT, 10);
+        curl_setopt($rConnect, CURLOPT_URL, $sFileUrl);
+        curl_setopt($rConnect, CURLOPT_HEADER, NULL === $sHttpCode ? false : true);
+        curl_setopt($rConnect, CURLOPT_RETURNTRANSFER, 1);
+
+        if (0 !== $iTimeout) {
+            curl_setopt($rConnect, CURLOPT_CONNECTTIMEOUT, $iTimeout);
+            curl_setopt($rConnect, CURLOPT_TIMEOUT, $iTimeout);
+        }
+
+        if (!ini_get('open_basedir'))
+            curl_setopt($rConnect, CURLOPT_FOLLOWLOCATION, 1);
+
+        if ($aHeaders)
+            curl_setopt($rConnect, CURLOPT_HTTPHEADER, $aHeaders);
+
+        if ($aBasicAuth)
+            curl_setopt($rConnect, CURLOPT_USERPWD, $aBasicAuth['user'] . ':' . $aBasicAuth['password']);
+
+        if ('post' == $sMethod) {
+            curl_setopt($rConnect, CURLOPT_POST, true);
+            curl_setopt($rConnect, CURLOPT_POSTFIELDS, http_build_query($aParams));
+        }
+
+        $sAllCookies = '';
+        foreach($_COOKIE as $sKey=>$sValue){
+            $sAllCookies .= $sKey . '=' . $sValue . ';';
+        }
+        curl_setopt($rConnect, CURLOPT_COOKIE, $sAllCookies);
+
+        if ($aCustomCurlParams)
+            foreach ($aCustomCurlParams as $sName => $mixedValue)
+                curl_setopt($rConnect, constant($sName), $mixedValue);
+
+        $sResult = curl_exec($rConnect);
+
+        if (curl_errno($rConnect) == 60) { // CURLE_SSL_CACERT
+            curl_setopt($rConnect, CURLOPT_CAINFO, BX_DIRECTORY_PATH_PLUGINS . 'curl/cacert.pem');
+            $sResult = curl_exec($rConnect);
+        }
+
+        if (NULL !== $sHttpCode)
+            $sHttpCode = curl_getinfo($rConnect, CURLINFO_HTTP_CODE);
+
+        curl_close($rConnect);
+    }
+    else {
+
+        $iSaveTimeout = false;
+        if (0 !== $iTimeout) {
+            $iSaveTimeout = ini_get('default_socket_timeout');
+            ini_set('default_socket_timeout', $iTimeout);
+        }
+
+        $sResult = @file_get_contents($sFileUrl);
+
+        if (0 !== $iTimeout && false !== $iSaveTimeout) {
+            ini_set('default_socket_timeout', $iSaveTimeout);
+        }
+    }
+
+    return $sResult;
+}
+
+function bx_get_site_info($sSourceUrl, $aProcessAdditionalTags = array())
+{
+    $aResult = array();
+    $sContent = bx_file_get_contents($sSourceUrl);
+
+    if ($sContent) {
+        $sCharset = '';
+        preg_match("/<meta.+charset=([A-Za-z0-9-]+).+>/i", $sContent, $aMatch);
+        if (isset($aMatch[1]))
+            $sCharset = $aMatch[1];
+
+        $sContent = preg_replace("/<script[^>]*>(.*?)<\/script>/i", '', $sContent);
+        $sContent = preg_replace("/<style[^>]*>(.*?)<\/style>/i", '', $sContent);
+        if (preg_match("/<title[^>]*>(.*?)<\/title>/i", $sContent, $aMatch))
+            $aResult['title'] = strip_tags($aMatch[1]);
+        else
+            $aResult['title'] = parse_url($sSourceUrl, PHP_URL_HOST);
+
+        $aResult['description'] = bx_parse_html_tag($sContent, 'meta', 'name', 'description', 'content', $sCharset);
+        $aResult['keywords'] = bx_parse_html_tag($sContent, 'meta', 'name', 'keywords', 'content', $sCharset);
+
+        if ($aProcessAdditionalTags) {
+
+            foreach ($aProcessAdditionalTags as $k => $a) {
+                $aResult[$k] = bx_parse_html_tag(
+                    $sContent, 
+                    isset($a['tag']) ? $a['tag'] : 'meta', 
+                    isset($a['name_attr']) ? $a['name_attr'] : 'itemprop', 
+                    isset($a['name']) ? $a['name'] : $k, 
+                    isset($a['content_attr']) ? $a['content_attr'] : 'content', 
+                    $sCharset,
+                    isset($a['specialchars_decode']) ? $a['specialchars_decode'] : true); 
+
+                if ((isset($a['name']) && 'og:image' == $a['name']) || (isset($a['tag']) && 'link' == $a['tag'])) {
+                    $aResult[$k] = bx_get_site_info_fix_relative_url ($sSourceUrl, $aResult[$k]);
+                }
+            }
+
+        }
+    }
+
+    return $aResult;
+}
+
+/**
+ * Fix relative URL to make it absolute
+ * @param $sSourceUrl main URL
+ * @param $s URL to fix
+ * @return absolute URL or URL wothout changes
+ */ 
+function bx_get_site_info_fix_relative_url ($sSourceUrl, $s)
+{
+    if (0 === stripos($s, 'http://') || 0 === stripos($s, 'https://') || 0 === stripos($s, 'ftp://') || !$s)
+        return $s;
+
+    if ('/' == $s[0]) {
+        $a = parse_url($sSourceUrl);
+        return $a['scheme'] . '://' . $a['host'] . (empty($a['port']) || 80 == $a['port'] || 443 == $a['port'] ? '' : ':' . $a['port']) . $s;
+    }
+
+    return $sSourceUrl . ('/' === substr($sSourceUrl, -1) ? $s : '/' . $s);
+}
+
+function bx_parse_html_tag ($sContent, $sTag, $sAttrNameName, $sAttrNameValue, $sAttrContentName, $sCharset = false, $bSpecialCharsDecode = true)
+{
+    if (!preg_match("/<{$sTag}\s+{$sAttrNameName}[='\" ]+{$sAttrNameValue}['\"]\s+{$sAttrContentName}[='\" ]+([^'>\"]*)['\"][^>]*>/i", $sContent, $aMatch) || !isset($aMatch[1]))
+        preg_match("/<{$sTag}\s+{$sAttrContentName}[='\" ]+([^'>\"]*)['\"]\s+{$sAttrNameName}[='\" ]+{$sAttrNameValue}['\"][^>]*>/i", $sContent, $aMatch);
+
+    $s = isset($aMatch[1]) ? $aMatch[1] : '';
+
+    if ($s && $sCharset)
+        $s = mb_convert_encoding($s, 'UTF-8', $sCharset);
+
+    if ($bSpecialCharsDecode)
+        $s = htmlspecialchars_decode($s);
+        
+    return $s;
+}
+
+// calculation ini_get('upload_max_filesize') in bytes as example
+function return_bytes($val)
+{
+    $val = trim($val);
+    $last = strtolower($val{strlen($val)-1});
+    $val = (int)$val;
+    switch($last) {
+        // The 'G' modifier is available since PHP 5.1.0
+        case 'k':
+            $val *= 1024;
+            break;
+        case 'm':
+            $val *= 1024 * 1024;
+            break;
+        case 'g':
+            $val *= 1024 * 1024 * 1024;
+            break;
+    }
+    return $val;
+}
+
+// Generate Random Password
+function genRndPwd($iLength = 8, $bSpecialCharacters = true)
+{
+    $sPassword = '';
+    $sChars = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
+
+    if($bSpecialCharacters === true)
+        $sChars .= "!?=/&+,.";
+
+    srand((double)microtime()*1000000);
+    for($i = 0; $i < $iLength; $i++) {
+        $x = mt_rand(0, strlen($sChars) -1);
+        $sPassword .= $sChars{$x};
+    }
+
+    return $sPassword;
+}
+
+// Generate Random Salt for Password encryption
+function genRndSalt()
+{
+    return genRndPwd(8, true);
+}
+
+/**
+ * Encrypt user password.
+ * When BX_PWD_ALGO is 'custom', then bx_custom_pwd_encrypt function with custom algorythm must be defined in inc/header.inc.php file.
+ * @param $sPwd clear password
+ * @param $sSalt salt
+ * @return password hash
+ */ 
+function encryptUserPwd($sPwd, $sSalt)
+{
+	$sAlgo = defined('BX_PWD_ALGO') ? BX_PWD_ALGO : '';
+
+    switch ($sAlgo) {
+        
+        case 'custom':
+            return bx_custom_pwd_encrypt($sPwd, $sSalt); 
+
+    	case 'crypt':
+            return crypt($sPwd, BX_PWD_ALGO_SALT);
+
+        case 'sha1_crypt_salt':
+            return sha1(crypt($sPwd, BX_PWD_ALGO_SALT) . $sSalt);
+
+        case 'sha1_md5_salt':
+        default:
+            return sha1(md5($sPwd) . $sSalt);
+    }
+}
+
+function bx_get ($sName, $sMethod = false)
+{
+    if (isset($_GET[$sName]) && ('get' == $sMethod || !$sMethod))
+        return $_GET[$sName];
+    elseif (isset($_POST[$sName]) && ('post' == $sMethod || !$sMethod))
+        return $_POST[$sName];
+    else
+        return false;
+}
+
+function bx_get_base_url_inline($aParams = array())
+{
+    $aBaseLink = parse_url(BX_DOL_URL_ROOT);
+    $sPageLink = (!empty($aBaseLink['scheme']) ? $aBaseLink['scheme'] : 'http') . '://' . $aBaseLink['host'] . $_SERVER['REQUEST_URI'];
+
+    list($sPageLink, $aPageParams) = bx_get_base_url($sPageLink);
+
+    if(!empty($_SERVER['QUERY_STRING'])) {
+        $aPageParamsAdd = array();
+        parse_str($_SERVER['QUERY_STRING'], $aPageParamsAdd);
+        if(!empty($aPageParamsAdd) && is_array($aPageParamsAdd))
+            $aPageParams = array_merge($aPageParams, $aPageParamsAdd);
+    }
+
+    if(!empty($aParams) && is_array($aParams))
+        $aPageParams = array_merge($aPageParams, $aParams);
+
+    return array($sPageLink, $aPageParams);
+}
+
+function bx_get_base_url_popup($aParams = array())
+{
+    list($sPageLink, $aPageParams) = bx_get_base_url($_SERVER['HTTP_REFERER']);
+
+    if(!empty($aParams) && is_array($aParams))
+        $aPageParams = array_merge($aPageParams, $aParams);
+
+    return array($sPageLink, $aPageParams);
+}
+
+function bx_get_base_url($sPageLink)
+{
+    $sPageLink = BxDolPermalinks::getInstance()->unpermalink($sPageLink, false);
+
+    $sPageParams = '';
+    if(strpos($sPageLink, '?') !== false)
+        list($sPageLink, $sPageParams) = explode('?', $sPageLink);
+
+    $aPageParams = array();
+    if(!empty($sPageParams))
+        parse_str($sPageParams, $aPageParams);
+
+    return array($sPageLink, $aPageParams);
+}
+
+function bx_encode_url_params ($a, $aExcludeKeys = array (), $aOnlyKeys = false)
+{
+    $s = '';
+    foreach ($a as $sKey => $sVal) {
+        if (in_array($sKey, $aExcludeKeys))
+            continue;
+        if (false !== $aOnlyKeys && !in_array($sKey, $aOnlyKeys))
+            continue;
+        if (is_array($sVal)) {
+            foreach ($sVal as $sSubVal) {
+                $s .= rawurlencode($sKey) . '[]=' . rawurlencode(is_array($sSubVal) ? 'array' : $sSubVal) . '&';
+            }
+        } else {
+            $s .= rawurlencode($sKey) . '=' . rawurlencode($sVal) . '&';
+        }
+    }
+    return $s;
+}
+
+function bx_append_url_params ($sUrl, $mixedParams)
+{
+    if (!$mixedParams)
+        return $sUrl;
+
+    $sParams = false == strpos($sUrl, '?') ? '?' : '&';
+
+    if (is_array($mixedParams)) {
+        foreach($mixedParams as $sKey => $sValue) {
+            if (!is_array($sValue)) {
+                $sParams .= $sKey . '=' . $sValue . '&';
+            }
+            else {
+                foreach($sValue as $sSubValue)
+                    $sParams .= $sKey . '[]=' . $sSubValue . '&';
+            }
+        }
+        $sParams = substr($sParams, 0, -1);
+    } else {
+        $sParams .= $mixedParams;
+    }
+    return $sUrl . $sParams;
+}
+
+function bx_rrmdir($directory)
+{
+    if (substr($directory,-1) == "/")
+        $directory = substr($directory,0,-1);
+
+    if (!file_exists($directory) || !is_dir($directory))
+        return false;
+    elseif (!is_readable($directory))
+        return false;
+
+    if (!($directoryHandle = opendir($directory)))
+        return false;
+
+    while ($contents = readdir($directoryHandle)) {
+        if ($contents != '.' && $contents != '..') {
+            $path = $directory . "/" . $contents;
+
+            if (is_dir($path))
+                bx_rrmdir($path);
+            else
+                unlink($path);
+        }
+    }
+
+    closedir($directoryHandle);
+
+    if (!rmdir($directory))
+        return false;
+
+    return true;
+}
+
+function bx_clear_folder ($sPath, $aExts = array ())
+{
+    if (substr($$sPath,-1) == "/")
+        $sPath = substr($sPath,0,-1);
+
+    if (!file_exists($sPath) || !is_dir($sPath))
+        return false;
+    elseif (!is_readable($sPath))
+        return false;
+
+    if (!($h = opendir($sPath)))
+        return false;
+
+    while ($sFile = readdir($h)) {
+        if ('.' == $sFile || '..' == $sFile)
+            continue;
+
+        $sFullPath = $sPath . '/' . $sFile;
+
+        if (is_dir($sFullPath))
+            continue;
+
+        if (!$aExts || (($sExt = pathinfo($sFullPath, PATHINFO_EXTENSION)) && in_array($sExt, $aExts)))
+            @unlink($sFullPath);
+    }
+
+    closedir($h);
+
+    return true;
+}
+
+function bx_ltrim_str ($sString, $sPrefix, $sReplace = '')
+{
+    if ($sReplace && substr($sString, 0, strlen($sReplace)) == $sReplace)
+        return $sString;
+    if (substr($sString, 0, strlen($sPrefix)) == $sPrefix)
+        return $sReplace . substr($sString, strlen($sPrefix));
+    return $sString;
+}
+
+function bx_rtrim_str ($sString, $sPrefix, $sReplace = '')
+{
+    if ($sReplace && substr($sString, -strlen($sReplace)) == $sReplace)
+        return $sString;
+    if (substr($sString, -strlen($sPrefix)) == $sPrefix)
+        return substr($sString, 0, -strlen($sPrefix)) . $sReplace;
+    return $sString;
+}
+
+/**
+ * Strip all lines with no information for example: <p></p>, <br /><br />
+ */ 
+function bx_trim_nl_duplicates($s)
+{
+    $sStrip = implode('', array_keys(get_html_translation_table(HTML_ENTITIES)));
+    return implode('', array_filter(mb_split("[\n\r]", $s), function($s) use ($sStrip) {
+        return trim(strip_tags($s, '<button><canvas><embed><hr><iframe><img><input><object><select><svg><video>'), $sStrip) !== '';
+    }));
+}
+
+/**
+ * Convert array to attributes string
+ *
+ * Example:
+ * @code
+ * $a = array('name' => 'test', 'value' => 5);
+ * $s = bx_convert_array2attrs($a);
+ * echo $s; // outputs: name="test" value="5"
+ * @endcode
+ *
+ * @param $a - array of attributes
+ * @param $sClasses - classes to merge with 'class' attribute
+ * @param $sStyles - styles to merge with 'style' attribute
+ * @return string
+ */
+function bx_convert_array2attrs ($a, $sClasses = false, $sStyles = false)
+{
+    $sRet = '';
+
+    if (!$a || !is_array($a))
+        $a = array();
+
+    if ($sClasses) {
+        $sClasses = trim($sClasses);
+        $a['class'] = $sClasses . (!empty($a['class']) ? ' ' . $a['class'] : '');
+    }
+
+    if ($sStyles) {
+        $sStyles = trim($sStyles);
+        if (';' != $sStyles[strlen($sStyles)-1])
+            $sStyles .= ';';
+        $a['style'] = $sStyles . (!empty($a['style']) ? ' ' . $a['style'] : '');
+    }
+
+    foreach ($a as $sKey => $sValue) {
+        if(is_null($sValue)) // pass NULL values
+            continue;
+
+        $sValueC = bx_html_attribute($sValue, BX_ESCAPE_STR_QUOTE);
+
+        $sRet .= " $sKey=\"$sValueC\"";
+    }
+
+    return $sRet;
+}
+
+function bx_unicode_urldecode($s)
+{
+    preg_match_all('/%u([[:alnum:]]{4})/', $s, $a);
+
+    foreach ($a[1] as $uniord) {
+        $dec = hexdec($uniord);
+        $utf = '';
+
+        if ($dec < 128) {
+            $utf = chr($dec);
+        } else if ($dec < 2048) {
+            $utf = chr(192 + (($dec - ($dec % 64)) / 64));
+            $utf .= chr(128 + ($dec % 64));
+        } else {
+            $utf = chr(224 + (($dec - ($dec % 4096)) / 4096));
+            $utf .= chr(128 + ((($dec % 4096) - ($dec % 64)) / 64));
+            $utf .= chr(128 + ($dec % 64));
+        }
+
+        $s = str_replace('%u'.$uniord, $utf, $s);
+    }
+
+    return urldecode($s);
+}
+
+/**
+ * Raise an alert
+ * @param string $sUnit - system type
+ * @param string $sAction - system action
+ * @param int $iObjectId - object id
+ * @param int $iSender - sender (action's author) profile id, if it is false - then currectly logged in profile id is used
+ * @param array $aExtras - extra parameters 
+ */
+function bx_alert($sUnit, $sAction, $iObjectId, $iSender = false, $aExtras = array())
+{
+    $o = new BxDolAlerts($sUnit, $sAction, $iObjectId, $iSender, $aExtras);
+    $o->alert();
+}
+
+/**
+ * Perform serice call
+ * @param $mixed module name or module id
+ * @param $sMethod service method name in format 'method_name', corresponding class metod is serviceMethodName
+ * @param $aParams params to pass to service method
+ * @param $sClass class to search for service method, by default it is main module class
+ * @return service call result
+ */
+function bx_srv($mixed, $sMethod, $aParams = array(), $sClass = 'Module', $bIgnoreCache = false)
+{
+    return BxDolService::call($mixed, $sMethod, $aParams, $sClass, $bIgnoreCache);
+}
+
+function bx_replace_markers($mixed, $aMarkers)
+{
+    if (empty($aMarkers))
+        return $mixed;
+
+    if (is_array($mixed)) {
+        foreach ($mixed as $sKey => $sValue)
+            $mixed[$sKey] = bx_replace_markers ($sValue, $aMarkers);
+    } else {
+        foreach ($aMarkers as $sKey => $sValue)
+            $mixed = str_replace('{' . $sKey . '}', $sValue, $mixed);
+    }
+
+    return $mixed;
+}
+
+function bx_site_hash($sSalt = '', $isSkipVersion = false)
+{
+    return md5($sSalt . ($isSkipVersion ? '' : bx_get_ver() . getParam('sys_revision')) . BX_DOL_SECRET . BX_DOL_URL_ROOT);
+}
+
+/**
+ * Transform string to method name string, for example it changes 'some_method' string to 'SomeMethod' string
+ * @param $s string where words are separated with underscore
+ * @param $aWordsDelimiter word delimeters
+ * @return string where every word begins with capital letter
+ */
+function bx_gen_method_name ($s, $aWordsDelimiter = array('_'))
+{
+    return str_replace(' ', '', ucwords(str_replace($aWordsDelimiter, ' ', $s)));
+}
+
+/**
+ * Trigger user error
+ * @param $sMsg message to display
+ * @param $iNumLevelsBack add additional debug backtracing N levels back
+ */
+function bx_trigger_error ($sMsg, $iNumLevelsBack = 0)
+{
+    $a = debug_backtrace();
+    $sMsgAdd = "<br />\n related code in <b>{$a[$iNumLevelsBack]['file']}</b> on line <b>{$a[$iNumLevelsBack]['line']}</b> <br />\n";
+    trigger_error ($sMsg . $sMsgAdd, E_USER_ERROR);
+}
+
+/**
+ * Get system DB version, for files version @see BX_DOL_VERSION, these versions must match
+ */
+function bx_get_ver ($bInvalidateCache = false)
+{
+    $oDb = BxDolDb::getInstance();
+
+    if ($bInvalidateCache)
+        $oDb->cleanMemory('sys_version');
+    
+    $sQuery = $oDb->prepare("SELECT `version` FROM `sys_modules` WHERE `name` = 'system'");
+    return $oDb->fromMemory('sys_version', 'getOne', $sQuery);
+}
+
+/**
+ * Check if site maintetance mode is enabled.
+ * Maintetance mode is enabled when '.bx_maintenance' file exists in the script root folder, 
+ * please note that this is hidden file and some file managers don't show it.
+ * @param $bShowHttpError show 503 HTTP error if site is in mainenance mode
+ * @return true if site is in maintenance mode, or false otherwise
+ */
+function bx_check_maintenance_mode ($bShowHttpError = false)
+{
+    $bMaintetance = file_exists(BX_DIRECTORY_PATH_ROOT . BX_MAINTENANCE_FILE) && !defined('BX_DOL_UPGRADING');
+
+    if ($bMaintetance && $bShowHttpError)
+        bx_show_service_unavailable_error_and_exit ('Site is temporarily unavailable due to scheduled maintenance, please try again in a minute.', 600);
+
+    return $bMaintetance;
+}
+
+/**
+ * Check for minimal requirements.
+ * if BX_DISABLE_REQUIREMENTS_CHECK is defined then this requirements checking is skipped.
+ * @param $bShowHttpError show 503 HTTP error if site doesn't meet minimal requirements
+ * @return false if requirements are met, or array of errors of requirements aren't met
+ */
+function bx_check_minimal_requirements ($bShowHttpError = false)
+{
+    if (defined('BX_DISABLE_REQUIREMENTS_CHECK'))
+        return false;
+
+    $aErrors = array();
+
+    $aErrors[] = (ini_get('register_globals') == 0) ? '' : '<b>register_globals</b> is on (you need to disable it, or your site will be unsafe)';
+    $aErrors[] = (ini_get('safe_mode') == 0) ? '' : '<b>safe_mode</b> is on (you need to disable it)';
+    $aErrors[] = (version_compare(PHP_VERSION, '5.3.0', '<')) ? 'PHP version is too old (please update to <b>PHP 5.3.0</b> at least)' : '';
+    $aErrors[] = (!extension_loaded( 'mbstring')) ? '<b>mbstring</b> extension not installed (the script cannot work without it)' : '';
+    $aErrors[] = (ini_get('allow_url_include') == 0) ? '' : '<b>allow_url_include</b> is on (you need to disable it, or your site will be unsafe)';
+
+    $aErrors = array_diff($aErrors, array('')); // delete empty
+
+    $bFailedMinimalRequirements = !empty($aErrors);
+
+    if ($bFailedMinimalRequirements && $bShowHttpError) {
+        $sErrors = implode(" <br /> ", $aErrors);
+        bx_show_service_unavailable_error_and_exit($sErrors);
+    }
+
+    return $bFailedMinimalRequirements ? $aErrors : false;
+}
+
+/**
+ * Check if redirect to the correct hostname is required, for example redirect from site.com to www.site.com
+ * @param $bProcessRedirect process redirect and exit if needed
+ */
+function bx_check_redirect_to_correct_hostname ($bProcessRedirect = false)
+{
+    $aUrl = parse_url(BX_DOL_URL_ROOT);
+    $iPortDefault = 'https' == $aUrl['scheme'] ? '443' : '80';
+
+    $bRedirectRequired = isset($_SERVER['HTTP_HOST']) && 0 !== strcasecmp($_SERVER['HTTP_HOST'], $aUrl['host']) && 0 !== strcasecmp($_SERVER['HTTP_HOST'], $aUrl['host'] . ':' . (!empty($aUrl['port']) ? $aUrl['port'] : $iPortDefault));
+
+    if ($bRedirectRequired && $bProcessRedirect) {
+        $sPort = empty($aUrl['port']) || 80 == $aUrl['port'] || 443 == $aUrl['port'] ? '' : ':' . $aUrl['port'];
+        header("Location:{$aUrl['scheme']}://{$aUrl['host']}{$sPort}{$_SERVER['REQUEST_URI']}", true, 301);
+        exit;
+    }
+    
+    return $bRedirectRequired;
+}
+
+/**
+ * Check if redirect to remove install folder.
+ * If BX_SKIP_INSTALL_CHECK is defined then this redirect checking is skipped.
+ * @param $bProcessRedirect process redirect and exit if needed
+ */
+function bx_check_redirect_to_remove_install_folder ($bProcessRedirect = false)
+{
+    $bRemoveInstallFolder = !defined ('BX_SKIP_INSTALL_CHECK') && file_exists(BX_DIRECTORY_PATH_ROOT . 'install');
+
+    if ($bRemoveInstallFolder && $bProcessRedirect) {
+        header('Location:' . BX_DOL_URL_ROOT . 'install/index.php?action=remove_install');
+        exit;
+    }
+
+    return $bRemoveInstallFolder;
+}
+
+/**
+ * Show HTTP 503 service unavailable error and exit
+ */
+function bx_show_service_unavailable_error_and_exit ($sMsg = false, $iRetryAfter = 86400)
+{
+    header('HTTP/1.0 503 Service Unavailable', true, 503);
+    header('Retry-After: 600');
+    echo $sMsg ? $sMsg : 'Service temporarily unavailable';
+    exit;
+}
+
+/**
+ * The function is sumilar to php readfile, but it send all required headers and can send file by chunks and suports file seek
+ * @param $sPath path to file to output to the browser
+ * @param $sFilename filename without path, ig file is saved from browser, then this name is used, not used(empty) by default
+ * @param $sMimeType file mime type, by default 'application/octet-stream'
+ * @param $iCacheAge file cache age, by default 0
+ * @param $sCachePrivacy cache privacy 'public' (default value) or 'private'
+ * @return true on success or false on error
+ */
+function bx_smart_readfile($sPath, $sFilename = '', $sMimeType = 'application/octet-stream', $iCacheAge = 0, $sCachePrivacy = 'public')
+{
+    if (!file_exists($sPath))
+        return  false;
+
+    $fp = @fopen($sPath, 'rb');
+
+    $size   = filesize($sPath);
+    $length = $size;
+    $start  = 0;
+    $end    = $size - 1;
+
+    header('Content-type: ' . $sMimeType);
+    header('Cache-Control: ' . $sCachePrivacy . ', must-revalidate, max-age=' . $iCacheAge);
+    header("Accept-Ranges: 0-$length");
+    if ($sFilename)
+        header('Content-Disposition: inline; filename="' . $sFilename . '"');
+
+    if (isset($_SERVER['HTTP_RANGE'])) {
+
+        $c_start = $start;
+        $c_end   = $end;
+
+        list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
+        if (strpos($range, ',') !== false) {
+            header('HTTP/1.1 416 Requested Range Not Satisfiable');
+            header("Content-Range: bytes $start-$end/$size");
+            return false;
+        }
+        if ($range == '-') {
+            $c_start = $size - substr($range, 1);
+        }else{
+            $range  = explode('-', $range);
+            $c_start = $range[0];
+            $c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
+        }
+        $c_end = ($c_end > $end) ? $end : $c_end;
+        if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
+            header('HTTP/1.1 416 Requested Range Not Satisfiable');
+            header("Content-Range: bytes $start-$end/$size");
+            return false;
+        }
+        $start  = $c_start;
+        $end    = $c_end;
+        $length = $end - $start + 1;
+        fseek($fp, $start);
+        header('HTTP/1.1 206 Partial Content');
+    }
+    header("Content-Range: bytes $start-$end/$size");
+    header("Content-Length: ".$length);
+
+
+    $buffer = 1024 * 8;
+    while(!feof($fp) && ($p = ftell($fp)) <= $end) {
+
+        if ($p + $buffer > $end) {
+            $buffer = $end - $p + 1;
+        }
+        set_time_limit(0);
+        echo fread($fp, $buffer);
+        flush();
+    }
+
+    fclose($fp);
+
+    return true;
+}
+
+/**
+ * Wrap in A tag links in TEXT string
+ * @param $text - text string without tags
+ * @param $sAttrs - attributes string to add to the added A tag
+ * @param $bHtmlSpecialChars - apply htmlspecialchars before processing
+ * @return string where all links are wrapped in A tag
+ */
+function bx_linkify($text, $sAttrs = '', $bHtmlSpecialChars = false)
+{
+    if ($bHtmlSpecialChars)
+        $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+    $re = "@\b((https?://)|(www\.))(([0-9a-zA-Z_!~*'().&=+$%-]+:)?[0-9a-zA-Z_!~*'().&=+$%-]+\@)?(([0-9]{1,3}\.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+\.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z]\.[a-zA-Z]{2,6})(:[0-9]{1,4})?((/[0-9a-zA-Z_!~*'().;?:\@&=+$,%#-]+)*/?)@";
+    preg_match_all($re, $text, $matches, PREG_OFFSET_CAPTURE);
+
+    $matches = $matches[0];
+
+    if ($i = count($matches))
+        $bAddNofollow = getParam('sys_add_nofollow') == 'on';
+
+    while ($i--)
+    {
+        $url = $matches[$i][0];
+        if (!preg_match('@^https?://@', $url))
+            $url = 'http://'.$url;
+
+        if (strncmp(BX_DOL_URL_ROOT, $url, strlen(BX_DOL_URL_ROOT)) !== 0) {
+            $sAttrs .= ' target="_blank" ';
+            if ($bAddNofollow)
+                $sAttrs .= ' rel="nofollow" ';
+        }
+
+        $text = substr_replace($text, '<a ' . $sAttrs . ' href="'.$url.'">'.$matches[$i][0].'</a>', $matches[$i][1], strlen($matches[$i][0]));
+    }
+	
+	// email pattern
+	$mail_pattern = "/([A-z0-9\._-]+\@[A-z0-9_-]+\.)([A-z0-9\_\-\.]{1,}[A-z])/";
+	$text = preg_replace($mail_pattern, '<a href="mailto:$1$2">$1$2</a>', $text);
+	
+    return $text;
+}
+
+/**
+ * Wrap in A tag links in HTML string, which aren't wrapped in A tag yet
+ * @param $sHtmlOrig - HTML string
+ * @param $sAttrs - attributes string to add to the added A tag
+ * @return modified HTML string, in case of errror original string is returned
+ */
+function bx_linkify_html($sHtmlOrig, $sAttrs = '') 
+{
+    if (!trim($sHtmlOrig))
+        return $sHtmlOrig;
+
+    $sId = 'bx-linkify-' . md5(microtime());
+    $dom = new DOMDocument();
+    @$dom->loadHTML('<?xml encoding="UTF-8"><div id="' . $sId . '">' . $sHtmlOrig . '</div>');
+    $xpath = new DOMXpath($dom);
+
+    foreach ($xpath->query('//text()') as $text) {
+        $frag = $dom->createDocumentFragment();
+        @$frag->appendXML(bx_linkify($text->nodeValue, $sAttrs, true));
+        $text->parentNode->replaceChild($frag, $text);
+    }
+
+    if (version_compare(PHP_VERSION, '5.3.6') >= 0)
+        $s = $dom->saveHTML($dom->getElementById($sId));
+    else
+        $s = $dom->saveXML($dom->getElementById($sId), LIBXML_NOEMPTYTAG);
+
+    if (false === $s) // in case of error return original string
+        return $sHtmlOrig;
+
+    if (false !== ($iPos = mb_strpos($s, '<html><body>')) && $iPos < mb_strpos($s, $sId))
+        $s = mb_substr($s, $iPos + 12, -15); // strip <html><body> tags and everything before them
+
+    return mb_substr($s, 54, -6); // strip added tags
+}
+
+/**
+ * Returns current site protocol http:// or https://
+ */
+function bx_proto()
+{
+    return 0 === strncmp('https', BX_DOL_URL_ROOT, 5) ? 'https' : 'http';
+}
+
+/**
+ * Checks protocol in the link
+ */
+function bx_has_proto($sLink)
+{
+    return preg_match('@^https?://@', $sLink);    
+}
+
+function bx_is_empty_array ($a)
+{
+    if (!is_array($a))
+        return true;
+    if (empty($a))
+        return true;
+    foreach ($a as $k => $v)
+        if ($v)
+            return false;
+    return true;
+}
+
+function bx_is_full_array ($a)
+{
+    if (!is_array($a))
+        return false;
+    if (empty($a))
+        return false;
+    foreach ($a as $k => $v)
+        if (empty($v))
+            return false;
+    return true;
+}
+
+function bx_is_url_in_content ($sContent)
+{
+    $a = array(
+        'http://',
+        'https://',
+        'https/',
+        'http/',
+        'www.',
+        '.com'
+    );
+    foreach ($a as $s)
+        if (false !== strpos($sContent, $s))
+            return true;
+
+    return false;
+}
+
+function bx_is_dynamic_request ()
+{
+    return bx_get('dynamic') || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 'XMLHttpRequest' == $_SERVER['HTTP_X_REQUESTED_WITH']);
+}
+
+/** @} */

+ 12 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/inc/version.inc.php

@@ -0,0 +1,12 @@
+<?php defined('BX_DOL') or defined('BX_DOL_INSTALL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+define('BX_DOL_VERSION', '9.0.0-RC12'); ///< system files version, for DB version @see bx_get_ver, DB and files version must match
+
+/** @} */

+ 320 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/connect/classes/BxBaseModConnectModule.php

@@ -0,0 +1,320 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseConnect Base classes for OAuth connect modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModConnectModule extends BxBaseModGeneralModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    public function serviceGetProfilesModules ()
+    {
+        $aModules = array();
+        $a = BxDolService::call('system', 'get_profiles_modules', array(), 'TemplServiceProfiles');
+        foreach ($a as &$aModule)
+            $aModules[$aModule['name']] = $aModule['title'];
+        return $aModules;
+    }
+
+    public function serviceGetPrivacyGroups ()
+    {
+        $oPrivacyQuery = new BxDolPrivacyQuery();
+        $a = $oPrivacyQuery->getGroupsBy(array('type' => 'active'));
+        $aGroups = array();
+        foreach ($a as $r) {
+            if (!(int)$r['active'])
+               continue;
+            $aGroups[$r['id']] = _t($r['title']);
+        }
+        return $aGroups;
+    }
+    
+    /**
+     * Logged profile
+     *
+     * @param $iProfileId integer
+     * @param $sPassword string
+     * @param $sCallbackUrl
+     * @param $bRedirect boolean
+     * @return void
+     */
+    function setLogged($iProfileId, $sCallbackUrl = '', $bRedirect = true, $bRememberMe = false)
+    {
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+ 
+        bx_login($oProfile->getAccountId(), $bRememberMe);
+
+        if ($bRedirect) {
+            $sCallbackUrl = $sCallbackUrl
+                ? $sCallbackUrl
+                : $this -> _oConfig -> sDefaultRedirectUrl;
+
+            header('Location: ' . $sCallbackUrl);
+        }
+    }
+
+    /**
+     * Create new profile;
+     *
+     * @param  : $aProfileInfo (array) - remote profile's information;
+     *
+     * @param  : $sAlternativeName (string) - profiles alternative nickname;
+     */
+    function _createProfile($aProfileInfo, $sAlternativeName = '')
+    {
+        $mixed = $this->_createProfileRaw($aProfileInfo, $sAlternativeName);
+
+        bx_alert($this->getName(), 'profile_created', 0, 0, array('override_result' => &$mixed, 'remote_profile_info' => $aProfileInfo));
+        
+        // display error
+        if (is_string($mixed)) {
+            $this->_oTemplate->getPage(_t($this->_oConfig->sDefaultTitleLangKey), DesignBoxContent(_t($this->_oConfig->sDefaultTitleLangKey), MsgBox($mixed)));
+            exit;
+        } 
+
+        // display join page
+        if (is_array($mixed) && isset($mixed['join_page_redirect'])) {
+            $this->_getJoinPage($mixed['profile_fields'], $mixed['remote_profile_info']['id']);
+            exit;
+        } 
+
+        // continue profile creation
+        if (is_array($mixed) && isset($mixed['profile_id'])) {
+            $iProfileId = (int)$mixed['profile_id'];
+
+            //redirect to other page
+            header('location:' . $this->_getRedirectUrl($iProfileId, $mixed['existing_profile']));
+            exit;
+        }
+
+        $this->_oTemplate->getPage( _t($this->_oConfig->sDefaultTitleLangKey), MsgBox(_t('_Error Occured')) );
+        exit;
+    }
+
+    /**
+     * @param $aProfileInfo - remote profile info
+     * @param $sAlternativeName - suffix to add to NickName to make it unique
+     * @return profile array info, ready for the local database
+     */
+    protected function _convertRemoteFields($aProfileInfo, $sAlternativeName = '')
+    {
+    }
+
+    /**
+     * Create new profile;
+     *
+     * @param  : $aProfileInfo (array) - remote profile's information;
+     *
+     * @param  : $sAlternativeName (string) - profiles alternative nickname;
+     * @return : error string or error or request invite form or profile info array on success
+     */
+    function _createProfileRaw($aProfileInfo, $sAlternativeName = '', $isAutoFriends = true, $isSetLoggedIn = true)
+    {
+        // join by invite only
+        if (BxDolRequest::serviceExists('bx_invites', 'account_add_form_check') && $sCode = BxDolService::call('bx_invites', 'account_add_form_check'))
+            return $sCode;
+
+        // convert fields to unique format
+        $aFieldsProfile = $aFieldsAccount = $this->_convertRemoteFields($aProfileInfo, $sAlternativeName);
+
+        bx_alert($this->getName(), 'fields_converted', 0, 0, array('override_profile_fields' => &$aFieldsProfile, 'override_account_fields' => &$aFieldsAccount, 'remote_profile_info' => $aProfileInfo));
+
+        if (empty($aFieldsProfile['email']))
+            return _t('_Incorrect Email');
+
+        // prepare fields for particular module
+        $aFieldsAccount = BxDolService::call('system', 'prepare_fields', array($aFieldsAccount));
+        $aFieldsProfile = BxDolService::call($this->_oConfig->sProfilesModule, 'prepare_fields', array($aFieldsProfile));
+
+        // check fields existence in Account
+        $oFormHelperAccount = BxDolService::call('system', 'forms_helper');
+        $oFormAccount = $oFormHelperAccount->getObjectFormAdd();
+        foreach ($aFieldsAccount as $sKey => $mValue) {
+            if (!$oFormAccount->isFieldExist($sKey))
+                unset($aFieldsAccount[$sKey]);
+        }
+
+        // check fields existence in Profile
+        if ('system' != $this->_oConfig->sProfilesModule && $oFormHelperProfile = BxDolService::call($this->_oConfig->sProfilesModule, 'forms_helper')) {
+            $oFormProfile = $oFormHelperProfile->getObjectFormEdit();
+            foreach ($aFieldsProfile as $sKey => $mValue) {
+                if (!$oFormProfile->isFieldExist($sKey))
+                    unset($aFieldsProfile[$sKey]);
+            }
+        }
+
+        // antispam check
+        $sErrorMsg = '';
+        $bSetPendingApproval = false;
+        bx_alert('account', 'check_join', 0, false, array('error_msg' => &$sErrorMsg, 'email' => $aFieldsAccount['email'], 'approve' => &$bSetPendingApproval));
+        if ($sErrorMsg)
+            return $sErrorMsg;
+
+        // check if user with the same email already exists
+        $oExistingAccount = BxDolAccount::getInstance($aFieldsAccount['email']);
+
+        // check redirect page
+        if ('join' == $this->_oConfig->sRedirectPage && !$oExistingAccount)
+            return array('remote_profile_info' => $aProfileInfo, 'profile_fields' => $aFieldsAccount, 'join_page_redirect' => true);
+
+        // create new profile
+        if ($oExistingAccount) {
+
+            if (!($oExistingProfile = BxDolProfile::getInstanceByAccount($oExistingAccount->id(), true)))
+                return _t('_sys_txt_error_account_creation');
+
+            $iProfileId = $oExistingProfile->id();
+
+            $this->setLogged($iProfileId);
+        }
+        else {
+
+            // create account
+            $aFieldsAccount['password'] = genRndPwd();
+            $aFieldsAccount['email_confirmed'] = $this->_oConfig->isAlwaysConfirmEmail; 
+            if (!($iAccountId = $oFormAccount->insert($aFieldsAccount)))
+                return _t('_sys_txt_error_account_creation');
+
+            $isSetPendingApproval = $this->_oConfig->isAlwaysAutoApprove ? false : !(bool)getParam('sys_account_autoapproval');
+            $iAccountProfileId = $oFormHelperAccount->onAccountCreated ($iAccountId, $isSetPendingApproval, BX_PROFILE_ACTION_EXTERNAL);
+
+            // create profile
+            if (isset($oFormProfile) && $oFormProfile) {
+                
+                $aFieldsProfile['picture'] = $this->_processImage($aFieldsProfile, $iAccountProfileId, $oFormHelperProfile);
+                $_POST['picture'] = $aFieldsProfile['picture']; // set POST variable to correctly process images in processFiles method in form object
+
+                if (!($iContentId = $oFormProfile->insert($aFieldsProfile)))
+                    return _t('_sys_txt_error_account_creation');
+
+                $oFormHelperProfile->setAutoApproval($oFormHelperProfile->isAutoApproval() ? true : $this->_oConfig->isAlwaysAutoApprove);
+                if ($sErrorMsg = $oFormHelperProfile->onDataAddAfter ($iAccountId, $iContentId))
+                    return $sErrorMsg;
+                
+                $oProfile = BxDolProfile::getInstanceByAccount($iAccountId, true);
+                $iProfileId = $oProfile->id();
+            } 
+            else {
+                $iProfileId = $iAccountProfileId;
+            }
+
+            // send email with password
+            sendMailTemplate($this->_oConfig->sEmailTemplatePasswordGenerated, $iAccountId, $iProfileId, array('password' => $aFieldsAccount['password']), BX_EMAIL_SYSTEM);
+        }
+
+        // remember remote profile id for created member
+        $this ->_oDb->saveRemoteId($iProfileId, $aProfileInfo['id']);
+
+        // auto-friend members if they are already friends on remote site
+        if ($isAutoFriends && method_exists($this, '_makeFriends'))
+            $this->_makeFriends($iProfileId);
+
+        return array('remote_profile_info' => $aProfileInfo, 'profile_id' => $iProfileId, 'existing_profile' => $oExistingAccount ? true : false);
+    }
+
+    protected function _processImage($aFieldsProfile, $iAccountProfileId, $oFormHelperProfile)
+    {
+        if (!isset($aFieldsProfile['picture']) || !$aFieldsProfile['picture'])
+            return 0;
+
+        if (!($oStorage = $oFormHelperProfile->getObjectStorage()))
+            return 0;
+        
+        if (!($iFileId = $oStorage->storeFileFromUrl($aFieldsProfile['picture'], false, $iAccountProfileId)))
+            return 0;
+
+        return $iFileId;
+    }
+
+     /**
+      * Get join page
+      *
+      * @param $aProfileFields array
+      * @param $iRemoteProfileId remote profile id
+      * @return void
+      */
+    function _getJoinPage($aProfileFields, $iRemoteProfileId)
+    {
+        bx_import('BxDolSession');
+        $oSession = BxDolSession::getInstance();
+        $oSession->setValue($this->_oConfig->sSessionUid, $iRemoteProfileId);
+
+        $oPage = BxDolPage::getObjectInstanceByURI('create-account');
+
+        BxBaseAccountForms::$PROFILE_FIELDS = $aProfileFields;
+
+        $this->_oTemplate->getPage(false, $oPage->getCode());
+    }
+
+    /**
+     * get redirect URL
+     * 
+     * @param $iProfileId integer - profile ID
+     * @return string redirect URL
+     */
+    function _getRedirectUrl($iProfileId, $isExistingProfile = false)
+    {
+        $sRedirectUrl = $this->_oConfig->sDefaultRedirectUrl;
+
+        switch($this->_oConfig->sRedirectPage) {
+            case 'index':
+                $sRedirectUrl = BX_DOL_URL_ROOT;
+                break;
+
+            case 'settings':
+                if (!$isExistingProfile) { 
+                    $sRedirectUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=account-settings-email');
+                    break;
+                }
+
+            case 'dashboard':
+            default:
+                $sRedirectUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=dashboard');
+                break;
+            }
+
+        return $sRedirectUrl;
+    }
+
+    protected function _redirect($sUrl, $iStatus = 302)
+    {
+        header("Location:{$sUrl}", true, $iStatus);
+        exit;
+    }
+
+    protected function _genToken($bReturn = false)
+    {
+        $oSession = BxDolSession::getInstance();
+
+        $sPrefix = $this->getName();
+
+        $iCsrfTokenLifetime = 3600;
+        if ($oSession->getValue($sPrefix . '_token') === false || (time() - (int)$oSession->getValue($sPrefix . '_token_time') > $iCsrfTokenLifetime)) {
+            $sToken = genRndPwd(20, false);
+            $oSession->setValue($sPrefix . '_token', $sToken);
+            $oSession->setValue($sPrefix . '_token_time', time());
+        }
+        else {
+            $sToken = $oSession->getValue($sPrefix . '_token');
+        }
+
+        return $sToken;
+    }
+
+    protected function _getToken()
+    {
+        $oSession = BxDolSession::getInstance();
+        return $oSession->getValue($this->getName() . '_token');
+    }
+}
+
+/** @} */

+ 1552 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/general/classes/BxBaseModGeneralModule.php

@@ -0,0 +1,1552 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGeneral Base classes for modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+bx_import('BxDolAcl');
+
+/**
+ * Base module class.
+ */
+class BxBaseModGeneralModule extends BxDolModule
+{
+    protected $_iProfileId;
+    protected $_aSearchableNamesExcept;
+    protected $_aFormParams;
+
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_iProfileId = bx_get_logged_profile_id();
+        $this->_aSearchableNamesExcept = array(
+            'allow_view_to'
+        );
+
+        $this->_aFormParams = array(
+            'display' => false, 
+            'dynamic_mode' => false, 
+            'ajax_mode' => false, 
+            'absolute_action_url' => false,
+            'visibility_autoselect' => false,
+            'context_id' => 0,
+        );
+    }
+
+    // ====== ACTIONS METHODS
+
+    public function actionRss ()
+    {
+        $aArgs = func_get_args();
+        $this->_rss($aArgs);
+    }
+    
+    public function actionGetCreatePostForm()
+    {
+    	$aParams = bx_process_input(array_intersect_key($_GET, $this->_aFormParams));
+    	$aParams = array_merge($this->_aFormParams, $aParams);
+
+    	$sForm = $this->serviceGetCreatePostForm($aParams);
+    	if(empty($sForm))
+    		return echoJson(array());
+
+	   	return echoJson(array(
+    		'module' => $this->_oConfig->getName(),
+    		'content' => $sForm
+    	));
+    }
+
+    // ====== SERVICE METHODS
+    public function serviceGetCreatePostForm($aParams = array())
+    {
+    	$aParams = array_merge($this->_aFormParams, $aParams);
+
+    	$oForm = $this->serviceGetObjectForm('add', $aParams);
+    	if(!$oForm)
+    		return ''; 	
+
+    	return $this->serviceEntityCreate($aParams);
+    }
+
+    public function serviceGetAuthor ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_AUTHOR', $iContentId);
+        return $mixedResult !== false ? (int)$mixedResult : 0; 
+    }
+
+    public function serviceGetDateAdded ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_ADDED', $iContentId);
+        return $mixedResult !== false ? (int)$mixedResult : 0; 
+    }
+
+    public function serviceGetDateChanged ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_CHANGED', $iContentId);
+        return $mixedResult !== false ? (int)$mixedResult : 0;
+    }
+
+    public function serviceGetLink ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['URI_VIEW_ENTRY']))
+            return '';
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo))
+            return '';
+
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+    }
+
+    public function serviceGetTitle ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_TITLE', $iContentId);
+        return $mixedResult !== false ? $mixedResult : '';
+    }
+
+    public function serviceGetText ($iContentId)
+    {
+        $mixedResult = $this->_getFieldValue('FIELD_TEXT', $iContentId);
+        if (false === $mixedResult)
+            return '';
+
+        $CNF = &$this->_oConfig->CNF;
+        if (!empty($CNF['OBJECT_METATAGS']) && is_string($mixedResult)) {
+            $oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+            $mixedResult = $oMetatags->metaParse($iContentId, $mixedResult);
+        }
+
+        return $mixedResult;
+    }
+
+    public function serviceGetInfo ($iContentId, $bSearchableFieldsOnly = true)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aContentInfo = $this->_getFields($iContentId);
+        if(empty($aContentInfo))
+            return array();
+
+        if(!$bSearchableFieldsOnly)
+            return $aContentInfo;
+
+        if(empty($CNF['PARAM_SEARCHABLE_FIELDS']))
+            return array();
+
+        $aFields = explode(',', getParam($CNF['PARAM_SEARCHABLE_FIELDS']));
+        if(empty($aFields))
+            return array();
+
+        $aResult = array();
+        foreach($aFields as $sField)
+            if(isset($aContentInfo[$sField]))
+                $aResult[$sField] = $aContentInfo[$sField];
+
+        return $aResult;
+    }
+
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo))
+            return '';
+
+        if(empty($sUnitTemplate))
+            $sUnitTemplate = 'unit.html';
+
+        return $this->_oTemplate->unit($aContentInfo, true, $sUnitTemplate);
+    }
+
+    public function serviceGetAll ($aParams = array())
+    {
+        if(empty($aParams) || !is_array($aParams))
+            $aParams = array('type' => 'all');
+
+        return $this->_oDb->getEntriesBy($aParams);
+    }
+
+    public function serviceGetAllByAuthor ($iProfileId)
+    {
+        return $this->_oDb->getEntriesByAuthor((int)$iProfileId);
+    }
+
+    public function serviceGetSearchableFieldsExtended()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_FORM_ENTRY']))
+            return array();
+
+        $aResult = array();
+        if(!empty($CNF['FIELD_AUTHOR']) && !in_array($CNF['FIELD_AUTHOR'], $this->_aSearchableNamesExcept))
+            $aResult[$CNF['FIELD_AUTHOR']] = array(
+                'type' => 'text_auto', 
+                'caption' => $CNF['T']['form_field_author'],
+                'info' => '',
+            	'value' => '',
+                'values' => '',
+                'pass' => ''
+            );
+
+        $aInputs = array();
+        if(!empty($CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'])) {
+            $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'], $this->_oTemplate);
+
+            $aInputs = array_merge($aInputs, $oForm->aInputs);
+        }
+        if(!empty($CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'])) {
+            $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_EDIT'], $this->_oTemplate);
+
+            $aInputs = array_merge($aInputs, $oForm->aInputs);
+        }
+
+        foreach($aInputs as $aInput)
+            if(in_array($aInput['type'], BxDolSearchExtended::$SEARCHABLE_TYPES) && !in_array($aInput['name'], $this->_aSearchableNamesExcept))
+                $aResult[$aInput['name']] = array(
+                    'type' => $aInput['type'], 
+                    'caption_system' => $aInput['caption_system_src'],
+                    'caption' => $aInput['caption_src'],
+                    'info' => $aInput['info_src'],
+                    'value' => !empty($aInput['value']) ? $aInput['value'] : '',
+                    'values' => !empty($aInput['values_src']) ? $aInput['values_src'] : '',
+                    'pass' => !empty($aInput['db']['pass']) ? $aInput['db']['pass'] : '',
+                );
+
+        return $aResult;
+    }
+
+    public function serviceGetSearchResultExtended($aParams, $iStart = 0, $iPerPage = 0)
+    {
+        if(empty($aParams) || !is_array($aParams))
+            return array();
+
+        return $this->_oDb->getEntriesBy(array('type' => 'search_ids', 'search_params' => $aParams, 'start' => $iStart, 'per_page' => $iPerPage));
+    }
+
+    public function serviceModuleIcon ()
+    {
+        return isset($this->_oConfig->CNF['ICON']) ? $this->_oConfig->CNF['ICON'] : '';
+    }
+
+    public function serviceGetSearchableFields ()
+    {
+        $CNF = $this->_oConfig->CNF;
+
+        if (!isset($CNF['PARAM_SEARCHABLE_FIELDS']) || !isset($CNF['OBJECT_FORM_ENTRY']) || !isset($CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD']))
+            return array();
+
+        $aTextTypes = array('text', 'textarea');
+        $aTextFields = array();
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_ENTRY'], $CNF['OBJECT_FORM_ENTRY_DISPLAY_ADD'], $this->_oTemplate);
+        foreach ($oForm->aInputs as $r) {
+            if (in_array($r['type'], $aTextTypes))
+                $aTextFields[$r['name']] = $r['caption'];
+        }
+        return $aTextFields;
+    }
+    
+	public function serviceManageTools($sType = 'common')
+    {
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->getGridObject($sType));
+        if(!$oGrid)
+            return '';
+
+		$CNF = &$this->_oConfig->CNF;
+
+		$sMenu = '';
+		if(BxDolAcl::getInstance()->isMemberLevelInSet(192)) {
+			$oPermalink = BxDolPermalinks::getInstance();
+
+			$aMenuItems = array();
+			if(!empty($CNF['OBJECT_GRID_COMMON']) && !empty($CNF['T']['menu_item_manage_my']))
+				$aMenuItems[] = array('id' => 'manage-common', 'name' => 'manage-common', 'class' => '', 'link' => $oPermalink->permalink($CNF['URL_MANAGE_COMMON']), 'target' => '_self', 'title' => _t($CNF['T']['menu_item_manage_my']), 'active' => 1);
+			if(!empty($CNF['OBJECT_GRID_ADMINISTRATION']) && !empty($CNF['T']['menu_item_manage_all']))
+				$aMenuItems[] = array('id' => 'manage-administration', 'name' => 'manage-administration', 'class' => '', 'link' => $oPermalink->permalink($CNF['URL_MANAGE_ADMINISTRATION']), 'target' => '_self', 'title' => _t($CNF['T']['menu_item_manage_all']), 'active' => 1);
+
+			if(count($aMenuItems) > 1) {
+	            $oMenu = new BxTemplMenu(array(
+	            	'template' => 'menu_vertical.html', 
+	            	'menu_items' => $aMenuItems
+	            ), $this->_oTemplate);
+	            $oMenu->setSelected($this->_aModule['name'], 'manage-' . $sType);
+	            $sMenu = $oMenu->getCode();
+			}
+		}
+
+		if(!empty($CNF['OBJECT_MENU_SUBMENU']) && isset($CNF['URI_MANAGE_COMMON'])) {
+			BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SUBMENU'])->setSelected($this->_aModule['name'], $CNF['URI_MANAGE_COMMON']);
+		}
+
+        $this->_oTemplate->addCss(array('manage_tools.css'));
+        $this->_oTemplate->addJs(array('manage_tools.js'));
+        $this->_oTemplate->addJsTranslation(array('_sys_grid_search'));
+        return array(
+        	'content' => $this->_oTemplate->getJsCode('manage_tools', array('sObjNameGrid' => $this->_oConfig->getGridObject($sType))) . $oGrid->getCode(),
+        	'menu' => $sMenu
+        );
+    }
+
+    public function serviceGetMenuAddonManageTools()
+    {
+    	return 0;
+    }
+    
+    public function serviceGetMenuAddonManageToolsProfileStats()
+    {
+    	return 0;
+    }
+
+    /**
+     * My entries actions block
+     */
+    public function serviceMyEntriesActions ($iProfileId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_MENU_ACTIONS_MY_ENTRIES']))
+            return false;
+
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId || !($oProfile = BxDolProfile::getInstance($iProfileId)))
+            return false;
+
+        if ($iProfileId != $this->_iProfileId)
+            return false;
+
+        $oMenu = BxTemplMenu::getObjectInstance($CNF['OBJECT_MENU_ACTIONS_MY_ENTRIES']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    /**
+     * Universal browse method
+     * @param $aParams custom browse params, possible params are the following:
+     *  - mode - browse mode, such as 'recent', 'featured', etc
+     *  - params - custom params to browse method, for example 'unit_view' can be passed here
+     *  - design_box - design box style, @see BxBaseFunctions::DesignBoxContent 
+     *  - empty_message - display or not "empty" message when there is no content
+     *  - ajax_paginate - use AJAX paginate or not
+     *  @return HTML string
+     */
+    public function serviceBrowse ($aParams = array())
+    {
+        $aDefaults = array (
+            'mode' => 'recent',
+            'params' => false,
+            'design_box' => BX_DB_PADDING_DEF,
+            'empty_message' => false,
+            'ajax_paginate' => true,
+        );
+        $aParams = array_merge($aDefaults, $aParams);
+        return $this->_serviceBrowse ($aParams['mode'], $aParams['params'], $aParams['design_box'], $aParams['empty_message'], $aParams['ajax_paginate']);
+    }
+    
+	/**
+     * Display featured entries
+     * @return HTML string
+     */
+    public function serviceBrowseFeatured ($sUnitView = false, $bEmptyMessage = false, $bAjaxPaginate = true)
+    {
+        return $this->_serviceBrowse ('featured', $sUnitView ? array('unit_view' => $sUnitView) : false, BX_DB_PADDING_DEF, $bEmptyMessage, $bAjaxPaginate);
+    }
+	
+	/**
+     * Display entries favored by a member
+     * @return HTML string
+     */
+    public function serviceBrowseFavorite ($iProfileId = 0, $aParams = array())
+    {
+        $oProfile = null;
+        if((int)$iProfileId)
+            $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile && bx_get('profile_id') !== false)
+            $oProfile = BxDolProfile:: getInstance(bx_process_input(bx_get('profile_id'), BX_DATA_INT));
+        if(!$oProfile)
+            $oProfile = BxDolProfile::getInstance();
+        if(!$oProfile)
+            return '';
+
+        $bEmptyMessage = false;
+        if(isset($aParams['empty_message'])) {
+            $bEmptyMessage = (bool)$aParams['empty_message'];
+            unset($aParams['empty_message']);
+        }
+
+        return $this->_serviceBrowse ('favorite', array_merge(array('user' => $oProfile->id()), $aParams), BX_DB_PADDING_DEF, $bEmptyMessage);
+    }
+
+    public function serviceFormsHelper ()
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        return new $sClass($this);
+    }
+
+	/**
+     * Add entry using provided fields' values.
+     * @return array with result: 'code' is 0 on success or non-zero on error, 'message' is error message in case of error, 'content' is content info array in case of success
+     */
+    public function serviceEntityAdd ($iProfile, $aValues)
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->addData($iProfile, $aValues);
+    }
+
+	/**
+     * Perform redirect after content creation
+     * @return nothing, rediret header is sent
+     */    
+    public function serviceRedirectAfterAdd($aContentInfo)
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        $oFormsHelper->redirectAfterAdd($aContentInfo);
+    }
+
+    /**
+     * Get form object for add, edit, view or delete the content 
+     * @param $sType 'add', 'edit', 'view' or 'delete'
+     * @param $aParams optional array with parameters(display name, etc)
+     * @return form object or false on error
+     */
+    public function serviceGetObjectForm ($sType, $aParams = array())
+    {
+        if (!in_array($sType, array('add', 'edit', 'view', 'delete')))
+            return false;
+
+		$CNF = &$this->_oConfig->CNF;
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+
+        $sDisplay = !empty($aParams['display']) ? $aParams['display'] : false;
+
+        $sFunc = 'getObjectForm' . ucfirst($sType);
+        $oForm = $oFormsHelper->$sFunc($sDisplay);
+
+        $sParamsKey = 'absolute_action_url';
+        if(isset($aParams[$sParamsKey]) && (bool)$aParams[$sParamsKey] === true) {
+        	$sKeyUri = 'URI_' . strtoupper($sType) . '_ENTRY';
+        	if(!empty($this->_oConfig->CNF[$sKeyUri]))
+        		$oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->_oConfig->CNF[$sKeyUri]);
+        }
+
+        $sParamsKey = 'ajax_mode';
+        if(isset($aParams[$sParamsKey]) && is_bool($aParams[$sParamsKey]))
+        	$oForm->setAjaxMode((bool)$aParams[$sParamsKey]);
+
+		$sKey = 'FIELD_ALLOW_VIEW_TO';
+		if(!empty($aParams['context_id']) && !empty($CNF[$sKey]) && !empty($oForm->aInputs[$CNF[$sKey]])) {
+			foreach($oForm->aInputs[$CNF[$sKey]]['values'] as $aValue)
+				if(isset($aValue['key']) && (int)$aValue['key'] == -(int)$aParams['context_id']) {
+					$oForm->aInputs[$CNF[$sKey]]['value'] = -(int)$aParams['context_id'];
+					$oForm->aInputs[$CNF[$sKey]]['type'] = 'hidden';
+					break;
+				}
+		}
+
+        return $oForm;
+    }
+
+    /**
+     * Create entry form
+     * @return HTML string
+     */
+    public function serviceEntityCreate ($sParams = false)
+    {
+        $bParamsArray = is_array($sParams);
+
+        $sDisplay = is_string($sParams) ? $sParams : false;
+        if($bParamsArray && !empty($sParams['display']))
+            $sDisplay = $sParams['display'];
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        if($bParamsArray && isset($sParams['dynamic_mode']))
+            $oFormsHelper->setDynamicMode($sParams['dynamic_mode']);
+
+        return $oFormsHelper->addDataForm($sDisplay);
+    }
+
+    public function serviceEntityEdit ($iContentId = 0, $sDisplay = false)
+    {
+        return $this->_serviceEntityForm ('editDataForm', $iContentId, $sDisplay);
+    }
+
+    public function serviceEntityDelete ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('deleteDataForm', $iContentId);
+    }
+
+    public function serviceEntityTextBlock ($iContentId = 0)
+    {
+        return $this->_serviceEntityForm ('viewDataEntry', $iContentId);
+    }
+
+    public function serviceEntityInfo ($iContentId = 0, $sDisplay = false)
+    {
+        return $this->_serviceEntityForm ('viewDataForm', $iContentId, $sDisplay);
+    }
+
+	public function serviceEntityInfoFull ($iContentId = 0)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$sDisplayName = 'OBJECT_FORM_ENTRY_DISPLAY_VIEW_FULL';
+        return $this->_serviceEntityForm ('viewDataForm', $iContentId, !empty($CNF[$sDisplayName]) ? $CNF[$sDisplayName] : false);
+    }
+
+	public function serviceEntityInfoExtended ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryInfo', $iContentId);
+    }
+
+    /**
+     * Entry location info
+     */
+    public function serviceEntityLocation ($iContentId = 0)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        return $this->_oTemplate->entryLocation ($iContentId);
+    }
+
+    /**
+     * Entry comments
+     */
+    public function serviceEntityComments ($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF['OBJECT_COMMENTS']))
+            return '';
+
+        return $this->_entityComments($CNF['OBJECT_COMMENTS'], $iContentId);
+    }
+
+    /**
+     * Entry attachments block
+     */
+    public function serviceEntityAttachments ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryAttachments', $iContentId);
+    }
+    
+    /**
+     * Delete content entry
+     * @param $iContentId content id 
+     * @return error message or empty string on success
+     */
+    public function serviceDeleteEntity ($iContentId, $sFuncDelete = 'deleteData')
+    {
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_oConfig->getClassPrefix() . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->$sFuncDelete($iContentId);
+    }
+
+    /**
+     * Entry actions and social sharing block
+     */
+    public function serviceEntityAllActions ($mixedContent = false, $aParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+                $mixedContent = array((int)$mixedContent, (method_exists($this->_oDb, 'getContentInfoById')) ? $this->_oDb->getContentInfoById((int)$mixedContent) : array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $sObjectMenu = !empty($aParams['object_menu']) ? $aParams['object_menu'] : '';
+        if(empty($sObjectMenu) && !empty($CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL']))
+            $sObjectMenu = $CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL'];
+
+        if(empty($sObjectMenu))
+            return false;
+
+        $sEntryTitle = !empty($aParams['entry_title']) ? $aParams['entry_title'] : '';
+        if(empty($sEntryTitle) && !empty($CNF['FIELD_TITLE']) && !empty($aContentInfo[$CNF['FIELD_TITLE']]))
+            $sEntryTitle = $aContentInfo[$CNF['FIELD_TITLE']];
+
+        $sEntryUrl = !empty($aParams['entry_url']) ? $aParams['entry_url'] : '';
+        if(empty($sEntryUrl) && !empty($CNF['URI_VIEW_ENTRY']))
+            $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId);
+
+        $iEntryThumb = !empty($aParams['entry_thumb']) ? (int)$aParams['entry_thumb'] : 0;
+        if(empty($iEntryThumb) && !empty($CNF['FIELD_THUMB']) && !empty($aContentInfo[$CNF['FIELD_THUMB']]))
+            $iEntryThumb = (int)$aContentInfo[$CNF['FIELD_THUMB']];
+
+        $sObjectStorage = !empty($aParams['object_storage']) ? $aParams['object_storage'] : false;
+        if(empty($sObjectStorage) && !empty($CNF['OBJECT_STORAGE']))
+            $sObjectStorage = $CNF['OBJECT_STORAGE'];
+
+        $sObjectTranscoder = !empty($aParams['object_transcoder']) ? $aParams['object_transcoder'] : false;
+
+        $aMarkers = array(
+            'id' => $iContentId,
+            'module' => $this->_oConfig->getName(),
+            'title' => !empty($sEntryTitle) ? $sEntryTitle : '',
+            'url' => !empty($sEntryUrl) ? $sEntryUrl : '',
+            'img_url' => ''
+        );
+
+        if(!empty($iEntryThumb)) {
+            if(!empty($sObjectTranscoder))
+                $o = BxDolTranscoder::getObjectInstance($sObjectTranscoder);
+            else if(!empty($sObjectStorage))
+                $o = BxDolStorage::getObjectInstance($sObjectStorage);
+
+            $sImageUrl = $o ? $o->getFileUrlById($iEntryThumb) : '';
+            if(!empty($sImageUrl))
+                $aMarkers['img_url'] = $sImageUrl;
+        }
+
+        $oActions = BxDolMenu::getObjectInstance($sObjectMenu);
+        if(!$oActions)
+            return false;
+
+        $oActions->setContentId($iContentId);
+        $oActions->addMarkers($aMarkers);
+        return $this->_oTemplate->entryAllActions($oActions->getCode());
+    }
+
+    /**
+     * Entry actions block
+     */
+    public function serviceEntityActions ($iContentId = 0)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        $oMenu = BxTemplMenu::getObjectInstance($this->_oConfig->CNF['OBJECT_MENU_ACTIONS_VIEW_ENTRY']);
+        return $oMenu ? $oMenu->getCode() : false;
+    }
+
+    /**
+     * Entry social sharing block.
+     * @param type $mixedContent integer content ID or array with integer content ID and array with content info, or false.
+     * @param type $aParams array with additional custom params which may overwrite some default values.
+     * @return boolean|string string with block content or false if something went wrong.
+     */
+    public function serviceEntitySocialSharing($mixedContent = false, $aParams = array())
+    {
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+               $mixedContent = array((int)$mixedContent, array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $sUri = !empty($aParams['uri']) ? $aParams['uri'] : '';
+        if(empty($sUri) && !empty($CNF['URI_VIEW_ENTRY']))
+            $sUri = $CNF['URI_VIEW_ENTRY'];
+
+        $sUrl = !empty($sUri) ? BxDolPermalinks::getInstance()->permalink('page.php?i=' . $sUri . '&id=' . $iContentId) : '';
+
+        $sTitle = !empty($aParams['title']) ? $aParams['title'] : '';
+        if(empty($sTitle) && !empty($aContentInfo[$CNF['FIELD_TITLE']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TITLE']];
+
+        $aMarkers = array(
+            'id' => $iContentId,
+            'module' => $this->_aModule['name'],
+            'url' => BX_DOL_URL_ROOT . $sUrl,
+            'title' => $sTitle,
+        );
+
+        $iIdThumb = !empty($aParams['id_thumb']) ? (int)$aParams['id_thumb'] : 0;
+        if(empty($iIdThumb) && !empty($CNF['FIELD_THUMB']) && !empty($aContentInfo[$CNF['FIELD_THUMB']]))
+            $iIdThumb = (int)$aContentInfo[$CNF['FIELD_THUMB']];
+
+        if ($iIdThumb) {
+            $sTranscoder = !empty($aParams['object_transcoder']) ? $aParams['object_transcoder'] : '';
+            $sStorage = !empty($aParams['object_storage']) ? $aParams['object_storage'] : '';
+            if(empty($sStorage) && !empty($CNF['OBJECT_STORAGE']))
+                $sStorage = $CNF['OBJECT_STORAGE'];
+
+            if(!empty($sTranscoder))
+                $o = BxDolTranscoder::getObjectInstance($sTranscoder);
+            else if(!empty($sStorage))
+                $o = BxDolStorage::getObjectInstance($sStorage);
+
+            $sImgUrl = $o ? $o->getFileUrlById($iIdThumb) : '';
+            if($sImgUrl)
+                $aMarkers['img_url'] = $sImgUrl;
+        }
+
+        $oMenu = BxDolMenu::getObjectInstance('sys_social_sharing');
+        $oMenu->addMarkers($aMarkers);
+        $sMenu = $oMenu->getCode();
+
+        if(empty($sMenu))
+            return '';
+
+        return $this->_oTemplate->parseHtmlByName('entry-share.html', array(
+            'menu' => $sMenu
+        ));
+    }
+
+    /**
+     * Data for Notifications module
+     */
+    public function serviceGetNotificationsData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+    	$sEventPrivacy = $sModule . '_allow_view_event_to';
+		if(BxDolPrivacy::getObjectInstance($sEventPrivacy) === false)
+			$sEventPrivacy = '';
+
+        return array(
+            'handlers' => array(
+                array('group' => $sModule . '_object', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'added', 'module_name' => $sModule, 'module_method' => 'get_notifications_post', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                array('group' => $sModule . '_object', 'type' => 'update', 'alert_unit' => $sModule, 'alert_action' => 'edited'),
+                array('group' => $sModule . '_object', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'deleted'),
+
+                array('group' => $sModule . '_comment', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'commentPost', 'module_name' => $sModule, 'module_method' => 'get_notifications_comment', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                array('group' => $sModule . '_comment', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'commentRemoved'),
+
+                array('group' => $sModule . '_reply', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'replyPost', 'module_name' => $sModule, 'module_method' => 'get_notifications_reply', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                array('group' => $sModule . '_reply', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'replyRemoved'),
+
+                array('group' => $sModule . '_vote', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_vote', 'module_class' => 'Module', 'module_event_privacy' => $sEventPrivacy),
+                array('group' => $sModule . '_vote', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'undoVote'),
+            ),
+            'settings' => array(
+                array('group' => 'content', 'unit' => $sModule, 'action' => 'added', 'types' => array('follow_member', 'follow_context')),
+                array('group' => 'comment', 'unit' => $sModule, 'action' => 'commentPost', 'types' => array('personal', 'follow_member', 'follow_context')),
+                array('group' => 'reply', 'unit' => $sModule, 'action' => 'replyPost', 'types' => array('personal')),
+                array('group' => 'vote', 'unit' => $sModule, 'action' => 'doVote', 'types' => array('personal', 'follow_member', 'follow_context'))
+            ),
+            'alerts' => array(
+                array('unit' => $sModule, 'action' => 'added'),
+                array('unit' => $sModule, 'action' => 'edited'),
+                array('unit' => $sModule, 'action' => 'deleted'),
+
+                array('unit' => $sModule, 'action' => 'commentPost'),
+                array('unit' => $sModule, 'action' => 'commentRemoved'),
+
+                array('unit' => $sModule, 'action' => 'replyPost'),
+                array('unit' => $sModule, 'action' => 'replyRemoved'),
+
+                array('unit' => $sModule, 'action' => 'doVote'),
+                array('unit' => $sModule, 'action' => 'undoVote'),
+            )
+        );
+    }
+
+    /**
+     * Entry post for Notifications module
+     */
+    public function serviceGetNotificationsPost($aEvent)
+    {
+		$CNF = &$this->_oConfig->CNF;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+        $sEntryCaption = isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+		return array(
+			'entry_sample' => $CNF['T']['txt_sample_single'],
+			'entry_url' => $sEntryUrl,
+			'entry_caption' => $sEntryCaption,
+			'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+			'entry_privacy' => '', //may be empty or not specified. In this case Public privacy will be used.
+			'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+		);
+    }
+
+	/**
+     * Entry post comment for Notifications module
+     */
+    public function serviceGetNotificationsComment($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+		$oComment = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $iContentId);
+        if(!$oComment || !$oComment->isEnabled())
+            return array();
+
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+        $sEntryCaption = isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+		return array(
+			'entry_sample' => $CNF['T']['txt_sample_single'],
+			'entry_url' => $sEntryUrl,
+			'entry_caption' => $sEntryCaption,
+			'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+			'subentry_sample' => $CNF['T']['txt_sample_comment_single'],
+			'subentry_url' => $oComment->getViewUrl((int)$aEvent['subobject_id']),
+			'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+		);
+    }
+
+	/**
+     * Entry post reply for Notifications module
+     */
+    public function serviceGetNotificationsReply($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$oComment = BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], 0, false);
+        if(!$oComment || !$oComment->isEnabled())
+            return array();
+
+    	$iParentId = (int)$aEvent['object_id'];
+        $aParentInfo = $oComment->getQueryObject()->getCommentsBy(array('type' => 'id', 'id' => $iParentId));
+        if(empty($aParentInfo) || !is_array($aParentInfo))
+            return array();
+
+        $iObjectId = (int)$aParentInfo['cmt_object_id'];
+		$oComment->init($iObjectId);
+
+		return array(
+		    'object_id' => $iObjectId,
+			'entry_sample' => '_cmt_txt_sample_comment_single',
+			'entry_url' => $oComment->getViewUrl($iParentId),
+			'entry_caption' => strmaxtextlen($aParentInfo['cmt_text'], 20, '...'),
+			'entry_author' => (int)$aParentInfo['cmt_author_id'],
+			'subentry_sample' => '_cmt_txt_sample_reply_to',
+			'subentry_url' => $oComment->getViewUrl((int)$aEvent['subobject_id']),
+			'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+		);
+    }
+
+	/**
+     * Entry post vote for Notifications module
+     */
+    public function serviceGetNotificationsVote($aEvent)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$iContentId = (int)$aEvent['object_id'];
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+		$oVote = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $iContentId);
+        if(!$oVote || !$oVote->isEnabled())
+            return array();
+
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+        $sEntryCaption = isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...');
+
+		return array(
+			'entry_sample' => $CNF['T']['txt_sample_single'],
+			'entry_url' => $sEntryUrl,
+			'entry_caption' => $sEntryCaption,
+			'entry_author' => $aContentInfo[$CNF['FIELD_AUTHOR']],
+			'subentry_sample' => $CNF['T']['txt_sample_vote_single'],
+			'lang_key' => '', //may be empty or not specified. In this case the default one from Notification module will be used.
+		);
+    }
+
+    /**
+     * Data for Timeline module
+     */
+    public function serviceGetTimelineData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+        return array(
+            'handlers' => array(
+                array('group' => $sModule . '_object', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'added', 'module_name' => $sModule, 'module_method' => 'get_timeline_post', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_object', 'type' => 'update', 'alert_unit' => $sModule, 'alert_action' => 'edited'),
+                array('group' => $sModule . '_object', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'deleted')
+            ),
+            'alerts' => array(
+                array('unit' => $sModule, 'action' => 'added'),
+                array('unit' => $sModule, 'action' => 'edited'),
+                array('unit' => $sModule, 'action' => 'deleted'),
+            )
+        );
+    }
+
+    /**
+     * Entry post for Timeline module
+     */
+    public function serviceGetTimelinePost($aEvent, $aBrowseParams = array())
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($aEvent['object_id']);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return false;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $iUserId = $this->getUserId();
+        $iAuthorId = (int)$aContentInfo[$CNF['FIELD_AUTHOR']];
+        $iAuthorIdAbs = abs($iAuthorId);
+        if($iAuthorId < 0 && $iAuthorIdAbs == (int)$aEvent['owner_id'] && $iAuthorIdAbs != $iUserId)
+            return false;
+
+        //--- Views
+        $oViews = isset($CNF['OBJECT_VIEWS']) ? BxDolView::getObjectInstance($CNF['OBJECT_VIEWS'], $aEvent['object_id']) : null;
+
+        $aViews = array();
+        if ($oViews && $oViews->isEnabled())
+            $aViews = array(
+                'system' => $CNF['OBJECT_VIEWS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['views']
+            );
+
+        //--- Votes
+        $oVotes = isset($CNF['OBJECT_VOTES']) ? BxDolVote::getObjectInstance($CNF['OBJECT_VOTES'], $aEvent['object_id']) : null;
+
+        $aVotes = array();
+        if ($oVotes && $oVotes->isEnabled())
+            $aVotes = array(
+                'system' => $CNF['OBJECT_VOTES'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['votes']
+            );
+
+        //--- Scores
+        $oScores = isset($CNF['OBJECT_SCORES']) ? BxDolScore::getObjectInstance($CNF['OBJECT_SCORES'], $aEvent['object_id']) : null;
+
+        $aScores = array();
+        if ($oScores && $oScores->isEnabled())
+            $aScores = array(
+                'system' => $CNF['OBJECT_SCORES'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'score' => $aContentInfo['score']
+            );
+
+		//--- Reports
+        $oReports = isset($CNF['OBJECT_REPORTS']) ? BxDolReport::getObjectInstance($CNF['OBJECT_REPORTS'], $aEvent['object_id']) : null;
+
+        $aReports = array();
+        if ($oReports && $oReports->isEnabled())
+            $aReports = array(
+                'system' => $CNF['OBJECT_REPORTS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['reports']
+            );
+
+        //--- Comments
+        $oCmts = isset($CNF['OBJECT_COMMENTS']) ? BxDolCmts::getObjectInstance($CNF['OBJECT_COMMENTS'], $aEvent['object_id']) : null;
+
+        $aComments = array();
+        if($oCmts && $oCmts->isEnabled())
+            $aComments = array(
+                'system' => $CNF['OBJECT_COMMENTS'],
+                'object_id' => $aContentInfo[$CNF['FIELD_ID']],
+                'count' => $aContentInfo['comments']
+            );
+
+        //--- Title & Description
+        $sTitle = !empty($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : '';
+        if(empty($sTitle) && !empty($aContentInfo[$CNF['FIELD_TEXT']]))
+            $sTitle = $aContentInfo[$CNF['FIELD_TEXT']];
+
+        return array(
+            'owner_id' => $iAuthorId,
+            'icon' => !empty($CNF['ICON']) ? $CNF['ICON'] : '',
+            'sample' => isset($CNF['T']['txt_sample_single_with_article']) ? $CNF['T']['txt_sample_single_with_article'] : $CNF['T']['txt_sample_single'],
+            'sample_wo_article' => $CNF['T']['txt_sample_single'],
+            'sample_action' => isset($CNF['T']['txt_sample_action']) ? $CNF['T']['txt_sample_action'] : '',
+            'url' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]),
+            'content' => $this->_getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams), //a string to display or array to parse default template before displaying.
+            'allowed_view' => method_exists($this, 'checkAllowedView') ? $this->checkAllowedView($aContentInfo) : CHECK_ACTION_RESULT_ALLOWED,
+            'date' => $aContentInfo[$CNF['FIELD_ADDED']],
+            'views' => $aViews,
+            'votes' => $aVotes,
+            'scores' => $aScores,
+            'reports' => $aReports,
+            'comments' => $aComments,
+            'title' => $sTitle, //may be empty.
+            'description' => '' //may be empty.
+        );
+    }
+
+    /**
+     * Check particular action permission without content
+     * @param $sAction action to check, for example: Browse, Add
+     * @param $iContentId content ID
+     * @return message on error, or CHECK_ACTION_RESULT_ALLOWED when allowed
+     */ 
+    public function serviceCheckAllowed($sAction, $isPerformAction = false)
+    {
+        $sMethod = 'checkAllowed' . bx_gen_method_name($sAction);
+        if (!method_exists($this, $sMethod))
+            return _t('_sys_request_method_not_found_cpt');
+
+        return $this->$sMethod($isPerformAction);
+    }
+    
+    /**
+     * Check particular action permission with content
+     * @param $sAction action to check, for example: View, Edit
+     * @param $iContentId content ID
+     * @return message on error, or CHECK_ACTION_RESULT_ALLOWED when allowed
+     */ 
+    public function serviceCheckAllowedWithContent($sAction, $iContentId, $isPerformAction = false)
+    {
+        if (!$iContentId || !($aContentInfo = $this->_oDb->getContentInfoById($iContentId)))
+            return _t('_sys_request_page_not_found_cpt');
+
+        $sMethod = 'checkAllowed' . bx_gen_method_name($sAction);
+        if (!method_exists($this, $sMethod))
+            return _t('_sys_request_method_not_found_cpt');
+
+        return $this->$sMethod($aContentInfo, $isPerformAction);
+    }
+    
+    public function serviceCheckAllowedCommentsView($iContentId, $sObjectComments) 
+    {
+        return $this->serviceCheckAllowedWithContent('comments_view', $iContentId);
+    }
+    
+    public function serviceCheckAllowedCommentsPost($iContentId, $sObjectComments) 
+    {
+        return $this->serviceCheckAllowedWithContent('comments_post', $iContentId);
+    }
+
+    public function serviceGetContentOwnerProfileId ($iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // file owner must be author of the content or profile itself in case of profile based module
+        if ($iContentId) {
+            if ($this instanceof iBxDolProfileService) {
+                $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+            }
+            else {
+                $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+                $oProfile = $aContentInfo ? BxDolProfile::getInstance($aContentInfo[$CNF['FIELD_AUTHOR']]) : null;
+            }
+            $iProfileId = $oProfile ? $oProfile->id() : bx_get_logged_profile_id();
+        }
+        else {
+            $iProfileId = bx_get_logged_profile_id();
+        }
+
+        return $iProfileId;
+    }
+    
+    // ====== PERMISSION METHODS
+
+    public function checkAllowedSetThumb ($iContentId = 0)
+    {
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+    
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make "true === " checking.
+     */
+    public function checkAllowedBrowse ()
+    {
+        // check alert to allow custom checks
+        $mixedResult = null;
+        bx_alert('system', 'check_allowed_browse', 0, 0, array('module' => $this->getName(), 'profile_id' => $this->_iProfileId, 'override_result' => &$mixedResult));
+        if($mixedResult !== null)
+            return $mixedResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = $this->_iProfileId;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        // moderator and owner always have access
+        if (!empty($iProfileId) && (abs($aDataEntry[$CNF['FIELD_AUTHOR']]) == (int)$iProfileId || $this->_isModerator($isPerformAction)))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($iProfileId, 'view entry', $this->getName(), $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        // check privacy
+        if (!empty($CNF['OBJECT_PRIVACY_VIEW'])) {
+            $oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_VIEW']);
+            if ($oPrivacy && !$oPrivacy->check($aDataEntry[$CNF['FIELD_ID']], $iProfileId))
+                return _t('_sys_access_denied_to_private_content');
+        }
+
+        // check alert to allow custom checks
+        $mixedResult = null;
+        bx_alert('system', 'check_allowed_view', 0, 0, array('module' => $this->getName(), 'content_info' => $aDataEntry, 'profile_id' => $iProfileId, 'override_result' => &$mixedResult));
+        if($mixedResult !== null)
+            return $mixedResult;
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedAdd ($isPerformAction = false)
+    {
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'create entry', $this->getName(), $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedEdit ($aDataEntry, $isPerformAction = false)
+    {
+        // moderator and owner always have access
+        if ($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || -$aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || $this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedDelete (&$aDataEntry, $isPerformAction = false)
+    {
+        // moderator always has access
+        if ($this->_isModerator($isPerformAction))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'delete entry', $this->getName(), $isPerformAction);
+        if (($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || -$aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId) && $aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED)
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedSetMembership (&$aDataEntry, $isPerformAction = false)
+    {
+        // admin always has access
+        if (isAdmin())
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check ACL
+        $aCheck = checkActionModule($this->_iProfileId, 'set acl level', 'system', $isPerformAction);
+        if ($aCheck[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheck[CHECK_ACTION_MESSAGE];
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make strict(===) checking.
+     */
+    public function checkAllowedEditAnyEntry ($isPerformAction = false)
+    {
+    	$aCheck = checkActionModule($this->_iProfileId, 'edit any entry', $this->getName(), $isPerformAction);
+    	if($aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED)
+    		return CHECK_ACTION_RESULT_ALLOWED;
+
+    	return _t('_sys_txt_access_denied');
+    }
+
+	/**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make "true === " checking.
+     */
+    public function checkAllowedCommentsView ($aContentInfo, $isPerformAction = false)
+    {
+        return $this->checkAllowedView ($aContentInfo, $isPerformAction);
+    }
+
+	/**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden. So make sure to make "true === " checking.
+     */
+    public function checkAllowedCommentsPost ($aContentInfo, $isPerformAction = false)
+    {
+        return $this->checkAllowedView ($aContentInfo, $isPerformAction);
+    }
+
+    public function _serviceBrowse ($sMode, $aParams = false, $iDesignBox = BX_DB_PADDING_DEF, $bDisplayEmptyMsg = false, $bAjaxPaginate = true, $sClassSearchResult = 'SearchResult')
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedBrowse()))
+            return MsgBox($sMsg);
+
+        bx_import($sClassSearchResult, $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . $sClassSearchResult;
+        $o = new $sClass($sMode, $aParams);
+
+        $o->setDesignBoxTemplateId($iDesignBox);
+        $o->setDisplayEmptyMsg($bDisplayEmptyMsg);
+        $o->setAjaxPaginate($bAjaxPaginate);
+        $o->setUnitParams(array('context' => $sMode));
+
+        if ($o->isError)
+            return '';
+
+        if ($s = $o->processing())
+            return $s;
+        else
+            return '';
+    }
+
+	// ====== COMMON METHODS
+
+    public function getEntryImageData($aContentInfo, $sField = 'FIELD_THUMB', $aTranscoders = array())
+    {
+        if(empty($aTranscoders))
+            $aTranscoders = array('OBJECT_TRANSCODER_COVER', 'OBJECT_IMAGES_TRANSCODER_COVER', 'OBJECT_IMAGES_TRANSCODER_GALLERY');
+
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF[$sField]) || empty($aContentInfo[$CNF[$sField]]) || empty($CNF['OBJECT_STORAGE']))
+            return false;
+
+        $iId = (int)$aContentInfo[$CNF[$sField]];
+        foreach($aTranscoders as $sTranscoder)
+            if(!empty($CNF[$sTranscoder]))
+                return array('id' => $iId, 'transcoder' => $CNF[$sTranscoder]);
+
+        return array('id' => $iId, 'object' => $CNF['OBJECT_STORAGE']);
+    }
+
+    public function getProfileId()
+    {
+    	return bx_get_logged_profile_id();
+    }
+
+	public function getProfileInfo($iUserId = 0)
+    {
+        $oProfile = $this->getObjectUser($iUserId);
+
+        $oAccount = null;
+        if($oProfile && !($oProfile instanceof BxDolProfileUndefined) && !($oProfile instanceof BxDolProfileAnonymous))
+            $oAccount = $oProfile->getAccountObject();
+        $bAccount = !empty($oAccount);
+
+        return array(
+        	'id' => $oProfile->id(),
+            'name' => $oProfile->getDisplayName(),
+        	'email' => $bAccount ? $oAccount->getEmail() : '',
+            'link' => $oProfile->getUrl(),
+            'icon' => $oProfile->getIcon(),
+        	'thumb' => $oProfile->getThumb(),
+        	'avatar' => $oProfile->getAvatar(),
+            'unit' => $oProfile->getUnit(),
+        	'active' => $oProfile->isActive(),
+        );
+    }
+    
+    public function getObjectUser($iUserId = 0)
+    {
+    	bx_import('BxDolProfile');
+        $oProfile = BxDolProfile::getInstanceMagic($iUserId);
+        return $oProfile;
+    }
+
+    public function getObjectFavorite($sSystem = '', $iId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($sSystem) && !empty($CNF['OBJECT_FAVORITES']))
+            $sSystem = $CNF['OBJECT_FAVORITES'];
+
+        if(empty($sSystem))
+            return false;
+
+        $oFavorite = BxDolFavorite::getObjectInstance($sSystem, $iId, true, $this->_oTemplate);
+        if(!$oFavorite->isEnabled())
+            return false;
+
+        return $oFavorite;
+    }
+
+	public function getUserId()
+    {
+        return isLogged() ? bx_get_logged_profile_id() : 0;
+    }
+
+    public function getUserIp()
+    {
+        return getVisitorIP();
+    }
+
+    public function getUserInfo($iUserId = 0)
+    {
+        $oProfile = BxDolProfile::getInstanceMagic($iUserId);
+
+        return array(
+            $oProfile->getDisplayName(),
+            $oProfile->getUrl(),
+            $oProfile->getThumb(),
+            $oProfile->getUnit(),
+            $oProfile->getUnit(0, array('template' => 'unit_wo_info'))
+        );
+    }
+
+    public function isMenuItemVisible($sObject, &$aItem, &$aContentInfo)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        // default visible settings
+        if(!BxDolAcl::getInstance()->isMemberLevelInSet($aItem['visible_for_levels']))
+            return false;
+
+        if (!empty($aItem['visibility_custom'])) {
+            $oMenu = BxDolMenu::getObjectInstance($sObject);
+            if ($oMenu && !BxDolService::callSerialized($aItem['visibility_custom'], $oMenu->getMarkers()))
+                return false;
+        }
+        
+        // get custom function name to check menu item visibility
+        $sFuncCheckAccess = false;
+        if(isset($CNF['MENU_ITEM_TO_METHOD'][$sObject][$aItem['name']]))
+            $sFuncCheckAccess = $CNF['MENU_ITEM_TO_METHOD'][$sObject][$aItem['name']];
+
+        // check custom visibility settings defined in module config class
+        if($sFuncCheckAccess && CHECK_ACTION_RESULT_ALLOWED !== call_user_func_array(array($this, $sFuncCheckAccess), isset($aContentInfo) ? array(&$aContentInfo) : array()))
+            return false;
+
+        return true;
+    }
+
+    public function _isModerator ($isPerformAction = false)
+    {
+        return CHECK_ACTION_RESULT_ALLOWED === $this->checkAllowedEditAnyEntry ($isPerformAction);
+    }
+
+    // ====== PROTECTED METHODS
+
+    protected function _serviceEntityForm ($sFormMethod, $iContentId = 0, $sDisplay = false, $sCheckFunction = false, $bErrorMsg = true)
+    {
+        $iContentId = $this->_getContent($iContentId, false);
+        if($iContentId === false)
+            return false;
+
+        bx_import('FormsEntryHelper', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'FormsEntryHelper';
+        $oFormsHelper = new $sClass($this);
+        return $oFormsHelper->$sFormMethod((int)$iContentId, $sDisplay, $sCheckFunction, $bErrorMsg);
+    }
+
+    protected function _serviceTemplateFunc ($sFunc, $iContentId, $sFuncGetContent = 'getContentInfoById')
+    {
+        $mixedContent = $this->_getContent($iContentId, $sFuncGetContent);
+        if($mixedContent === false)
+            return false;
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        return $this->_oTemplate->$sFunc($aContentInfo);
+    }
+
+    protected function _rss ($aArgs, $sClass = 'SearchResult')
+    {
+        $sMode = array_shift($aArgs);
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== ($sMsg = $this->checkAllowedBrowse())) {
+            $this->_oTemplate->displayAccessDenied ($sMsg);
+            exit;
+        }
+
+        $aParams = $this->_buildRssParams($sMode, $aArgs);
+
+        bx_import ($sClass, $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . $sClass;
+        $o = new $sClass($sMode, $aParams);
+
+        if ($o->isError)
+            $this->_oTemplate->displayPageNotFound ();
+        else
+            $o->outputRSS();
+
+        exit;
+    }
+
+    protected function _getContent($iContentId = 0, $sFuncGetContent = 'getContentInfoById')
+    {
+        if(!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if(!$iContentId)
+            return false;
+
+        if(empty($sFuncGetContent) || !method_exists($this->_oDb, $sFuncGetContent))
+            return $iContentId;
+
+        $aContentInfo = $this->_oDb->$sFuncGetContent($iContentId);
+        if(!$aContentInfo)
+            return false;
+
+        return array($iContentId, $aContentInfo);
+    }
+
+    protected function _getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams = array())
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	$sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+    	//--- Image(s)
+        $aImages = $this->_getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        //--- Video(s)
+        $aVideos = $this->_getVideosForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        //--- Text
+        $sText = isset($CNF['FIELD_TEXT']) && isset($aContentInfo[$CNF['FIELD_TEXT']]) ? $aContentInfo[$CNF['FIELD_TEXT']] : '';
+        if(!empty($CNF['OBJECT_METATAGS']) && is_string($sText)) {
+        	$oMetatags = BxDolMetatags::getObjectInstance($CNF['OBJECT_METATAGS']);
+        	$sText = $oMetatags->metaParse($aContentInfo[$CNF['FIELD_ID']], $sText);
+        }
+
+    	return array(
+    		'sample' => isset($CNF['T']['txt_sample_single_with_article']) ? $CNF['T']['txt_sample_single_with_article'] : $CNF['T']['txt_sample_single'],
+    		'sample_wo_article' => $CNF['T']['txt_sample_single'],
+    	    'sample_action' => isset($CNF['T']['txt_sample_action']) ? $CNF['T']['txt_sample_action'] : '',
+			'url' => $sUrl,
+			'title' => isset($CNF['FIELD_TITLE']) && isset($aContentInfo[$CNF['FIELD_TITLE']]) ? $aContentInfo[$CNF['FIELD_TITLE']] : 
+			(isset($CNF['FIELD_TEXT']) && isset($aContentInfo[$CNF['FIELD_TEXT']]) ? strmaxtextlen($aContentInfo[$CNF['FIELD_TEXT']], 20, '...') : ''),
+			'text' => $sText,
+			'images' => $aImages,
+            'videos' => $aVideos
+		);
+    }
+
+    protected function _getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sImage = $sImageOrig = '';
+        if(isset($CNF['FIELD_COVER']) && isset($aContentInfo[$CNF['FIELD_COVER']]) && $aContentInfo[$CNF['FIELD_COVER']]) {
+            $sImage = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_COVER']], array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_THUMB'));
+            $sImageOrig = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_COVER']], array('OBJECT_IMAGES_TRANSCODER_COVER'));
+        }
+
+        $bImageThumb = isset($CNF['FIELD_THUMB']) && isset($aContentInfo[$CNF['FIELD_THUMB']]) && $aContentInfo[$CNF['FIELD_THUMB']];
+        if($sImage == '' && $bImageThumb)
+            $sImage = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_THUMB']], array('OBJECT_IMAGES_TRANSCODER_GALLERY', 'OBJECT_IMAGES_TRANSCODER_THUMB'));
+
+        if($sImageOrig == '' && $bImageThumb)
+            $sImageOrig = $this->_oConfig->getImageUrl($aContentInfo[$CNF['FIELD_THUMB']], array('OBJECT_IMAGES_TRANSCODER_COVER'));
+
+        if(empty($sImage))
+            return array();
+
+        if($sImageOrig == '')
+            $sImageOrig = $sImage;
+
+        return array(
+		    array('url' => $sUrl, 'src' => $sImage, 'src_orig' => $sImageOrig),
+		);
+    }
+
+    protected function _getVideosForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        return array();
+    }
+
+    protected function _entityComments ($sObject, $iId = 0)
+    {
+        if (!$iId)
+            $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+
+        if (!$iId)
+            return false;
+
+        $oCmts = BxDolCmts::getObjectInstance($sObject, $iId);
+        if (!$oCmts || !$oCmts->isEnabled())
+            return false;
+
+        return $oCmts->getCommentsBlock(array(), array('in_designbox' => false, 'show_empty' => true));
+    }
+
+    protected function _getFields($iContentId)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo))
+            return array();
+
+        return BxDolContentInfo::formatFields($aContentInfo);
+    }
+
+    protected function _getFieldValue($sField, $iContentId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        if(empty($CNF[$sField]))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || empty($aContentInfo[$CNF[$sField]]))
+            return false;
+
+        return $aContentInfo[$CNF[$sField]];
+    }
+
+    protected function _getFieldValueThumb($sField, $iContentId, $sTranscoder = '') 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($sTranscoder) || empty($CNF[$sField]))
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || empty($aContentInfo[$CNF[$sField]]))
+            return false;
+
+        $oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($sTranscoder);
+        if(!$oImagesTranscoder)
+            return false;
+
+        return $oImagesTranscoder->getFileUrl($aContentInfo[$CNF[$sField]]);
+    }
+
+	protected function _prepareResponse($aResponse, $bAsJson = false, $aAdditional = array())
+    {
+    	if(!$bAsJson)
+    		return $aResponse;
+
+		if(!empty($aAdditional) && is_array($aAdditional))
+			$aResponse = array_merge($aResponse, $aAdditional);
+
+		echoJson($aResponse);
+		exit;
+    }
+}
+
+/** @} */

+ 683 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/groups/classes/BxBaseModGroupsModule.php

@@ -0,0 +1,683 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Groups profiles module.
+ */
+class BxBaseModGroupsModule extends BxBaseModProfileModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    /**
+     * Get possible recipients for start conversation form
+     */
+    public function actionAjaxGetInitialMembers ()
+    {
+        $sTerm = bx_get('term');
+
+        $a = BxDolService::call('system', 'profiles_search', array($sTerm), 'TemplServiceProfiles');
+
+        header('Content-Type:text/javascript; charset=utf-8');
+        echo(json_encode($a));
+    }
+
+    public function serviceGetSearchResultUnit ($iContentId, $sUnitTemplate = '')
+    {
+        if(empty($sUnitTemplate))
+            $sUnitTemplate = 'unit.html';
+
+        return parent::serviceGetSearchResultUnit($iContentId, $sUnitTemplate);
+    }
+
+    /**
+     * @see BxBaseModProfileModule::serviceGetSpaceTitle
+     */ 
+    public function serviceGetSpaceTitle()
+    {
+        return _t($this->_oConfig->CNF['T']['txt_sample_single']);
+    }
+    
+    /**
+     * @see iBxDolProfileService::serviceGetParticipatingProfiles
+     */ 
+    public function serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects = false)
+    {
+        if (isset($this->_oConfig->CNF['OBJECT_CONNECTIONS'])){
+            $aConnectionObjects = array($this->_oConfig->CNF['OBJECT_CONNECTIONS'], 'sys_profiles_subscriptions');
+            return parent::serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects);
+        }
+        return parent::serviceGetParticipatingProfiles($iProfileId);
+    }
+    
+    /**
+     * Check if this module entry can be used as profile
+     */
+    public function serviceActAsProfile ()
+    {
+        return false;
+    }
+
+    /**
+     * Check if this module is group profile
+     */
+    public function serviceIsGroupProfile ()
+    {
+        return true;
+    }
+
+    /**
+     * check if provided profile is member if the group 
+     */ 
+    public function serviceIsFan ($iGroupProfileId, $iProfileId = false) 
+    {
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        return $this->isFan($oGroupProfile->getContentId(), $iProfileId);
+    }
+
+    /**
+     * Delete profile from fans and admins tables
+     * @param $iProfileId profile id 
+     */
+    public function serviceDeleteProfileFromFansAndAdmins ($iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $this->_oDb->deleteAdminsByProfileId($iProfileId);
+
+        if (isset($CNF['OBJECT_CONNECTIONS']) && ($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])))
+            $oConnection->onDeleteInitiatorAndContent($iProfileId);
+    }
+
+    /**
+     * Reset group's author for particular group
+     * @param $iContentId group id 
+     * @return false of error, or number of updated records on success
+     */
+    public function serviceReassignEntityAuthor ($iContentId)
+    {
+        $aContentInfo = $this->_oDb->getContentInfoById((int)$iContentId);
+        if (!$aContentInfo)
+            return false;
+
+        if (!($oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName())))
+            return false;
+
+        $aAdmins = $this->_oDb->getAdmins($oGroupProfile->id());
+
+        return $this->_oDb->updateAuthorById($iContentId, $aAdmins ? array_pop($aAdmins) : 0);
+    }
+
+    /**
+     * Entry actions and social sharing block
+     */
+    public function serviceEntityAllActions ($mixedContent = false, $aParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+                $mixedContent = array((int)$mixedContent, (method_exists($this->_oDb, 'getContentInfoById')) ? $this->_oDb->getContentInfoById((int)$mixedContent) : array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        if(!empty($CNF['FIELD_PICTURE']) && !empty($aContentInfo[$CNF['FIELD_PICTURE']]))
+            $aParams = array_merge(array(
+                'entry_thumb' => (int)$aContentInfo[$CNF['FIELD_PICTURE']]
+            ), $aParams); 
+
+        return parent::serviceEntityAllActions ($mixedContent, $aParams);
+    }
+    
+    /**
+     * Reset group's author when author profile is deleted
+     * @param $iProfileId profile id 
+     * @return number of changed items
+     */
+    public function serviceReassignEntitiesByAuthor ($iProfileId)
+    {
+        $a = $this->_oDb->getEntriesByAuthor((int)$iProfileId);
+        if (!$a)
+            return 0;
+
+        $iCount = 0;
+        foreach ($a as $aContentInfo)
+            $iCount += ('' == $this->serviceReassignEntityAuthor($aContentInfo[$this->_oConfig->CNF['FIELD_ID']]) ? 1 : 0);
+
+        return $iCount;
+    }
+
+    public function servicePrepareFields ($aFieldsProfile)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        $aFieldsProfile[$CNF['FIELD_NAME']] = $aFieldsProfile['name'];
+        $aFieldsProfile[$CNF['FIELD_TEXT']] = isset($aFieldsProfile['description']) ? $aFieldsProfile['description'] : '';
+        unset($aFieldsProfile['name']);
+        unset($aFieldsProfile['description']);
+        return $aFieldsProfile;
+    }
+
+    public function serviceOnRemoveConnection ($iGroupProfileId, $iInitiatorId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        list ($iProfileId, $iGroupProfileId, $oGroupProfile) = $this->_prepareProfileAndGroupProfile($iGroupProfileId, $iInitiatorId);
+        if (!$oGroupProfile)
+            return false;
+
+        $this->_oDb->fromAdmins($iGroupProfileId, $iProfileId);
+
+        if ($oConn = BxDolConnection::getObjectInstance('sys_profiles_subscriptions'))
+            return $oConn->removeConnection($iProfileId, $iGroupProfileId);
+
+        return false;
+    }
+
+    public function serviceAddMutualConnection ($iGroupProfileId, $iInitiatorId, $bSendInviteOnly = false)
+    {        
+        $CNF = &$this->_oConfig->CNF;
+
+        list ($iProfileId, $iGroupProfileId, $oGroupProfile) = $this->_prepareProfileAndGroupProfile($iGroupProfileId, $iInitiatorId);
+        if (!$oGroupProfile)
+            return false;
+
+        if (!($aContentInfo = $this->_oDb->getContentInfoById((int)BxDolProfile::getInstance($iGroupProfileId)->getContentId())))
+            return false;
+
+        if (!($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])))
+            return false;
+
+        $sEntryTitle = $aContentInfo[$CNF['FIELD_NAME']];
+        $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aContentInfo[$CNF['FIELD_ID']]);
+
+        // send invitation to the group 
+        if ($bSendInviteOnly && !$oConnection->isConnected((int)$iInitiatorId, $oGroupProfile->id()) && !$oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId) && bx_get_logged_profile_id() != $iProfileId) {
+
+            bx_alert($this->getName(), 'join_invitation', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array('content' => $aContentInfo, 'entry_title' => $sEntryTitle, 'entry_url' => $sEntryUrl, 'group_profile' => $iGroupProfileId, 'profile' => $iProfileId, 'notification_subobject_id' => $iProfileId, 'object_author_id' => $iGroupProfileId));
+
+        }
+        // send notification to group's admins that new connection is pending confirmation 
+        elseif (!$bSendInviteOnly && $oConnection->isConnected((int)$iInitiatorId, $oGroupProfile->id()) && !$oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId) && $aContentInfo['join_confirmation']) {
+
+            bx_alert($this->getName(), 'join_request', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            	'object_author_id' => $iGroupProfileId,
+            	'performer_id' => $iProfileId, 
+
+            	'content' => $aContentInfo, 
+            	'entry_title' => $sEntryTitle, 
+            	'entry_url' => $sEntryUrl, 
+
+            	'group_profile' => $iGroupProfileId, 
+            	'profile' => $iProfileId
+            ));
+        }
+        // send notification that join request was accepted 
+        else if (!$bSendInviteOnly && $oConnection->isConnected((int)$iInitiatorId, $oGroupProfile->id(), true) && $oGroupProfile->getModule() != $this->getName() && bx_get_logged_profile_id() != $iProfileId) {
+            bx_alert($this->getName(), 'join_request_accepted', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            	'object_author_id' => $iGroupProfileId,
+            	'performer_id' => $iProfileId,
+
+            	'content' => $aContentInfo, 
+            	'entry_title' => $sEntryTitle, 
+            	'entry_url' => $sEntryUrl, 
+
+            	'group_profile' => $iGroupProfileId, 
+            	'profile' => $iProfileId
+            ));
+        }
+
+        // new fan was added
+        if ($oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId, true)) {
+            bx_alert($this->getName(), 'fan_added', $aContentInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            	'object_author_id' => $iGroupProfileId,
+            	'performer_id' => $iProfileId,
+
+            	'content' => $aContentInfo,
+            	'entry_title' => $sEntryTitle, 
+            	'entry_url' => $sEntryUrl,
+
+            	'group_profile' => $iGroupProfileId, 
+            	'profile' => $iProfileId,
+            ));
+            return false;
+        }
+
+        // don't automatically add connection (mutual) if group requires manual join confirmation
+        if ($bSendInviteOnly || $aContentInfo['join_confirmation'])
+            return false;
+
+        // check if connection already exists
+        if ($oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId, true) || $oConnection->isConnected($oGroupProfile->id(), (int)$iInitiatorId))
+            return false;
+
+        if (!$oConnection->addConnection($oGroupProfile->id(), (int)$iInitiatorId))
+            return false;
+
+        return true;
+    }
+
+    public function serviceFansTable ()
+    {
+        $oGrid = BxDolGrid::getObjectInstance($this->_oConfig->CNF['OBJECT_GRID_CONNECTIONS']);
+        if (!$oGrid)
+            return false;
+
+        return $oGrid->getCode();
+    }
+
+    public function serviceFans ($iContentId = 0, $bAsArray = false)
+    {
+        if (!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if (!$iContentId)
+            return false;
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if (!$aContentInfo)
+            return false;
+
+        if (!($oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName())))
+            return false;
+
+        if(!$bAsArray) {
+            bx_import('BxDolConnection');
+            $mixedResult = $this->serviceBrowseConnectionsQuick ($oGroupProfile->id(), $this->_oConfig->CNF['OBJECT_CONNECTIONS'], BX_CONNECTIONS_CONTENT_TYPE_CONTENT, true);
+            if (!$mixedResult)
+                return MsgBox(_t('_sys_txt_empty'));
+        }
+        else
+            $mixedResult = BxDolConnection::getObjectInstance($this->_oConfig->CNF['OBJECT_CONNECTIONS'])->getConnectedContent($oGroupProfile->id(), true);
+
+        return $mixedResult;
+    }
+
+    public function serviceAdmins ($iContentId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iContentId)
+            $iContentId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if(!$iContentId)
+            return false;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return false;
+
+        $aProfiles = $this->_oDb->getAdmins($oGroupProfile->id());
+        if(empty($aProfiles) || !is_array($aProfiles))
+            return false;
+
+        $iStart = (int)bx_get('start');
+        $iLimit = !empty($CNF['PARAM_NUM_CONNECTIONS_QUICK']) ? getParam($CNF['PARAM_NUM_CONNECTIONS_QUICK']) : 4;
+        if(!$iLimit)
+            $iLimit = 4;
+
+        return $this->_serviceBrowseQuick($aProfiles, $iStart, $iLimit);
+    }
+
+    public function serviceBrowseJoinedEntries ($iProfileId = 0, $bDisplayEmptyMsg = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId)
+            return '';
+
+        return $this->_serviceBrowse ('joined_entries', array('joined_profile' => $iProfileId), BX_DB_PADDING_DEF, $bDisplayEmptyMsg);
+    }
+
+    public function serviceBrowseCreatedEntries ($iProfileId = 0, $bDisplayEmptyMsg = false)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if (!$iProfileId)
+            return '';
+
+        return $this->_serviceBrowse ('created_entries', array('author' => $iProfileId), BX_DB_PADDING_DEF, $bDisplayEmptyMsg);
+    }
+
+    
+    public function serviceEntityInvite ($iContentId = 0, $bErrorMsg = true)
+    {
+		if (isset($this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_INVITE']))
+        	return $this->_serviceEntityForm ('editDataForm', $iContentId, $this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_INVITE'], false, $bErrorMsg);
+        return false;
+    }
+    
+    /**
+     * Entry social sharing block
+     */
+    public function serviceEntitySocialSharing ($mixedContent = false, $aParams = array())
+    {
+        if(!empty($mixedContent)) {
+            if(!is_array($mixedContent))
+               $mixedContent = array((int)$mixedContent, array());
+        }
+        else {
+            $mixedContent = $this->_getContent();
+            if($mixedContent === false)
+                return false;
+        }
+
+        list($iContentId, $aContentInfo) = $mixedContent;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return false;
+
+        return parent::serviceEntitySocialSharing(array($iContentId, $aContentInfo), array(
+            'title' => $oGroupProfile->getDisplayName()
+        ));
+    }
+
+	/**
+     * Data for Notifications module
+     */
+    public function serviceGetNotificationsData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+        $aSettingsTypes = array('follow_context');
+        if($this->serviceActAsProfile())
+            $aSettingsTypes[] = 'follow_member';
+
+        return array(
+            'handlers' => array(
+                array('group' => $sModule . '_vote', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'doVote', 'module_name' => $sModule, 'module_method' => 'get_notifications_vote', 'module_class' => 'Module'),
+                array('group' => $sModule . '_vote', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'undoVote'),
+
+                array('group' => $sModule . '_fan_added', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'fan_added', 'module_name' => $sModule, 'module_method' => 'get_notifications_fan_added', 'module_class' => 'Module'),
+
+                array('group' => $sModule . '_join_request', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'join_request', 'module_name' => $sModule, 'module_method' => 'get_notifications_join_request', 'module_class' => 'Module', 'module_event_privacy' => $this->_oConfig->CNF['OBJECT_PRIVACY_VIEW_NOTIFICATION_EVENT']),
+                
+                array('group' => $sModule . '_timeline_post_common', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'timeline_post_common', 'module_name' => $sModule, 'module_method' => 'get_notifications_timeline_post_common', 'module_class' => 'Module'),
+            ),
+            'settings' => array(
+                array('group' => 'vote', 'unit' => $sModule, 'action' => 'doVote', 'types' => $aSettingsTypes),
+                
+                array('group' => 'fan', 'unit' => $sModule, 'action' => 'fan_added', 'types' => $aSettingsTypes),
+
+                array('group' => 'join', 'unit' => $sModule, 'action' => 'join_request', 'types' => $aSettingsTypes),
+
+                array('group' => 'timeline_post', 'unit' => $sModule, 'action' => 'timeline_post_common', 'types' => $aSettingsTypes)
+            ),
+            'alerts' => array(
+                array('unit' => $sModule, 'action' => 'doVote'),
+                array('unit' => $sModule, 'action' => 'undoVote'),
+
+                array('unit' => $sModule, 'action' => 'fan_added'),
+
+                array('unit' => $sModule, 'action' => 'join_request'),
+
+                array('unit' => $sModule, 'action' => 'timeline_post_common'),
+            )
+        );
+    }
+
+    /**
+     * Notification about new member requst in the group
+     */
+    public function serviceGetNotificationsJoinRequest($aEvent)
+    {
+        return $this->_serviceGetNotification($aEvent, $this->_oConfig->CNF['T']['txt_ntfs_join_request']);
+    }
+
+	/**
+     * Notification about new member in the group
+     */
+    public function serviceGetNotificationsFanAdded($aEvent)
+    {
+        return $this->_serviceGetNotification($aEvent, $this->_oConfig->CNF['T']['txt_ntfs_fan_added']);
+    }
+
+    protected function _serviceGetNotification($aEvent, $sLangKey)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        $iContentId = (int)$aEvent['object_id'];
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType((int)$iContentId, $this->getName());
+        if(!$oGroupProfile)
+            return array();
+
+        $aContentInfo = $this->_oDb->getContentInfoById($iContentId);
+        if(empty($aContentInfo) || !is_array($aContentInfo))
+            return array();
+
+        $oProfile = BxDolProfile::getInstance((int)$aEvent['subobject_id']);
+        if(!$oProfile)
+            return array();
+
+        /*
+         * Note. Group Profile URL is used for both Entry and Subentry URLs, 
+         * because Subentry URL has higher display priority and notification
+         * should be linked to Group Profile (Group Profile -> Members tab) 
+         * instead of Personal Profile of a member, who performed an action.
+         */
+        $sEntryUrl = $oGroupProfile->getUrl();
+        if(!empty($CNF['URL_ENTRY_FANS']))
+            $sEntryUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($CNF['URL_ENTRY_FANS'], array(
+                'profile_id' => $oGroupProfile->id()
+            ));
+
+        return array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $sEntryUrl,
+            'entry_caption' => $oGroupProfile->getDisplayName(),
+            'entry_author' => $oGroupProfile->id(),
+            'subentry_sample' => $oProfile->getDisplayName(),
+            'subentry_url' => $sEntryUrl,
+            'lang_key' => $sLangKey
+        );
+    }
+
+    /**
+     * Data for Timeline module
+     */
+    public function serviceGetTimelineData()
+    {
+        return BxBaseModGeneralModule::serviceGetTimelineData();
+    }
+
+    /**
+     * Entry post for Timeline module
+     */
+    public function serviceGetTimelinePost($aEvent, $aBrowseParams = array())
+    {
+        $a = parent::serviceGetTimelinePost($aEvent, $aBrowseParams);
+        if($a === false)
+            return false;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aEvent['object_id'], $this->getName());
+
+        $a['content']['url'] = $oGroupProfile->getUrl();
+        $a['content']['title'] = $oGroupProfile->getDisplayName();
+
+        return $a;
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedPost ($aDataEntry, $isPerformAction = false)
+    {
+        if (isLogged() && ($this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]) || ($aDataEntry[$this->_oConfig->CNF['FIELD_ID']] == BxDolProfile::getInstance()->getContentId() && BxDolProfile::getInstance()->getModule() == $this->getName())))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        return _t('_sys_txt_access_denied');
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedView ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction);
+    }
+
+    public function serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction = false, $iProfileId = false)
+    {
+        if ($this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $iProfileId))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedCompose (&$aDataEntry, $isPerformAction = false)
+    {
+        if (!$this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]))
+            return _t('_sys_txt_access_denied');
+        
+        return parent::checkAllowedCompose ($aDataEntry, $isPerformAction);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFanAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if ($this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]) || !isLogged())
+            return _t('_sys_txt_access_denied');
+
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, $this->_oConfig->CNF['OBJECT_CONNECTIONS'], true, false);
+    }
+
+    /**
+     * @return CHECK_ACTION_RESULT_ALLOWED if access is granted or error message if access is forbidden.
+     */
+    public function checkAllowedFanRemove (&$aDataEntry, $isPerformAction = false)
+    {
+        if (CHECK_ACTION_RESULT_ALLOWED === $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, $this->_oConfig->CNF['OBJECT_CONNECTIONS'], false, true, true))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return $this->_checkAllowedConnect ($aDataEntry, $isPerformAction, $this->_oConfig->CNF['OBJECT_CONNECTIONS'], false, true, false);
+    }
+
+    public function checkAllowedManageAdmins ($mixedDataEntry, $isPerformAction = false)
+    {
+        if (is_array($mixedDataEntry)) {
+            $aDataEntry = $mixedDataEntry;
+        }
+        else {
+            $oGroupProfile = BxDolProfile::getInstance((int)$mixedDataEntry);
+            $aDataEntry = $oGroupProfile && $this->getName() == $oGroupProfile->getModule() ? $this->_oDb->getContentInfoById($oGroupProfile->getContentId()) : array();
+        }
+
+        return parent::checkAllowedEdit ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedEdit ($aDataEntry, $isPerformAction = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($this->_oDb->isAdmin($oGroupProfile->id(), bx_get_logged_profile_id(), $aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::checkAllowedEdit ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedInvite ($aDataEntry, $isPerformAction = false)
+    {
+        return $this->checkAllowedEdit ($aDataEntry, $isPerformAction);
+    }
+    
+    public function checkAllowedChangeCover ($aDataEntry, $isPerformAction = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($this->_oDb->isAdmin($oGroupProfile->id(), bx_get_logged_profile_id(), $aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::checkAllowedChangeCover ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedDelete (&$aDataEntry, $isPerformAction = false)
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$this->_oConfig->CNF['FIELD_ID']], $this->getName());
+        if ($oGroupProfile && $this->_oDb->isAdmin($oGroupProfile->id(), bx_get_logged_profile_id(), $aDataEntry))
+            return CHECK_ACTION_RESULT_ALLOWED;
+        return parent::checkAllowedDelete ($aDataEntry, $isPerformAction);
+    }
+
+    public function checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        if (!$this->isFan($aDataEntry[$this->_oConfig->CNF['FIELD_ID']]))
+            return _t('_sys_txt_access_denied');
+
+        return $this->_checkAllowedSubscribeAdd ($aDataEntry, $isPerformAction);
+    }
+    
+    public function _checkAllowedSubscribeAdd (&$aDataEntry, $isPerformAction = false)
+    {
+        return parent::checkAllowedSubscribeAdd ($aDataEntry, $isPerformAction);
+    }
+
+    protected function _checkAllowedConnect (&$aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap = false)
+    {
+        $sResult = $this->checkAllowedView($aDataEntry);
+
+        $oPrivacy = BxDolPrivacy::getObjectInstance($this->_oConfig->CNF['OBJECT_PRIVACY_VIEW']);
+
+        if (CHECK_ACTION_RESULT_ALLOWED !== $sResult && !in_array($aDataEntry[$this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']], array('c', 's')))
+            return $sResult;
+
+        return parent::_checkAllowedConnect ($aDataEntry, $isPerformAction, $sObjConnection, $isMutual, $isInvertResult, $isSwap);
+    }
+
+    public function isFan ($iContentId, $iProfileId = false) 
+    {
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        if (isset($this->_oConfig->CNF['OBJECT_CONNECTIONS']))
+            return $oGroupProfile && ($oConnection = BxDolConnection::getObjectInstance($this->_oConfig->CNF['OBJECT_CONNECTIONS'])) && $oConnection->isConnected($iProfileId ? $iProfileId : bx_get_logged_profile_id(), $oGroupProfile->id(), true);
+        return false;
+    }
+
+    protected function _getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aEvent['object_id'], $this->getName());
+
+        $sSrc = '';
+        if(isset($CNF['FIELD_COVER']) && !empty($aContentInfo[$CNF['FIELD_COVER']]))
+            $sSrc = $oGroupProfile->getCover();
+
+        if(empty($sSrc) && isset($CNF['FIELD_PICTURE']) && !empty($aContentInfo[$CNF['FIELD_PICTURE']]))
+             $sSrc = $oGroupProfile->getPicture();
+
+        return empty($sSrc) ? array() : array(
+		    array('url' => $sUrl, 'src' => $sSrc, 'src_orig' => $sSrc),
+		);
+    }
+
+    protected function _prepareProfileAndGroupProfile($iGroupProfileId, $iInitiatorId)
+    {
+        if (!($oGroupProfile = BxDolProfile::getInstance($iGroupProfileId)))
+            return array(0, 0, null);
+
+        if ($oGroupProfile->getModule() == $this->getName()) {
+            $iProfileId = $iInitiatorId;
+            $iGroupProfileId = $oGroupProfile->id();
+        } else {
+            $iProfileId = $oGroupProfile->id();
+            $iGroupProfileId = $iInitiatorId;
+        }
+
+        return array($iProfileId, $iGroupProfileId, $oGroupProfile);
+    }
+}
+
+/** @} */

+ 124 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/payment/classes/BxBaseModPaymentGridOrders.php

@@ -0,0 +1,124 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BasePayment Base classes for Payment like modules
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxBaseModPaymentGridOrders extends BxTemplGrid
+{
+    protected $MODULE;
+    protected $_oModule;
+
+    protected $_sOrdersType;
+
+    protected $_sLangsPrefix;
+    protected $_sCurrencySign;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+
+        $this->_sLangsPrefix = $this->_oModule->_oConfig->getPrefix('langs');
+        $this->_sCurrencySign = $this->_oModule->_oConfig->getDefaultCurrencySign();
+
+        $this->_sDefaultSortingOrder = 'DESC';
+
+	    $iSellerId = bx_get('seller_id');
+        if($iSellerId !== false)
+            $this->_aQueryAppend['seller_id'] = (int)$iSellerId;
+
+	    $iClientId = bx_get('client_id');
+        if($iClientId !== false)
+            $this->_aQueryAppend['client_id'] = (int)$iClientId;
+    }
+
+    public function addQueryParam($sKey, $sValue)
+    {
+    	if(empty($sKey) || !isset($sValue))
+    		return;
+
+		$this->_aQueryAppend[$sKey] = $sValue;
+    }
+
+    public function performActionViewOrder()
+    {
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) 
+        	return echoJson(array());
+
+        $iId = (int)$aIds[0];
+
+        $sKey = 'order_' . $this->_sOrdersType . '_view';
+    	$sId = $this->_oModule->_oConfig->getHtmlIds($this->_sOrdersType, $sKey);
+    	$sTitle = _t($this->_sLangsPrefix . 'popup_title_ods_' . $sKey);
+    	$sContent = $this->_oModule->getObjectOrders()->getOrder($this->_sOrdersType, $iId);
+
+    	return echoJson(array('popup' => BxTemplFunctions::getInstance()->popupBox($sId, $sTitle, $sContent)));
+    }
+
+    public function performActionCancel()
+    {
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) 
+        	return echoJson(array());
+
+		$oOrders = $this->_oModule->getObjectOrders();
+
+		$iAffected = 0;
+		$aAffected = array();
+		foreach($aIds as $iId)
+			if($oOrders->cancel($this->_sOrdersType, $iId)) {
+				$aAffected[] = $iId;
+            	$iAffected++;
+			}
+
+        echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aAffected) : array('msg' => _t($this->_sLangsPrefix . 'err_cannot_perform')));
+    }
+
+    protected function _getCellClientId($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_oModule->_oTemplate->displayProfileLink($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellSellerId($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_oModule->_oTemplate->displayProfileLink($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellItem($mixedValue, $sKey, $aField, $aRow)
+    {
+        $sTxtUnknown = _t('_uknown');
+
+        if(empty($aRow['module_id']) || empty($aRow['item_id']))
+            return $sTxtUnknown;
+
+        $aItemInfo = $this->_oModule->callGetCartItem((int)$aRow['module_id'], array((int)$aRow['item_id']));
+        if(empty($aItemInfo) || !is_array($aItemInfo))
+            return $sTxtUnknown;
+
+        return parent::_getCellDefault($this->_oModule->_oTemplate->displayLink('link', array(
+            'href' => $aItemInfo['url'],
+            'title' => bx_html_attribute($aItemInfo['title']),
+            'content' => $aItemInfo['title']
+    	)), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellAmount($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_sCurrencySign . $mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellDate($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(bx_time_js($mixedValue), $sKey, $aField, $aRow);
+    }
+}
+
+/** @} */

+ 199 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/modules/base/profile/classes/BxBaseModProfileMenuSnippetMeta.php

@@ -0,0 +1,199 @@
+<?php use Symfony\Component\Finder\Tests\FakeAdapter\DummyAdapter;
+defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseProfile Base classes for profile modules
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxBaseModProfileMenuSnippetMeta extends BxBaseModGeneralMenuSnippetMeta
+{
+    protected $_bContentPublic;
+    protected $_oContentProfile;
+
+    protected $_aConnectionToFunctionCheck;
+    protected $_aConnectionToFunctionTitle;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        parent::__construct($aObject, $oTemplate);
+
+        $this->_bShowZeros = true;
+        $this->_bContentPublic = false;
+
+        $this->_aConnectionToFunctionCheck = array(
+            'sys_profiles_friends' => array(
+            	'add' => 'checkAllowedFriendAdd', 
+            	'remove' => 'checkAllowedFriendRemove'
+            ),
+            'sys_profiles_subscriptions' => array(
+                'add' => 'checkAllowedSubscribeAdd',
+                'remove' => 'checkAllowedSubscribeRemove'
+            )
+        );
+
+        $this->_aConnectionToFunctionTitle = array(
+        	'sys_profiles_friends' => '_getMenuItemProfilesFriendsTitle',
+            'sys_profiles_subscriptions' => '_getMenuItemProfilesSubscriptionsTitle'
+        );
+    }
+
+    public function setContentId($iContentId)
+    {
+        parent::setContentId($iContentId);
+
+        if(!empty($this->_iContentId))
+            $this->_oContentProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->_sModule);
+    }
+
+    public function setContentPublic($bContentPublic)
+    {
+        $this->_bContentPublic = $bContentPublic;
+    }
+
+    protected function _getMenuItemBefriend($aItem)
+    {
+        return $this->_getMenuItemConnection('sys_profiles_friends', 'add', $aItem);
+    }
+
+    protected function _getMenuItemUnfriend($aItem)
+    {
+        return $this->_getMenuItemConnection('sys_profiles_friends', 'remove', $aItem);
+    }
+
+    protected function _getMenuItemSubscribe($aItem)
+    {
+        return $this->_getMenuItemConnection('sys_profiles_subscriptions', 'add', $aItem);
+    }
+
+    protected function _getMenuItemUnsubscribe($aItem)
+    {
+        return $this->_getMenuItemConnection('sys_profiles_subscriptions', 'remove', $aItem);
+    }
+
+    protected function _getMenuItemFriends($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!$this->_bContentPublic || !$this->_oContentProfile)
+            return false;
+
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_friends');
+        if(!$oConnection)
+            return false;
+
+        $iFriends = $oConnection->getConnectedInitiatorsCount($this->_oContentProfile->id(), true);
+        if(!$iFriends && !$this->_bShowZeros)
+            return false;
+
+        return $this->getUnitMetaItemText(_t('_sys_menu_item_title_sm_friends', $iFriends));
+    }
+
+    protected function _getMenuItemSubscribers($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!$this->_bContentPublic || !$this->_oContentProfile)
+            return false;
+
+        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
+        if(!$oConnection)
+            return false;
+            
+        $iSubscribers = $oConnection->getConnectedInitiatorsCount($this->_oContentProfile->id());
+        if(!$iSubscribers && !$this->_bShowZeros)
+            return false;
+
+        return $this->getUnitMetaItemText(_t('_sys_menu_item_title_sm_subscribers', $iSubscribers));
+    }
+
+    protected function _getMenuItemConnection($sConnection, $sAction, &$aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!isLogged() || $this->_oModule->{$this->_aConnectionToFunctionCheck[$sConnection][$sAction]}($this->_aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+
+        $iProfile = bx_get_logged_profile_id();
+        $iContentProfile = $this->_oContentProfile->id();
+
+        $oConnection = BxDolConnection::getObjectInstance($sConnection);
+        if(!$oConnection)
+            return false;
+
+        $sTitle = $this->{$this->_aConnectionToFunctionTitle[$sConnection]}($sAction, $oConnection);
+        if(empty($sTitle))
+            return false;
+
+        return $this->getUnitMetaItemButtonSmall($sTitle, array(
+            'onclick' => 'bx_conn_action(this, \'' . $sConnection . '\', \'' . $sAction . '\', \'' . $iContentProfile . '\', false, function(oData, eLink) {$(eLink).parents(\'.bx-menu-item:first\').remove();})'
+        ));
+    }
+
+    protected function _getMenuItemProfilesFriendsTitle($sAction, &$oConnection)
+    {
+        $iProfile = bx_get_logged_profile_id();
+        $iContentProfile = $this->_oContentProfile->id();
+
+        $aResult = array();
+        if($oConnection->isConnectedNotMutual($iProfile, $iContentProfile))
+            $aResult = array(
+                'add' => '',
+                'remove' => _t('_sys_menu_item_title_sm_unfriend_cancel'),
+            );
+        else if($oConnection->isConnectedNotMutual($iContentProfile, $iProfile))
+            $aResult = array(
+                'add' => _t('_sys_menu_item_title_sm_befriend_confirm'),
+                'remove' => _t('_sys_menu_item_title_sm_unfriend_reject'),
+            );
+        else if($oConnection->isConnected($iProfile, $iContentProfile, true))
+            $aResult = array(
+                'add' => '',
+                'remove' => _t('_sys_menu_item_title_sm_unfriend'),
+            );
+        else
+            $aResult = array(
+                'add' => _t('_sys_menu_item_title_sm_befriend'),
+                'remove' => '',
+            );
+
+        return !empty($sAction) && isset($aResult[$sAction]) ? $aResult[$sAction] : $aResult;
+    }
+
+    protected function _getMenuItemProfilesSubscriptionsTitle($sAction, &$oConnection)
+    {
+        $iProfile = bx_get_logged_profile_id();
+        $iContentProfile = $this->_oContentProfile->id();
+
+        $aResult = array();
+        if($oConnection->isConnected($iProfile, $iContentProfile))
+            $aResult = array(
+                'add' => '',
+                'remove' => _t('_sys_menu_item_title_sm_unsubscribe'),
+            );
+        else
+            $aResult = array(
+                'add' => _t('_sys_menu_item_title_sm_subscribe'),
+                'remove' => '',
+            );
+
+        return !empty($sAction) && !empty($aResult[$sAction]) ? $aResult[$sAction] : $aResult;
+    }
+
+    protected function _getMenuItemMembership($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!$this->_bContentPublic || !$this->_oContentProfile)
+            return false;
+
+        $aMembership = BxDolAcl::getInstance()->getMemberMembershipInfo($this->_oContentProfile->id());
+        return $aMembership ? $this->getUnitMetaItemText(_t($aMembership['name'])) : false;
+    }
+}
+
+/** @} */

+ 157 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/classes/BxDolStudioFormsField.php

@@ -0,0 +1,157 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+bx_import('BxDolStudioForm');
+
+class BxDolStudioFormsField extends BxDol
+{
+    protected $oDb;
+    protected $aTypes = array();
+    protected $aTypesRelated = array();
+
+    protected $sType = '';
+    protected $aCheckFunctions = array();
+    protected $sDbPass = '';
+    protected $aDbPassDependency = array();
+
+    protected $aParams = array();
+    protected $aField = array();
+    protected $iFieldNameMaxLen;
+
+    public function __construct($aParams = array(), $aField = array())
+    {
+        parent::__construct();
+
+        $this->oDb = new BxDolStudioFormsQuery();
+
+        $this->aTypes = array(
+        	'block_header' => array('add' => 1), 
+        	'text' => array('add' => 1), 
+        	'datepicker' => array('add' => 1), 
+        	'datetime' => array('add' => 1), 
+        	'number' => array('add' => 1), 
+        	'checkbox' => array('add' => 1), 
+        	'password' => array('add' => 1), 
+        	'slider' => array('add' => 1), 
+        	'doublerange' => array('add' => 1), 
+        	'hidden' => array('add' => 1), 
+        	'switcher' => array('add' => 1), 
+        	'reset' => array('add' => 1), 
+        	'submit' => array('add' => 1), 
+        	'textarea' => array('add' => 1), 
+        	'select' => array('add' => 1), 
+        	'select_multiple' => array('add' => 1), 
+        	'checkbox_set' => array('add' => 1), 
+        	'radio_set' => array('add' => 1), 
+        	'value' => array('add' => 1), 
+        	'file' => array('add' => 1), 
+        	'files' => array('add' => 1),
+        	'captcha' => array('add' => 0),
+        	'location' => array('add' => 0), 
+        	'custom' => array('add' => 0)
+        );
+
+        $this->aTypesRelated = array(
+            'select_multiple' => array('types' => array('select_multiple', 'checkbox_set'), 'reload_on_change' => 0),
+            'checkbox_set' => array('types' => array('select_multiple', 'checkbox_set'), 'reload_on_change' => 0),
+
+            'select' => array('types' => array('select', 'radio_set'), 'reload_on_change' => 0),
+            'radio_set' => array('types' => array('select', 'radio_set'), 'reload_on_change' => 0),
+
+            'datepicker' => array('types' => array('datepicker', 'datetime'), 'reload_on_change' => 1),
+            'datetime' => array('types' => array('datepicker', 'datetime'), 'reload_on_change' => 1),
+
+            'checkbox' => array('types' => array('checkbox', 'switcher'), 'reload_on_change' => 0),
+            'switcher' => array('types' => array('checkbox', 'switcher'), 'reload_on_change' => 0),
+
+            'number' => array('types' => array('number', 'slider'), 'reload_on_change' => 1),
+            'slider' => array('types' => array('number', 'slider'), 'reload_on_change' => 1),
+        );
+
+        $this->aParams = $aParams;
+        $this->aField = $aField;
+        $this->iFieldNameMaxLen = 32;
+    }
+
+    public function init()
+    {
+    	if(isset($this->aParams['object']) && isset($this->aParams['display'])) {
+            $aForm = array();
+            $this->oDb->getForms(array('type' => 'by_object_display', 'object' => $this->aParams['object'], 'display' => $this->aParams['display']), $aForm, false);
+
+            $this->aParams['table'] = $aForm['table'];
+        }
+    }
+
+    public function canAdd()
+    {
+        return isset($this->aParams['table']) && !empty($this->aParams['table']);
+    }
+
+    public function isField($sName)
+    {
+        if(!isset($this->aParams['table_alter']) || $this->aParams['table_alter'] !== true)
+            return false;
+
+        if(!isset($this->aParams['table']))
+            return false;
+
+        return $this->oDb->isFieldExists($this->aParams['table'], $sName);
+    }
+
+    public function alterAdd($sName)
+    {
+        if(!isset($this->aParams['table_alter']) || $this->aParams['table_alter'] !== true)
+            return true;
+
+        if(!isset($this->aParams['table'], $this->aParams['table_field_type']))
+            return false;
+
+        return $this->oDb->alterAdd($this->aParams['table'], $sName, $this->aParams['table_field_type']);
+    }
+
+    public function alterChange($sNameOld, $sNameNew)
+    {
+        if(!isset($this->aParams['table_alter']) || $this->aParams['table_alter'] !== true)
+            return true;
+
+        if(!isset($this->aParams['table'], $this->aParams['table_field_type']))
+            return false;
+
+        return $this->oDb->alterChange($this->aParams['table'], $sNameOld, $sNameNew, $this->aParams['table_field_type']);
+    }
+
+    public function alterRemove($sName)
+    {
+        if(!isset($this->aParams['table_alter']) || $this->aParams['table_alter'] !== true)
+            return true;
+
+        if(!isset($this->aParams['table']))
+            return false;
+
+        return $this->oDb->alterRemove($this->aParams['table'], $sName);
+    }
+
+    protected function getSystemName($sValue)
+    {
+        return BxDolStudioUtils::getSystemName($sValue);
+    }
+
+    protected function getClassName($sValue)
+    {
+        return BxDolStudioUtils::getClassName($sValue);
+    }
+
+    protected function addInArray($aInput, $sKey, $aValues, $bAddAfter = true)
+    {
+        return BxDolStudioUtils::addInArray($aInput, $sKey, $aValues, $bAddAfter);
+    }
+}
+
+/** @} */

+ 707 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/classes/BxDolStudioFormsQuery.php

@@ -0,0 +1,707 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+class BxDolStudioFormsQuery extends BxDolDb
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    function getForms($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tf`.`title` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tf`.`id`=:id ";
+                break;
+
+            case 'by_object_display':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'object' => $aParams['object'],
+                	'display_name' => $aParams['display']
+                );
+
+                $sJoinClause = "LEFT JOIN `sys_form_displays` AS `td` ON `tf`.`object`=`td`.`object` ";
+                $sWhereClause = "AND `td`.`object`=:object AND `td`.`display_name`=:display_name ";
+                break;
+
+            case 'by_module':
+            	$aMethod['params'][1] = array(
+                	'module' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `tf`.`module`=:module";
+                break;
+
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `tf`.`module`";
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tf`.`id` AS `id`,
+                `tf`.`object` AS `object`,
+                `tf`.`module` AS `module`,
+                `tf`.`title` AS `title`,
+                `tf`.`action` AS `action`,
+                `tf`.`form_attrs` AS `form_attrs`,
+                `tf`.`table` AS `table`,
+                `tf`.`key` AS `key`,
+                `tf`.`uri` AS `uri`,
+                `tf`.`uri_title` AS `uri_title`,
+                `tf`.`submit_name` AS `submit_name`,
+                `tf`.`params` AS `params`,
+                `tf`.`deletable` AS `deletable`,
+                `tf`.`active` AS `active`,
+                `tf`.`override_class_name` AS `override_class_name`,
+                `tf`.`override_class_file` AS `override_class_file`" . $sSelectClause . "
+            FROM `sys_objects_form` AS `tf` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function getDisplays($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `td`.`title` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `td`.`id`=:id ";
+                $sLimitClause = "LIMIT 1";
+                break;
+
+            case 'by_object_display':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'object' => $aParams['object'],
+                	'display_name' => $aParams['display']
+                );
+
+                $sWhereClause = " AND `td`.`object`=:object AND `td`.`display_name`=:display_name ";
+                $sLimitClause = "LIMIT 1";
+                break;
+
+            case 'by_name':
+            	$aMethod['params'][1] = array(
+                	'display_name' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `td`.`display_name`=:display_name ";
+                $sLimitClause = "LIMIT 1";
+                break;
+
+            case 'by_module':
+            	$aMethod['params'][1] = array(
+                	'module' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `td`.`module`=:module ";
+                break;
+
+            case 'by_module_with_forms':
+            	$aMethod['params'][1] = array(
+                	'module' => $aParams['value']
+                );
+
+                $sSelectClause = ", `tf`.`title` AS `form_title`";
+                $sJoinClause = "LEFT JOIN `sys_objects_form` AS `tf` ON `td`.`object`=`tf`.`object` ";
+                $sWhereClause = " AND `td`.`module`=:module ";
+                $sOrderClause = "ORDER BY `td`.`object` ASC, `td`.`title` ASC";
+                break;
+
+            case 'by_object':
+            	$aMethod['params'][1] = array(
+                	'object' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `td`.`object`=:object ";
+                break;
+
+            case 'counter_by_forms':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'object';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `td`.`object`";
+                break;
+
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `td`.`module`";
+                break;
+
+            case 'all':
+                break;
+
+            case 'all_with_forms':
+                $sSelectClause = ", `tf`.`title` AS `form_title`";
+                $sJoinClause = "LEFT JOIN `sys_objects_form` AS `tf` ON `td`.`object`=`tf`.`object` ";
+                $sOrderClause = "ORDER BY `td`.`object` ASC, `td`.`title` ASC";
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `td`.`id` AS `id`,
+                `td`.`object` AS `object`,
+                `td`.`display_name` AS `name`,
+                `td`.`display_name` AS `display_name`,
+                `td`.`module` AS `module`,
+                `td`.`view_mode` AS `view_mode`,
+                `td`.`title` AS `title`" . $sSelectClause . "
+            FROM `sys_form_displays` AS `td` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function isInput($sObject, $sName)
+    {
+        $sSql = $this->prepare("SELECT `id` FROM `sys_form_inputs` WHERE `object`=? AND `name`=? LIMIT 1", $sObject, $sName);
+        return (int)$this->getOne($sSql) > 0;
+    }
+
+    function getInputs($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sFromClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        $sSelectClause = "
+            `tdi`.`id` AS `id`,
+            `ti`.`name` AS `name`,
+            `ti`.`type` AS `type`,
+            `ti`.`caption_system` AS `caption_system`,
+            `ti`.`caption` AS `caption`,
+            `tdi`.`visible_for_levels` AS `visible_for_levels`,
+            `tdi`.`active` AS `active`,
+            `tdi`.`order` AS `order`";
+
+        $sFromClause = "`sys_form_display_inputs` AS `tdi`";
+        $sJoinClause = "LEFT JOIN `sys_form_inputs` AS `ti` ON `tdi`.`input_name`=`ti`.`name` ";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tdi`.`order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tdi`.`id`=:id ";
+                break;
+
+            case 'by_object_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'object' => $aParams['object'],
+                	'id' => $aParams['id']
+                );
+
+                $sSelectClause = "`ti`.*, `tdi`.`id` AS `di_id`, `tdi`.`display_name` AS `display_name`, `tdi`.`visible_for_levels` AS `visible_for_levels`";
+                $sWhereClause = " AND `ti`.`object`=:object AND `tdi`.`id`=:id ";
+                break;
+
+            case 'by_object_display':
+            	$aMethod['params'][1] = array(
+                	'object' => $aParams['object'],
+                	'display_name' => $aParams['display']
+                );
+
+                $sWhereClause = " AND `ti`.`object`=:object AND `tdi`.`display_name`=:display_name ";
+
+                if(isset($aParams['active'])) {
+                	$aMethod['params'][1]['active'] = (int)$aParams['active'];
+                	$sWhereClause .= " AND `tdi`.`active`=:active ";
+                }
+                break;
+
+            case 'by_object_name_filter':
+                $aMethod['name'] = 'getColumn';
+                $aMethod['params'][1] = array(
+                	'object' => $aParams['object'],
+                	'name_filter' => $aParams['name_filter']
+                );
+
+                $sSelectClause = "DISTINCT `ti`.`name` AS `name`";
+                $sWhereClause = " AND `ti`.`object`=:object AND `ti`.`name` LIKE (:name_filter) ";
+                $sOrderClause = "ORDER BY LENGTH(`ti`.`name`) ASC, `ti`.`name` ASC";
+                break;
+
+            case 'dump_inputs':
+            	$aMethod['params'][1] = array(
+                	'object' => $aParams['value']
+                );
+
+                $sSelectClause = "`ti`.*";
+                $sFromClause = "`sys_form_inputs` AS `ti`";
+                $sJoinClause = "";
+                $sWhereClause = " AND `ti`.`object`=:object ";
+                $sOrderClause = "";
+                break;
+
+            case 'dump_connections':
+            	$aMethod['params'][1] = array(
+                	'object' => $aParams['value']
+                );
+
+                $sSelectClause = "`tdi`.*";
+                $sJoinClause .= "LEFT JOIN `sys_form_displays` AS `td` ON `tdi`.`display_name`=`td`.`display_name` ";
+                $sWhereClause = " AND `td`.`object`=:object AND `ti`.`object`=:object ";
+                $sOrderClause = "ORDER BY `tdi`.`display_name` ASC, `tdi`.`order` ASC";
+                break;
+
+            case 'counter_by_displays':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'display_name';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = "`tdi`.`display_name` AS `display_name`, COUNT(`tdi`.`display_name`) AS `counter`";
+                $sJoinClause = "";
+                $sGroupClause = "GROUP BY `tdi`.`display_name`";
+                break;
+
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = "`ti`.`module` AS `module`, COUNT(*) AS `counter`";
+                $sFromClause = "`sys_form_inputs` AS `ti`";
+                $sJoinClause = "";
+                $sGroupClause = "GROUP BY `ti`.`module`";
+                $sOrderClause = "";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . " " . $sSelectClause . "
+            FROM " . $sFromClause . " " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function deleteInputs($aParams)
+    {
+    	$aBindings = array();
+        $sWhereClause = $sLimitClause = "";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                if(isset($aParams['object'], $aParams['name'])) {
+                    $sSql = $this->prepare("DELETE FROM `tdi` USING `sys_form_display_inputs` AS `tdi` LEFT JOIN `sys_form_displays` AS `td` ON `tdi`.`display_name`=`td`.`display_name` WHERE `td`.`object`=? AND `tdi`.`input_name`=?", $aParams['object'], $aParams['name']);
+                    $this->query($sSql);
+                }
+
+                $aBindings['id'] = $aParams['value'];
+
+                $sWhereClause = " AND `ti`.`id`=:id ";
+                break;
+            case 'all':
+                break;
+        }
+
+        $sSql = "DELETE FROM `ti` USING `sys_form_inputs` AS `ti` WHERE 1 " . $sWhereClause . " " . $sLimitClause;
+        return (int)$this->query($sSql, $aBindings) > 0;
+    }
+
+    function checkInputsInDisplays($sObject, $sDisplayName)
+    {
+        $aDisplay = array();
+        $this->getDisplays(array('type' => 'by_object_display', 'object' => $sObject, 'display' => $sDisplayName), $aDisplay, false);
+        if(empty($aDisplay) || !is_array($aDisplay))
+            return false;
+
+		$sSql = $this->prepare("DELETE FROM `tdi` USING `sys_form_display_inputs` AS `tdi` LEFT JOIN `sys_form_inputs` AS `ti` ON `tdi`.`input_name`=`ti`.`name` WHERE 1 AND `tdi`.`display_name`=? AND `ti`.`id` IS NULL", $sDisplayName);
+		$this->query($sSql);
+
+        $sSql = $this->prepare("INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`) SELECT ? AS `display_name`,`ti`.`name` AS `input_name` FROM `sys_form_inputs` AS `ti` LEFT JOIN `sys_form_display_inputs` AS `tdi` ON  `ti`.`name`=`tdi`.`input_name` AND `tdi`.`display_name`=? WHERE 1 AND `ti`.`object`=? AND `tdi`.`id` IS NULL", $sDisplayName, $sDisplayName, $sObject);
+        return (int)$this->query($sSql) > 0;
+    }
+
+    function getInputOrderMax($sDisplayName)
+    {
+        $sSql = $this->prepare("SELECT MAX(`order`) FROM `sys_form_display_inputs` WHERE `display_name`=? LIMIT 1", $sDisplayName);
+        return (int)$this->getOne($sSql);
+    }
+
+    function getLists($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tl`.`key` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tl`.`id`=:id ";
+                break;
+
+            case 'by_key':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'key' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tl`.`key`=:key ";
+                break;
+
+            case 'by_module':
+            	$aMethod['params'][1] = array(
+                	'module' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tl`.`module`=:module ";
+                break;
+
+            case 'pairs_list_values':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'key';
+                $aMethod['params'][2] = 'values';
+                $sSelectClause = ",COUNT( `tv`.`id`) AS `values`";
+                $sJoinClause = "LEFT JOIN `sys_form_pre_values` AS `tv` ON `tl`.`key`=`tv`.`Key`";
+                $sGroupClause = "GROUP BY `tl`.`id`";
+                break;
+
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `tl`.`module`";
+                break;
+
+            case 'all_for_sets':
+                $sWhereClause = "AND `tl`.`use_for_sets`='1'";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tl`.`id` AS `id`,
+                `tl`.`module` AS `module`,
+                `tl`.`key` AS `key`,
+                `tl`.`title` AS `title`,
+                `tl`.`use_for_sets` AS `use_for_sets`,
+                `tl`.`extendable` AS `extendable`" . $sSelectClause . "
+            FROM `sys_form_pre_lists` AS `tl` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function updateList($iId, $aFields)
+    {
+        $sSql = "UPDATE `sys_form_pre_lists` SET `" . implode("`=?, `", array_keys($aFields)) . "`=?  WHERE `id`=?";
+        $sSql = call_user_func_array(array($this, 'prepare'), array_merge(array($sSql), array_values($aFields), array($iId)));
+        return $this->query($sSql);
+    }
+
+    function isListUsedInSet($sKey)
+    {
+        bx_import('BxDolForm');
+
+        $sSql = $this->prepare("SELECT
+                COUNT( DISTINCT `ti`.`id`) AS `id`
+            FROM `sys_form_pre_lists` AS `tl`
+            LEFT JOIN `sys_form_inputs` AS `ti` ON CONCAT('" . BX_DATA_LISTS_KEY_PREFIX . "', `tl`.`key`)=`ti`.`values` AND `ti`.`type` IN ('select_multiple', 'checkbox_set')
+            WHERE 1 AND `tl`.`key`=?", $sKey);
+        return (int)$this->getOne($sSql) > 0;
+    }
+
+    function getValues($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tv`.`Order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tv`.`id`=:id ";
+                break;
+
+            case 'by_key':
+            	$aMethod['params'][1] = array(
+                	'key' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tv`.`Key`=:key ";
+                break;
+
+            case 'by_key_key_value':
+                $aMethod['name'] = 'getAllWithKey';
+                $aMethod['params'][1] = 'value';
+                $aMethod['params'][2] = array(
+                	'key' => $aParams['value']
+                );
+
+                $sWhereClause = " AND `tv`.`Key`=:key ";
+                break;
+
+            case 'counter_by_lists':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'key';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `tv`.`Key`";
+                break;
+
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", `tl`.`module` AS `module`, COUNT(`tv`.`id`) AS `counter`";
+                $sJoinClause = "LEFT JOIN `sys_form_pre_lists` AS `tl` ON `tv`.`Key`=`tl`.`key`";
+                $sGroupClause = "GROUP BY `tl`.`module`";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tv`.`id` AS `id`,
+                `tv`.`Key` AS `key`,
+                `tv`.`Key` AS `Key`,
+                `tv`.`Value` AS `value`,
+                `tv`.`Value` AS `Value`,
+                `tv`.`LKey` AS `lkey`,
+                `tv`.`LKey` AS `LKey`,
+                `tv`.`LKey2` AS `lkey2`,
+                `tv`.`LKey2` AS `LKey2`,
+                `tv`.`Order` AS `order`,
+                `tv`.`Order` AS `Order`" . $sSelectClause . "
+            FROM `sys_form_pre_values` AS `tv` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function deleteValues($aParams)
+    {
+    	$aBindings = array();
+        $sWhereClause = $sLimitClause = "";
+
+        switch($aParams['type']) {
+            case 'by_key':
+            	$aBindings['key'] = $aParams['value'];
+
+                $sWhereClause = " AND `tv`.`Key`=:key ";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $sSql = "DELETE FROM `tv` USING `sys_form_pre_values` AS `tv` WHERE 1 " . $sWhereClause . " " . $sLimitClause;
+        return $this->query($sSql, $aBindings) !== false;
+    }
+
+    function getValuesOrderMax($sKey)
+    {
+        $sSql = $this->prepare("SELECT MAX(`Order`) FROM `sys_form_pre_values` WHERE `Key`=? LIMIT 1", $sKey);
+        return (int)$this->getOne($sSql);
+    }
+
+    function getSearchForms($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tf`.`title` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `tf`.`id`=:id ";
+                break;
+
+            case 'by_module':
+            	$aMethod['params'][1] = array(
+                	'module' => $aParams['module']
+                );
+
+                $sWhereClause = "AND `tf`.`module`=:module";
+                break;
+
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `tf`.`module`";
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . "
+                `tf`.*" . $sSelectClause . "
+            FROM `sys_objects_search_extended` AS `tf` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function getSearchFields($aParams, &$aItems, $bReturnCount = true)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sSelectClause = $sFromClause = $sJoinClause = $sWhereClause = $sGroupClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+           $sOrderClause = "ORDER BY `tfld`.`order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `tfld`.`id`=:id ";
+                break;
+
+            case 'by_object':
+            	$aMethod['params'][1] = array(
+                	'object' => $aParams['object']
+                );
+
+                $sWhereClause = "AND `tfld`.`object`=:object";
+                break;
+
+            case 'counter_by_modules':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'module';
+                $aMethod['params'][2] = 'counter';
+                $sSelectClause = ", COUNT(*) AS `counter`";
+                $sGroupClause = "GROUP BY `tfld`.`module`";
+                $sOrderClause = "";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . ($bReturnCount ? "SQL_CALC_FOUND_ROWS" : "") . " 
+            	`tfld`.*" . $sSelectClause . "
+            FROM `sys_search_extended_fields` AS `tfld` " . $sJoinClause . "
+            WHERE 1 " . $sWhereClause . " " . $sGroupClause . " " . $sOrderClause . " " . $sLimitClause;
+        $aItems = call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+
+        if(!$bReturnCount)
+            return !empty($aItems);
+
+        return (int)$this->getOne("SELECT FOUND_ROWS()");
+    }
+
+    function alterAdd($sTable, $sField, $sType)
+    {
+        if($this->isFieldExists($sTable, $sField))
+            return true;
+
+        return (int)$this->query("ALTER TABLE `" . $sTable . "` ADD `" . $sField . "` " . $sType) > 0;
+    }
+
+    function alterChange($sTable, $sFieldOld, $sFieldNew, $sTypeNew)
+    {
+        if(!$this->isFieldExists($sTable, $sFieldOld))
+            return false;
+
+        if(strcmp($sFieldOld, $sFieldNew) !== 0 && $this->isFieldExists($sTable, $sFieldNew))
+            return false;
+
+        return (int)$this->query("ALTER TABLE `" . $sTable . "` CHANGE `" . $sFieldOld . "` `" . $sFieldNew . "` " . $sTypeNew) > 0;
+    }
+
+    function alterRemove($sTable, $sField)
+    {
+        if(!$this->isFieldExists($sTable, $sField))
+            return true;
+
+        return (int)$this->query("ALTER TABLE `" . $sTable . "` DROP `" . $sField . "`") > 0;
+    }
+}
+
+/** @} */

+ 664 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/classes/BxDolStudioInstallerUtils.php

@@ -0,0 +1,664 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+bx_import('BxDolLanguages');
+
+define('BX_DOL_MARKET_URL_ROOT', 'https://una.io/');
+define('BX_DOL_MARKET_URL_INTEGRATION', BX_DOL_MARKET_URL_ROOT . 'm/market_api/');
+
+define('BX_DOL_STUDIO_IU_RC_SUCCESS', 0);
+define('BX_DOL_STUDIO_IU_RC_FAILED', 1);
+define('BX_DOL_STUDIO_IU_RC_SCHEDULED', 2);
+
+class BxDolStudioInstallerUtils extends BxDolInstallerUtils implements iBxDolSingleton
+{
+	protected $bUseFtp;
+
+	protected $sAuthorizedAccessClass;
+	protected $sStoreDataUrlPublic;
+
+    public function __construct()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+        parent::__construct();
+
+        $this->bUseFtp = BX_FORCE_USE_FTP_FILE_TRANSFER;
+
+		$this->sAuthorizedAccessClass = 'BxDolStudioOAuthOAuth2';
+		$this->sStoreDataUrlPublic = BX_DOL_MARKET_URL_INTEGRATION;
+    }
+
+    /**
+     * Prevent cloning the instance
+     */
+    public function __clone()
+    {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+    /**
+     * Get singleton instance of the class
+     */
+    public static function getInstance()
+    {
+        if (!isset($GLOBALS['bxDolClasses'][__CLASS__]))
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolStudioInstallerUtils();
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    public static function getNamePerformAction($sParam)
+    {
+    	return 'sys_perform_action_' . md5($sParam);
+    }
+
+    public static function getNameDownloadFile($sParam)
+    {
+    	return 'sys_download_file_complete_' . $sParam;
+    }
+
+	public static function getNamePerformModulesUpgrade()
+    {
+    	return 'sys_upgrade_modules_transient';
+    }
+
+    /*
+     * Is used to complete Downloading inside Transient Cron Job.
+     */
+    public function serviceDownloadFileComplete($sFilePath, $aParams = array())
+    {
+    	return $this->downloadFileComplete($sFilePath, array_merge($aParams, array('transient' => true)));
+    }
+
+    /*
+     * Is used to perform action inside Transient Cron Job.
+     */
+	public function servicePerformAction($sDirectory, $sOperation, $aParams)
+    {
+    	return $this->perform($sDirectory, $sOperation, array_merge($aParams, array('transient' => true)));
+    }
+
+	/*
+     * Is used to perform modules upgrade inside Transient Cron Job.
+     */
+	public function servicePerformModulesUpgrade($bEmailNotify)
+    {
+    	return $this->performModulesUpgrade(true, $bEmailNotify);
+    }
+
+    public function getStoreDataUrl($sType = 'public')
+    {
+    	return $sType == 'public' ? $this->sStoreDataUrlPublic : '';
+    }
+
+    public function getAccessObject($bAuthorizedAccess)
+    {
+    	$sClass = $bAuthorizedAccess ? $this->sAuthorizedAccessClass : 'BxDolStudioJson';
+
+		bx_import($sClass);
+		return $sClass::getInstance();
+    }
+
+    public function getModules($bTitleAsKey = true)
+    {
+        $aModules = array();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aInstalledInfo = $this->getInstalledInfo();
+
+        $sPath = BX_DIRECTORY_PATH_MODULES;
+        if(($rHandleVendor = opendir($sPath)) !== false) {
+            while(($sVendor = readdir($rHandleVendor)) !== false) {
+                if(substr($sVendor, 0, 1) == '.' || !is_dir($sPath . $sVendor))
+                    continue;
+
+                if(($rHandleModule = opendir($sPath . $sVendor)) !== false) {
+                    while(($sModule = readdir($rHandleModule)) !== false) {
+                        if(!is_dir($sPath . $sVendor . '/' . $sModule) || substr($sModule, 0, 1) == '.')
+                            continue;
+
+						$sModulePath = $sVendor . '/' . $sModule . '/';
+                        $aModule = $this->getConfigModule($sPath . $sModulePath . 'install/config.php', !empty($aInstalledInfo[$sModulePath]) ? $aInstalledInfo[$sModulePath] : array());
+                        if(empty($aModule))
+                            continue;
+
+                        $aModules[$bTitleAsKey ? $aModule['title'] : $aModule['name']] = $aModule;
+                    }
+                    closedir($rHandleModule);
+                }
+            }
+            closedir($rHandleVendor);
+        }
+
+        ksort($aModules);
+        return $aModules;
+    }
+
+    public function getUpdates()
+    {
+        $aUpdates = array();
+
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aInstalledInfo = $this->getInstalledInfo();
+
+        $sPath = BX_DIRECTORY_PATH_MODULES;
+        if(($rHandleVendor = opendir($sPath)) !== false) {
+            while(($sVendor = readdir($rHandleVendor)) !== false) {
+                if(substr($sVendor, 0, 1) == '.' || !is_dir($sPath . $sVendor))
+                    continue;
+
+                if(($rHandleModule = opendir($sPath . $sVendor . '/')) !== false) {
+                    while(($sModule = readdir($rHandleModule)) !== false) {
+                        if(!is_dir($sPath . $sVendor . '/' . $sModule) || substr($sModule, 0, 1) == '.')
+                            continue;
+
+						$sModulePath = $sVendor . '/' . $sModule . '/';
+						if(empty($aInstalledInfo[$sModulePath]))
+							continue;
+
+						$aUpdate = $this->getUpdate($aInstalledInfo[$sModulePath]);
+						if(!empty($aUpdate))
+							$aUpdates[$aUpdate['title']] = $aUpdate;
+                    }
+                    closedir($rHandleModule);
+                }
+            }
+            closedir($rHandleVendor);
+        }
+
+        ksort($aUpdates);
+        return $aUpdates;
+    }
+
+    public function getUpdate($mixedModule)
+    {
+    	$aResult = array();
+
+    	if(!is_array($mixedModule))
+			$mixedModule = BxDolModuleQuery::getInstance()->{is_numeric($mixedModule) ? 'getModuleById' : 'getModuleByName'}($mixedModule); 
+
+    	if(empty($mixedModule) || !is_array($mixedModule))
+    		return $aResult;
+
+    	$sPathModule = BX_DIRECTORY_PATH_MODULES . $mixedModule['path'];
+    	$sPathUpdates = $sPathModule . 'updates/';
+
+    	$rHandleUpdate = @opendir($sPathUpdates);
+		if($rHandleUpdate === false)
+			return $aResult; 
+
+		while(($sUpdate = readdir($rHandleUpdate)) !== false) {
+			$sPathUpdate = $sPathUpdates . $sUpdate . '/';
+			if(substr($sUpdate, 0, 1) == '.' || !is_dir($sPathUpdate))
+            	continue;
+
+			$aUpdate = $this->getConfigUpdate($sPathModule . 'install/config.php', $sPathUpdate . 'install/config.php', $mixedModule);
+			if(empty($aUpdate) || !is_array($aUpdate) || version_compare($aUpdate['module_version'], $aUpdate['version_from']) != 0)
+            	continue;
+
+			$aResult = $aUpdate;
+			break;
+		}
+
+		closedir($rHandleUpdate);
+
+        return $aResult;
+    }
+
+    public function perform($sDirectory, $sOperation, $aParams = array())
+    {
+	    $bTransient = false;
+    	if(isset($aParams['transient']) && $aParams['transient'] === true) {
+    		$bTransient = true;
+    		unset($aParams['transient']);
+    	}
+
+    	if(!defined('BX_DOL_CRON_EXECUTE') && !self::isRealOwner() && !in_array($sOperation, array('install', 'uninstall', 'enable', 'disable'))) {
+    		if($this->addTransientJob(self::getNamePerformAction($sDirectory), 'perform_action', array($sDirectory, $sOperation, $aParams)))
+    			return array('code' => BX_DOL_STUDIO_IU_RC_SCHEDULED, 'message' => _t('_adm_mod_msg_process_operation_scheduled'));
+    		else 
+    			return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => _t('_adm_mod_err_process_operation_failed', $sOperation, $sDirectory));
+    	}
+
+    	$sConfigFile = 'install/config.php';
+        $sInstallerFile = 'install/installer.php';
+        $sInstallerClass = $sOperation == 'update' ? 'Updater' : 'Installer';
+
+        $aConfig = self::getModuleConfig(BX_DIRECTORY_PATH_MODULES . $sDirectory . $sConfigFile);
+
+        $sPathInstaller = BX_DIRECTORY_PATH_MODULES . $sDirectory . $sInstallerFile;
+        if(empty($aConfig) || !file_exists($sPathInstaller)) {
+        	$sMessage = _t('_adm_mod_err_process_operation_failed', $sOperation, $sDirectory);
+        	if($bTransient)
+        		$this->emailNotify($sMessage);
+
+        	return array('code' => BX_DOL_STUDIO_IU_RC_FAILED, 'message' => $sMessage);
+        } 
+
+		require_once($sPathInstaller);
+
+		$sClassName = $aConfig['class_prefix'] . $sInstallerClass;
+		$oInstaller = new $sClassName($aConfig);
+		$aResult = $oInstaller->$sOperation($aParams);
+
+		if(!$aResult['result'] && $bTransient)
+			$this->emailNotify($aResult['message']);
+
+        return array('code' => $aResult['result'] ? BX_DOL_STUDIO_IU_RC_SUCCESS : BX_DOL_STUDIO_IU_RC_FAILED, 'message' => $aResult['message']);
+    }
+
+    public function performModulesUpgrade($bDirectly = true, $bEmailNotify = true)
+    {
+        if(!defined('BX_DOL_CRON_EXECUTE') && !$bDirectly)
+            return $this->addTransientJob(self::getNamePerformModulesUpgrade(), 'perform_modules_upgrade', array($bEmailNotify));   	
+
+        $oLog = BxDolLog::getInstance();
+        $oLog->setName('upgrade_modules');
+
+        $aFailed = array();
+        $aUpdates = $this->checkUpdates();
+        if(empty($aUpdates) || !is_array($aUpdates)) {
+            $oLog->write('Cannot get a list of modules which require to be updated.');
+            return true;
+        }
+
+        foreach($aUpdates as $aUpdate) {
+            $mixedResult = $this->downloadUpdatePublic($aUpdate['name']);
+            if($mixedResult !== true) {
+                $aFailed[$aUpdate['name']] = $mixedResult;
+                continue;
+            }
+        }
+
+        $aSuccess = array();
+        $aUpdates = $this->getUpdates();
+        if(empty($aUpdates) || !is_array($aUpdates)) {
+            $oLog->write('Cannot find update scripts for modules. They are damaged or were not downloaded.');
+            return true;
+        }
+
+        foreach($aUpdates as $aUpdate) {
+            $aResult = $this->perform($aUpdate['dir'], 'update');
+            if((int)$aResult['code'] != 0) {
+                $aFailed[$aUpdate['module_name']] = $aResult['message'];
+                continue;
+            }
+
+            $aSuccess[$aUpdate['module_name']] = $aUpdate['version_to'];
+        }
+
+        if(!empty($aFailed)) {
+            $oLog->write('Failed to update modules:', $aFailed);
+
+            if($bEmailNotify)
+                $this->emailNotifyModulesUpgrade('failed', $aFailed);
+        }
+
+        if(!empty($aSuccess)) {
+            $oLog->write('Successfully updated modules:', $aSuccess);
+
+            if($bEmailNotify)
+                $this->emailNotifyModulesUpgrade('success', $aSuccess);
+        }
+
+        return empty($aFailed);
+    }
+
+    public function checkModules($bAuthorizedAccess = false)
+    {
+        $iPerPage = 9999; //--- Note. It's essential to load all purchased products at the same time.
+
+    	if($bAuthorizedAccess)
+        	$aProducts = $this->getAccessObject(true)->loadItems(array(
+        		'method' => 'browse_purchased', 
+        		'domain' => BX_DOL_URL_ROOT, 
+        		'products' => $this->getInstalledInfoShort(),
+        	    'per_page' => $iPerPage
+        	));
+    	else
+			$aProducts = $this->getAccessObject(false)->load($this->sStoreDataUrlPublic . 'json_browse_purchased', array('key' => getParam('sys_oauth_key'), 'per_page' => $iPerPage));
+
+		if(empty($aProducts) || !is_array($aProducts))
+			return $aProducts;
+
+    	$oModuleDb = BxDolModuleQuery::getInstance();
+
+    	$oModuleDb->updateModule(array('hash' => ''));
+        if(!empty($aProducts) && is_array($aProducts))
+	        foreach ($aProducts as $aProduct)
+	        	if(!empty($aProduct['hash']))
+	        		$oModuleDb->updateModule(array('hash' => $aProduct['hash']), array('name' => $aProduct['name']));
+
+		return $aProducts;
+    }
+
+    public function checkUpdates($bAuthorizedAccess = false)
+    {
+        $sProducts = $this->getInstalledInfoShort();
+
+        if($bAuthorizedAccess)
+	        return $this->getAccessObject(true)->loadItems(array(
+	        	'method' => 'browse_updates', 
+	        	'products' => $sProducts
+	        ));
+
+		return $this->getAccessObject(false)->load($this->sStoreDataUrlPublic . 'json_browse_updates', array(
+			'key' => getParam('sys_oauth_key'),
+			'products' => $sProducts
+		));
+    }
+
+    public function downloadFileAuthorized($iFileId)
+    {
+		$aItem = $this->getAccessObject(true)->loadItems(array(
+			'method' => 'download_file', 
+			'file_id' => $iFileId
+		));
+
+		if(empty($aItem) || !is_array($aItem))
+			return $aItem;
+
+		return $this->downloadFileInit($aItem, array('module_name' => $aItem['module_name']));
+    }
+
+    public function downloadUpdatePublic($sModuleName, $bAutoUpdate = false)
+    {
+		$aModule = BxDolModuleQuery::getInstance()->getModuleByName($sModuleName);
+
+		$aItem = $this->getAccessObject(false)->load($this->sStoreDataUrlPublic . 'json_download_update', array(
+			'key' => getParam('sys_oauth_key'),
+			'product' => base64_encode(serialize(array(
+				'name' => $aModule['name'],
+	            'version' => $aModule['version'],
+	            'hash' => $aModule['hash'],
+			))),
+		));
+
+		return $this->downloadFileInit($aItem, array('module_name' => $sModuleName, 'auto_action' => $bAutoUpdate ? 'update' : ''));
+    }
+
+    protected function downloadFileInit($aItem, $aParams = array())
+    {
+    	if(empty($aItem) || !is_array($aItem))
+			return $aItem;
+
+    	//--- write ZIP archive.
+        $sFilePath = '';
+        $mixedResult = $this->performWrite($aItem, $sFilePath);
+        if($mixedResult !== true)
+        	return $mixedResult;
+
+		return $this->downloadFileComplete($sFilePath, $aParams);
+    }
+
+    protected function downloadFileComplete($sFilePath, $aParams = array())
+    {
+		$bTransient = false;
+    	if(isset($aParams['transient']) && $aParams['transient'] === true) {
+    		$bTransient = true;
+    		unset($aParams['transient']);
+    	}
+
+    	$sAutoAction = isset($aParams['auto_action']) ? $aParams['auto_action'] : '';
+    	$bAutoAction = !empty($sAutoAction);
+
+		if(!defined('BX_DOL_CRON_EXECUTE') && !self::isRealOwner()) {
+			if(!$this->addTransientJob(self::getNameDownloadFile($aParams['module_name']), 'download_file_complete', array($sFilePath, $aParams)))
+				return _t('_adm_str_err_download_failed');
+
+			return array('code' => BX_DOL_STUDIO_IU_RC_SCHEDULED, 'message' => _t('_adm_str_msg_download' . ($bAutoAction ? '_and_install' : '') . '_scheduled'));
+		}
+
+        //--- Unarchive package.
+        $sPackagePath = '';
+        $mixedResult = $this->performUnarchive($sFilePath, $sPackagePath);
+
+        @unlink($sFilePath);
+        if($mixedResult !== true) {
+        	if($bTransient)
+        		$this->emailNotify($mixedResult);
+
+        	return $mixedResult;
+        }
+
+        //--- Copy unarchived package.
+        $sHomePath = '';
+        $mixedResult = $this->performCopy($sPackagePath, $sHomePath);
+
+        @bx_rrmdir($sPackagePath);
+        if($mixedResult !== true) {
+        	if($bTransient)
+        		$this->emailNotify($mixedResult);
+
+        	return $mixedResult;
+        }
+
+		if(!$bAutoAction)
+			 return true;
+
+		//--- Autoinstall the downloaded package if it's needed.
+		$aResult = $this->perform($sHomePath, $sAutoAction, $aParams);
+		if((int)$aResult['code'] != 0) {
+			if($bTransient)
+        		$this->emailNotify($aResult['message']);
+
+			return $aResult['message'];
+		}
+
+        return true;
+    }
+
+    protected function performWrite($aItem, &$sFilePath)
+    {
+    	$sFilePath = BX_DIRECTORY_PATH_TMP . $aItem['name'];
+    	if(file_exists($sFilePath))
+        	@unlink($sFilePath);
+
+		$iUmaskSave = umask(0);
+
+        if(!$rHandler = fopen($sFilePath, 'w')) {
+        	umask($iUmaskSave);
+            return _t('_adm_str_err_cannot_write');
+        }
+
+        $sContent = urldecode($aItem['content']);
+        if(!fwrite($rHandler, $sContent, strlen($sContent))) {
+        	umask($iUmaskSave);
+            return _t('_adm_str_err_cannot_write');
+        }
+
+        fclose($rHandler);
+        
+        umask($iUmaskSave);
+        return true;
+    }
+
+    protected function performUnarchive($sFilePath, &$sPackagePath)
+    {
+    	if(!class_exists('ZipArchive'))
+            return _t('_adm_str_err_zip_not_available');
+
+		$iUmaskSave = umask(0);
+
+        $oZip = new ZipArchive();
+        if($oZip->open($sFilePath) !== true) {
+        	umask($iUmaskSave);
+            return _t('_adm_str_err_cannot_unzip_package');
+        }
+
+        $sPackageFolder = '';
+        if($oZip->numFiles > 0)
+        	$sPackageFolder = $oZip->getNameIndex(0);
+
+        if(empty($sPackageFolder)) {
+        	umask($iUmaskSave);
+        	return _t('_adm_str_err_cannot_unzip_package');
+        }
+        
+		$sPackagePath = BX_DIRECTORY_PATH_TMP . $sPackageFolder;
+        if(file_exists($sPackagePath)) // remove existing tmp folder with the same name
+            @bx_rrmdir($sPackagePath);
+
+        if(!$oZip->extractTo(BX_DIRECTORY_PATH_TMP)) {
+        	umask($iUmaskSave);
+            return _t('_adm_str_err_cannot_unzip_package');
+        }
+
+        $oZip->close();
+
+        umask($iUmaskSave);
+        return true;
+    }
+
+    protected function performCopy($sPackagePath, &$sHomePath)
+    {
+    	if($this->bUseFtp) {
+	        $sLogin = getParam('sys_ftp_login');
+	        $sPassword = getParam('sys_ftp_password');
+	        $sPath = getParam('sys_ftp_dir');
+	        if(empty($sLogin) || empty($sPassword) || empty($sPath))
+	            return _t('_adm_str_err_no_ftp_info');
+	
+	        $oFile = new BxDolFtp($_SERVER['HTTP_HOST'], $sLogin, $sPassword, $sPath);
+	
+	        if(!$oFile->connect())
+	            return _t('_adm_str_err_cannot_connect_to_ftp');
+	
+	        if(!$oFile->isUna())
+	            return _t('_adm_str_err_destination_not_valid');
+        }
+        else
+        	$oFile = BxDolFile::getInstance();
+
+        $aConfig = self::getModuleConfig($sPackagePath . 'install/config.php');
+        if(empty($aConfig) || !is_array($aConfig) || empty($aConfig['home_dir']))
+            return _t('_adm_str_err_wrong_package_format');
+
+		$sHomePath = $aConfig['home_dir'];
+        if(!$oFile->copy($sPackagePath, 'modules/' . $sHomePath))
+            return _t('_adm_str_err_files_copy_failed');
+
+		return true;
+    }
+
+    protected function getConfigModule($sConfigPath, $aInstalled = array())
+    {
+    	$aConfig = self::getModuleConfig($sConfigPath);
+        if(empty($aConfig) || !is_array($aConfig))
+            return array();
+
+        $bInstalled = !empty($aInstalled);
+        $bEnabled = $bInstalled && (int)$aInstalled['enabled'] == 1;
+        $sVersion = $bInstalled ? $aInstalled['version'] : $aConfig['version'];
+
+        return array(
+        	'type' => $aConfig['type'],
+            'name' => isset($aConfig['name']) ? $aConfig['name'] : $aConfig['home_uri'],
+            'title' => bx_process_output($aConfig['title']),
+            'vendor' => $aConfig['vendor'],
+            'version' => $sVersion,
+            'uri' => $aConfig['home_uri'],
+            'dir' => $aConfig['home_dir'],
+            'note' => isset($aConfig['note']) ? bx_process_output($aConfig['note']) : '',
+            'installed' => $bInstalled,
+            'enabled' => $bEnabled
+        );
+    }
+
+    protected function getConfigUpdate($sConfigPathModule, $sConfigPathUpdate, $aInstalledModule = array())
+    {
+        $aModule = $this->getConfigModule($sConfigPathModule, $aInstalledModule);
+        if(empty($aModule) || !$aModule['installed'])
+            return array();
+
+		$aConfig = self::getModuleConfig($sConfigPathUpdate);
+        if(empty($aConfig) || !is_array($aConfig))
+            return array();
+
+        return array(
+            'title' => bx_process_output($aConfig['title']),
+            'vendor' => $aConfig['vendor'],
+            'version_from' => $aConfig['version_from'],
+            'version_to' => $aConfig['version_to'],
+            'dir' => $aConfig['home_dir'],
+        	'module_type' => $aModule['type'],
+            'module_name' => $aModule['name'],
+            'module_dir' => $aModule['dir'],
+        	'module_version' => $aModule['version']
+        );
+    }
+
+    private function getInstalledInfo()
+    {
+        $aModules = BxDolModuleQuery::getInstance()->getModules();
+
+        $aInstalledInfo = array();
+        foreach($aModules as $aModule)
+            $aInstalledInfo[$aModule['path']] = $aModule;
+
+        return $aInstalledInfo;
+    }
+
+	private function getInstalledInfoShort()
+    {
+    	$aModules = BxDolModuleQuery::getInstance()->getModules();
+
+        $aProducts = array();
+        foreach($aModules as $aModule) {
+            if(empty($aModule['name']))
+                continue;
+
+            $aProducts[$aModule['name']] = array(
+                'name' => $aModule['name'],
+                'version' => $aModule['version'],
+            	'hash' => $aModule['hash'],
+            );
+        }
+
+        return base64_encode(serialize($aProducts));
+    }
+
+    private function addTransientJob($sName, $sAction, $aParams)
+    {
+		if(BxDolCronQuery::getInstance()->addTransientJobService($sName, array('system', $sAction, $aParams, 'DolStudioInstallerUtils')))
+			return true;
+
+		return false;
+    }
+
+    private function emailNotify($sMessage)
+    {
+    	sendMailTemplateSystem('t_BgOperationFailed', array (
+			'conclusion' => strip_tags($sMessage),
+		));
+    }
+
+    private function emailNotifyModulesUpgrade($sResult, $aData)
+    {
+		$oModuleQuery = BxDolModuleQuery::getInstance();
+
+    	$sConclusion = '';
+    	if(!empty($aData))
+			foreach($aData as $sModule => $sMessage) {
+				$aModule = $oModuleQuery->getModuleByName($sModule);
+
+				$sConclusion .= _t('_sys_et_txt_body_modules_upgrade_' . $sResult, $aModule['title'], $sMessage);
+			}
+
+		sendMailTemplateSystem('t_UpgradeModules' . ucfirst($sResult), array (
+			'conclusion' => $sConclusion,
+		));
+    }
+}
+
+/** @} */

+ 66 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/classes/BxDolStudioPermissionsLevels.php

@@ -0,0 +1,66 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+define('BX_DOL_STUDIO_PERMISSIONS_LEVEL_ID_INT_MAX', round(log(BX_DOL_INT_MAX, 2)));
+
+class BxDolStudioPermissionsLevels extends BxTemplStudioGrid
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->oDb = new BxDolStudioPermissionsQuery();
+    }
+
+    protected function delete($iId)
+    {
+        $aLevel = array();
+        $iLevel = $this->oDb->getLevels(array('type' => 'by_id', 'value' => (int)$iId), $aLevel);
+        if($iLevel != 1 || empty($aLevel))
+            return false;
+
+        if($aLevel['removable'] != 'yes' || $this->oDb->isLevelUsed($aLevel['id']))
+            return false;
+
+        // create system event before deletion
+        $isStopDeletion = false;
+        bx_alert('acl', 'before_delete', $aLevel['id'], 0, array('level' => $aLevel, 'stop_deletion' => &$isStopDeletion));
+        if($isStopDeletion)
+            return false;
+
+        if(is_numeric($aLevel['icon'])) {
+            if(!BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES)->deleteFile((int)$aLevel['icon'], 0))
+                return false;
+        }
+
+        $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+        $oLanguage->deleteLanguageString($aLevel['name']);
+        $oLanguage->deleteLanguageString($aLevel['description']);
+
+        $bResult = $this->oDb->deleteLevel(array('type' => 'by_id', 'value' => $aLevel['id']));
+        if($bResult) {
+            // create system event
+            bx_alert('acl', 'deleted', $aLevel['id'], 0, array('level' => $aLevel));
+        }
+
+        return $bResult;
+    }
+
+    protected function _switcherChecked2State($isChecked)
+    {
+        return $isChecked ? 'yes' : 'no';
+    }
+
+    protected function _switcherState2Checked($mixedState)
+    {
+        return 'yes' == $mixedState ? true : false;
+    }
+}
+
+/** @} */

+ 1708 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/template/scripts/BxBaseStudioFormsField.php

@@ -0,0 +1,1708 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+class BxBaseStudioFormsField extends BxDolStudioFormsField
+{
+    protected $aForm;
+    protected $sTypeTitlePrefix = '_adm_form_txt_field_type_';
+
+    function __construct($aParams = array(), $aField = array())
+    {
+        parent::__construct($aParams, $aField);
+    }
+
+	public function init()
+	{
+		parent::init();
+
+		$sJsObject = $this->getJsObject();
+
+        bx_import('BxTemplStudioFormView');
+
+        $this->aForm = array(
+            'form_attrs' => array(
+                'id' => '',
+                'action' => '',
+                'method' => BX_DOL_STUDIO_METHOD_DEFAULT
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_form_inputs',
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_submit'
+                ),
+            ),
+            'inputs' => array (
+                'type' => array(
+                    'type' => 'custom',
+                    'name' => 'type',
+                    'content' => '',
+                    'attrs' => array(
+                        'id' => 'bx-form-field-type',
+                        'class' => 'bx-form-field-type',
+                        'onchange' => $sJsObject . '.onSelectType()'
+                    ),
+                ),
+                'reset' => array (
+                    'type' => 'reset',
+                    'name' => 'close',
+                    'value' => _t('_adm_form_btn_field_cancel'),
+                    'attrs' => array(
+                        'onclick' => "$('.bx-popup-applied:visible').dolPopupHide()",
+                        'class' => 'bx-def-margin-sec-left',
+                    ),
+                )
+            )
+        );
+
+        $aTypes = array();
+        foreach($this->aTypes as $sType => $aParams)
+        	if(isset($aParams['add']) && (int)$aParams['add'] == 1)
+            	$aTypes[$sType] = _t($this->sTypeTitlePrefix . $sType);
+
+        asort($aTypes);
+
+        $aMenu = array();
+        foreach($aTypes as $sName => $sTitle)
+            $aMenu[] = array(
+                'name' => $sName,
+                'icon' => 'ui-' . $sName . '.png',
+                'onclick' => $sJsObject . ".onSelectType('" . $sName . "', this)",
+                'title' => $sTitle
+            );
+
+        $oMenu = new BxTemplStudioMenu(array('template' => 'menu_vertical.html', 'menu_items' => $aMenu));
+        $this->aForm['inputs']['type']['content'] = $oMenu->getCode();
+	}
+
+    public function getJsObject()
+    {
+        return BX_DOL_STUDIO_FORMS_FIELDS_JS_OBJECT;
+    }
+
+    public function getCode($sAction, $sObject)
+    {
+        $sFunction = 'getCode' . $this->getClassName($sAction);
+        if(method_exists($this, $sFunction))
+            return $this->$sFunction($sAction, $sObject);
+
+        return false;
+    }
+
+    protected function getCodeAdd($sAction, $sObject)
+    {
+        $aForm = $this->getFormAdd($sAction, $sObject);
+        $oForm = new BxTemplStudioFormView($aForm);
+
+        if($oForm->isSubmitted())
+            $this->onCheckField('add', $oForm);
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            //--- Process field name.
+            $sInputName = $oForm->getCleanValue('name');
+            if(empty($sInputName)) {
+                $sInputObject = $oForm->getCleanValue('object');
+
+                $sLanguage = BxDolStudioLanguagesUtils::getInstance()->getCurrentLangName(false);
+                $sInputCaption = BxDolForm::getSubmittedValue('caption-' . $sLanguage, $aForm['form_attrs']['method']);
+                if(empty($sInputCaption))
+                    $sInputCaption = BxDolForm::getSubmittedValue('caption_system-' . $sLanguage, $aForm['form_attrs']['method']);
+
+                $sInputName = $this->getFieldName($sInputObject, $sInputCaption);
+                BxDolForm::setSubmittedValue('name', $sInputName, $oForm->aFormAttrs['method']);
+            }
+
+            $sFieldName = strmaxtextlen($sInputName, $this->iFieldNameMaxLen, '');
+            if(strcmp($sInputName, $sFieldName) !== 0)
+            	BxDolForm::setSubmittedValue('name', $sFieldName, $oForm->aFormAttrs['method']);
+
+            if($this->isField($sFieldName))
+                return false;
+
+            $this->onSubmitField($oForm);
+            if(($iId = $oForm->insert()) === false)
+                return false;
+
+            $this->alterAdd($sFieldName);
+            return true;
+        } 
+        else
+            return BxTemplStudioFunctions::getInstance()->popupBox('adm-form-field-add-' . $this->aParams['display'] . '-popup', _t('_adm_form_txt_field_add_popup'), BxDolStudioTemplate::getInstance()->parseHtmlByName('form_add_field.html', array(
+                'form_id' => $aForm['form_attrs']['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $sObject,
+                'action' => $sAction
+            )));
+    }
+
+    protected function getCodeEdit($sAction, $sObject)
+    {
+        $aForm = $this->getFormEdit($sAction, $sObject);
+        $oForm = new BxTemplStudioFormView($aForm);
+
+        $bAlter = false;
+        if($oForm->isSubmitted())
+            $bAlter = $this->onCheckField('edit', $oForm);
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $sInputName = $oForm->getCleanValue('name');
+
+            $sFieldName = strmaxtextlen($sInputName, $this->iFieldNameMaxLen, '');
+            if(strcmp($sInputName, $sFieldName) !== 0)
+            	BxDolForm::setSubmittedValue('name', $sFieldName, $oForm->aFormAttrs['method']);
+
+            $this->onSubmitField($oForm);
+            if($oForm->update((int)$this->aField['id']) === false)
+                return false;
+
+            $sFieldNameOld = $this->aField['name'];
+            if($bAlter || strcmp($sFieldNameOld, $sFieldName) !== 0)
+                $this->alterChange($sFieldNameOld, $sFieldName);
+
+            return true;
+        } 
+        else
+            return BxTemplStudioFunctions::getInstance()->popupBox('adm-form-field-edit-' . $this->aField['type'] . '-popup', _t('_adm_form_txt_field_edit_popup', _t($this->aField['caption_system'])), BxDolStudioTemplate::getInstance()->parseHtmlByName('form_add_field.html', array(
+                'form_id' => $aForm['form_attrs']['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $sObject,
+                'action' => $sAction
+            )));
+    }
+
+    protected function getFormAdd($sAction, $sObject)
+    {
+        $aForm = $this->aForm;
+        $aForm['form_attrs']['id'] = 'adm-form-field-add-' . $this->aParams['display'];
+        $aForm['form_attrs']['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $sObject . '&a=' . $sAction . '&object=' . $this->aParams['object'] . '&display=' . $this->aParams['display'];
+
+        if(isset($aForm['inputs']['object']))
+            $aForm['inputs']['object']['value'] = $this->aParams['object'];
+
+        return $aForm;
+    }
+
+    protected function getFormEdit($sAction, $sObject)
+    {
+        $aForm = $this->aForm;
+        $aForm['form_attrs']['id'] = 'adm-form-field-edit-' . $this->aParams['display'];
+        $aForm['form_attrs']['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $sObject . '&a=' . $sAction . '&object=' . $this->aParams['object'] . '&display=' . $this->aParams['display'] . '&di_id=' . (int)$this->aField['di_id'];
+
+        foreach($aForm['inputs'] as $sKey => $aInput)
+            if(!in_array($sKey, array('type_display', 'controls')))
+                switch($sKey) {
+                    case 'value':
+                        $aForm['inputs'][$sKey]['value'] = $this->aField[$sKey];
+
+                        //--- Date and DateTime
+                        if(in_array($aForm['inputs']['type']['value'], array('datepicker', 'datetime', 'date_time'))) 
+                            $aForm['inputs'][$sKey]['db']['pass'] = $this->aField['db_pass'];
+
+                        //--- Select, Multi Select, Radio Set and Checkbox Set
+                        if(in_array($aForm['inputs']['type']['value'], array('select', 'select_multiple', 'radio_set', 'checkbox_set'))) {
+                            $sList = trim($this->getFieldValues($this->aField), BX_DATA_LISTS_KEY_PREFIX . ' ');
+
+                            $bUseForSets = false;
+                            if(in_array($aForm['inputs']['type']['value'], array('select_multiple', 'checkbox_set'))) {
+                                $aList = array();
+                                $this->oDb->getLists(array('type' => 'by_key', 'value' => $sList), $aList, false);
+
+                                $bUseForSets = (int)$aList['use_for_sets'] == 1;
+                            }
+
+                            $aPreValues = BxDolForm::getDataItems($sList, $bUseForSets);
+                            foreach($aPreValues as $mixedValue => $sTitle)
+                                 $aForm['inputs'][$sKey]['values'][] = array('key' => $mixedValue, 'value' => $sTitle);
+                        }
+
+                        //--- Files
+                        if(in_array($aForm['inputs']['type']['value'], array('files'))) {
+                        	if(!empty($aForm['inputs'][$sKey]['value']))
+                        		$aForm['inputs'][$sKey]['value'] = unserialize($aForm['inputs'][$sKey]['value']);
+
+                        	$aValues = unserialize($this->getFieldValues($this->aField));
+                        	if(!empty($aValues) && is_array($aValues)) {
+                        		$aForm['inputs'][$sKey]['values'] = array();
+
+	                        	foreach($aValues as $mixedValue => $sTitle)
+									$aForm['inputs'][$sKey]['values'][$mixedValue] = _t($sTitle);
+                        	}
+                        }
+                        break;
+
+                    case 'values':
+                        if(!empty($this->aField[$sKey]))
+                            $aForm['inputs'][$sKey]['value'] = $this->getFieldValues($this->aField);
+
+                        if(in_array($aForm['inputs']['type']['value'], array('select', 'select_multiple', 'radio_set', 'checkbox_set'))) {
+                            $aForm['inputs'][$sKey]['type'] = 'value';
+                            foreach($aForm['inputs'][$sKey]['values'] as $aValue)
+                                if($aValue['key'] == $aForm['inputs'][$sKey]['value']) {
+                                    $sLink = BX_DOL_URL_STUDIO . 'builder_forms.php?page=pre_values&list=' . trim($aValue['key'], BX_DATA_LISTS_KEY_PREFIX . ' ');
+                                    $aForm['inputs'][$sKey]['value'] = BxDolStudioTemplate::getInstance()->parseLink($sLink, $aValue['value'], array(
+                                        'title' =>  _t('_adm_form_txt_field_values_manage') 
+                                    ));
+                                    break;
+                                }
+                            unset($aForm['inputs'][$sKey]['values'], $aForm['inputs'][$sKey]['db']);
+                        }
+                        break;
+
+                    case 'checked':
+                        $aForm['inputs'][$sKey]['checked'] = (int)$this->aField[$sKey];
+                        break;
+
+                    case 'collapsed':
+                        $aForm['inputs'][$sKey]['checked'] = (int)$this->aField[$sKey];
+                        break;
+
+                    case 'required':
+                        $aForm['inputs'][$sKey]['checked'] = (int)$this->aField[$sKey];
+                        if($aForm['inputs'][$sKey]['checked'] == 1 && isset($aForm['inputs']['checker_func']))
+                            unset($aForm['inputs']['checker_func']['tr_attrs']['style'], $aForm['inputs']['checker_error']['tr_attrs']['style']);
+                        break;
+
+                    case 'unique':
+                        $aForm['inputs'][$sKey]['checked'] = (int)$this->aField[$sKey];
+                        break;
+
+                    case 'checker_func':
+                        $aForm['inputs'][$sKey]['value'] = strtolower($this->aField[$sKey]);
+                        switch($aForm['inputs'][$sKey]['value']) {
+                            case 'length':
+                                unset($aForm['inputs']['checker_params_length_min']['tr_attrs']['style'], $aForm['inputs']['checker_params_length_max']['tr_attrs']['style']);
+                                break;
+                            case 'date_range':
+                                unset($aForm['inputs']['checker_params_length_min']['tr_attrs']['style'], $aForm['inputs']['checker_params_length_max']['tr_attrs']['style'], $aForm['inputs']['checker_params_required']['tr_attrs']['style']);
+                                break;
+                            case 'preg':
+                                unset($aForm['inputs']['checker_params_preg']['tr_attrs']['style']);
+                                break;
+                        }
+                        break;
+
+                    case 'checker_params_length_min':
+                        $aParams = unserialize($this->aField['checker_params']);
+                        $aForm['inputs'][$sKey]['value'] = isset($aParams['min']) ? (int)$aParams['min'] : 0;
+                        break;
+
+                    case 'checker_params_length_max':
+                        $aParams = unserialize($this->aField['checker_params']);
+                        $aForm['inputs'][$sKey]['value'] = isset($aParams['max']) ? (int)$aParams['max'] : 0;
+                        break;
+                    
+                    case 'checker_params_required':
+                        $aParams = unserialize($this->aField['checker_params']);
+                        $aForm['inputs'][$sKey]['checked'] = isset($aParams['required']) ? (int)$aParams['required'] : 0;
+                        break;
+
+                    case 'checker_params_preg':
+                        $aParams = unserialize($this->aField['checker_params']);
+                        $aForm['inputs'][$sKey]['value'] = isset($aParams['preg']) ? $aParams['preg'] : '';
+                        break;
+
+                    case 'attrs_min':
+                        $aParams = unserialize($this->aField['attrs']);
+                        $aForm['inputs'][$sKey]['value'] = isset($aParams['min']) ? (int)$aParams['min'] : 0;
+                        break;
+
+                    case 'attrs_max':
+                        $aParams = unserialize($this->aField['attrs']);
+                        $aForm['inputs'][$sKey]['value'] = isset($aParams['max']) ? (int)$aParams['max'] : 100;
+                        break;
+
+                    case 'attrs_step':
+                        $aParams = unserialize($this->aField['attrs']);
+                        $aForm['inputs'][$sKey]['value'] = isset($aParams['step']) ? (int)$aParams['step'] : 1;
+                        break;
+
+                    case 'attrs_src':
+                        $aParams = unserialize($this->aField['attrs']);
+                        $aForm['inputs'][$sKey]['value'] = isset($aParams['src']) ? $aParams['src'] : '';
+                        break;
+
+                    case 'editable':
+                        $aForm['inputs'][$sKey]['checked'] = (int)$this->aField[$sKey];
+                        break;
+
+					case 'deletable':
+                        $aForm['inputs'][$sKey]['checked'] = (int)$this->aField[$sKey];
+                        break;
+
+                    default:
+                        $aForm['inputs'][$sKey]['value'] = $this->aField[$sKey];
+                }
+
+        if(array_key_exists($aForm['inputs']['type']['value'], $this->aTypesRelated)) {
+           $aForm['inputs']['type'] = $this->getFieldTypesSelector('type', $aForm['inputs']['type']['value'], true);
+           unset($aForm['inputs']['type_display']);
+        }
+
+        $aForm['inputs']['controls'][0]['value'] = _t('_adm_form_btn_field_save');
+
+        return $aForm;
+    }
+
+    protected function getFieldTypes($sRelatedTo = '')
+    {
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aTypesList = $sRelatedTo != '' && isset($this->aTypesRelated[$sRelatedTo]) ? $this->aTypesRelated[$sRelatedTo]['types'] : array_keys($this->aTypes);
+
+        $aTypes = array();
+        foreach($aTypesList as $sType)
+            $aTypes[$sType] = _t($this->sTypeTitlePrefix . $sType);
+
+        asort($aTypes);
+
+        $aResult = array();
+        foreach($aTypes as $sName => $sTitle)
+            $aResult[] = array(
+                'key' => $sName,
+                'value' => $sTitle,
+                'style' => 'background-image:url(' . $oTemplate->getIconUrl('ui-' . $sName . '.png') . ')'
+            );
+
+        return $aResult;
+    }
+
+    protected function getFieldTypesSelector($sName, $sValue, $bRelated = false)
+    {
+        $bRelated = $bRelated && $sValue != '' && isset($this->aTypesRelated[$sValue]);
+
+        $aField = array(
+            'type' => 'select',
+            'name' => $sName,
+            'caption' => _t('_adm_form_txt_field_type_display'),
+            'info' => '',
+            'value' => $sValue,
+            'values' => $this->getFieldTypes($bRelated ? $sValue : ''),
+            'required' => '',
+            'attrs' => array(
+                'id' => 'bx-form-field-type',
+                'class' => 'bx-form-field-type',
+                'disabled' => 'disabled'
+            )
+        );
+
+        if($bRelated) {
+            $aField['db'] = array('pass' => 'Xss');
+            if((int)$this->aTypesRelated[$sValue]['reload_on_change'] == 1)
+                $aField['attrs']['onchange'] = $this->getJsObject() . '.onChangeType(' . $this->aField['di_id'] . ')';
+            unset($aField['attrs']['disabled']);
+        }
+
+        return $aField;
+    }
+
+    protected function getFieldName($sObject, $mixedCaption)
+    {
+        if($mixedCaption === false)
+            return $this->genFieldName($sObject);
+
+        $sName = get_mb_replace('/([^\d^\w]+)/u', '', $mixedCaption);
+        if(empty($sName))
+            return $this->genFieldName($sObject);
+
+        $sName = get_mb_replace('/([^\d^\w^\s]+)/u', '', $mixedCaption);
+        $sName = $this->getSystemName(trim($sName));
+        if($this->oDb->isInput($sObject, $sName))
+            $sName = $this->genFieldName($sObject, $sName);
+
+        return $sName;
+    }
+
+    protected function getFieldValues($aField)
+    {
+        $mixedResult = null;
+
+        switch($aField['type']) {
+            case 'select':
+            case 'select_multiple':
+            case 'checkbox_set':
+            case 'radio_set':
+                $mixedResult = $this->aField['values'];
+                break;
+
+            case 'input_set':
+                $mixedResult = $this->aField['values'];
+                break;
+
+			case 'files':
+                $mixedResult = $this->aField['values'];
+                break;
+        }
+
+        return $mixedResult;
+    }
+
+    protected function getCheckerFields($bMandatory = false)
+    {
+        $aResult = array(
+            'checker_func' => array(
+                'type' => 'select',
+                'name' => 'checker_func',
+                'caption' => _t('_adm_form_txt_field_checker_func'),
+                'info' => '',
+                'value' => '',
+                'values' => array(
+                    array('key' => '', 'value' => _t('_adm_form_txt_field_checker_empty'))
+                ),
+                'required' => $bMandatory ? '1' : '0',
+                'attrs' => array(
+                    'id' => 'bx-form-field-type',
+                    'onchange' => $this->getJsObject() . ".onSelectChecker(this)"
+                ),
+                'tr_attrs' => array(
+                    'style' => 'display:none'
+                ),
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+            'checker_params' => array(
+                'type' => 'hidden',
+                'name' => 'checker_params',
+                'value' => '',
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+            'checker_params_length_min' => array(
+                'type' => 'text',
+                'name' => 'checker_params_length_min',
+                'caption' => _t('_adm_form_txt_field_checker_params_length_min'),
+                'info' => '',
+                'value' => '',
+                'required' => $bMandatory ? '1' : '0',
+                'tr_attrs' => array(
+                    'style' => 'display:none'
+                ),
+                'db' => array (
+                    'pass' => 'Int',
+                )
+            ),
+            'checker_params_length_max' => array(
+                'type' => 'text',
+                'name' => 'checker_params_length_max',
+                'caption' => _t('_adm_form_txt_field_checker_params_length_max'),
+                'info' => '',
+                'value' => '',
+                'required' => $bMandatory ? '1' : '0',
+                'tr_attrs' => array(
+                    'style' => 'display:none'
+                ),
+                'db' => array (
+                    'pass' => 'Int',
+                )
+            ),
+            'checker_params_required' => array(
+                'type' => 'checkbox',
+                'name' => 'checker_params_required',
+                'caption' => _t('_adm_form_txt_field_checker_params_required'),
+                'info' => '',
+                'value' => '1',
+                'required' => $bMandatory ? '1' : '0',
+                'tr_attrs' => array(
+                    'style' => 'display:none'
+                ),
+                'db' => array (
+                    'pass' => 'Int',
+                )
+            ),
+            'checker_params_preg' => array(
+                'type' => 'text',
+                'name' => 'checker_params_preg',
+                'caption' => _t('_adm_form_txt_field_checker_params_preg'),
+                'info' => _t('_adm_form_dsc_field_checker_params_preg'),
+                'value' => '',
+                'required' => $bMandatory ? '1' : '0',
+                'tr_attrs' => array(
+                    'style' => 'display:none'
+                ),
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+            'checker_error' => array(
+                'type' => 'text_translatable',
+                'name' => 'checker_error',
+                'caption' => _t('_adm_form_txt_field_checker_error'),
+                'info' => '',
+                'value' => '_sys_form_txt_field_checker_error',
+                'required' => $bMandatory ? '1' : '0',
+                'tr_attrs' => array(
+                    'style' => 'display:none'
+                ),
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+        );
+
+        foreach($this->aCheckFunctions as $sCheckFunction)
+            $aResult['checker_func']['values'][] = array('key' => $sCheckFunction, 'value' => _t('_adm_form_txt_field_checker_' . $sCheckFunction));
+
+        if($bMandatory) 
+            $aResult = array_merge_recursive($aResult, array(
+                'checker_func' => array(
+            		'checker' => array (
+                        'func' => 'avail',
+                        'params' => array(),
+                        'error' => _t('_adm_form_err_field_checker_func'),
+                    )
+                ),
+                'checker_params_length_min' => array(
+                	'checker' => array (
+                        'func' => 'preg',
+                        'params' => array('/^[0-9]+$/'),
+                        'error' => _t('_adm_form_err_field_checker_params_length_min'),
+                    )
+                ),
+                'checker_params_length_max' => array(
+                	'checker' => array (
+                        'func' => 'preg',
+                        'params' => array('/^[0-9]+$/'),
+                        'error' => _t('_adm_form_err_field_checker_params_length_max'),
+                    )
+                ),
+                'checker_params_preg' => array(
+                    'checker' => array (
+                        'func' => 'avail',
+                        'params' => array(),
+                        'error' => _t('_adm_form_err_field_checker_params_preg'),
+                    ),
+                    
+                ),
+                'checker_error' => array(
+                	'checker' => array (
+                        'func' => 'availTranslatable',
+                        'params' => array('checker_error'),
+                        'error' => _t('_adm_form_err_field_checker_error'),
+                    )
+                )
+            ));
+
+        return $aResult;
+    }
+
+    protected function updateCheckerFields($sType, &$oForm)
+    {
+            if((int)$oForm->getCleanValue('required') == 0)
+                switch($sType) {
+                    case 'add':
+                        $this->unsetCheckerFields($oForm);
+                        break;
+
+                    case 'edit':
+                        $this->clearCheckerFields($oForm);
+                        break;
+                }
+            else {
+                unset($oForm->aInputs['checker_func']['tr_attrs']['style'], $oForm->aInputs['checker_error']['tr_attrs']['style']);
+                switch($oForm->getCleanValue('checker_func')) {
+                    case 'length':
+                        unset($oForm->aInputs['checker_params_length_min']['tr_attrs']['style'], $oForm->aInputs['checker_params_length_max']['tr_attrs']['style']);
+                        $this->unsetCheckerFields($oForm, 'preg');
+                        break;
+                    case 'date_range':
+                        unset($oForm->aInputs['checker_params_length_min']['tr_attrs']['style'], $oForm->aInputs['checker_params_length_max']['tr_attrs']['style'], $oForm->aInputs['checker_params_required']['tr_attrs']['style']);
+                        $this->unsetCheckerFields($oForm, 'preg');
+                        break;
+                    case 'preg':
+                        unset($oForm->aInputs['checker_params_preg']['tr_attrs']['style']);
+                        $this->unsetCheckerFields($oForm, 'length');
+                        break;
+                    default:
+                        $this->unsetCheckerFields($oForm, 'params');
+                }
+            }
+    }
+
+    protected function clearCheckerFields(&$oForm, $sCheckerFunc = 'all')
+    {
+        switch($sCheckerFunc) {
+            case 'length':
+                $oForm->aInputs['checker_params_length_min']['value'] = '';
+                $oForm->aInputs['checker_params_length_max']['value'] = '';
+                break;
+                
+            case 'date_range':
+                $oForm->aInputs['checker_params_length_min']['value'] = '';
+                $oForm->aInputs['checker_params_length_max']['value'] = '';
+                $oForm->aInputs['checker_params_required']['value'] = '';
+                break;
+
+            case 'preg':
+                $oForm->aInputs['checker_params_preg']['value'] = '';
+                break;
+
+            case 'params':
+                $oForm->aInputs['checker_params_length_min']['value'] = '';
+                $oForm->aInputs['checker_params_length_max']['value'] = '';
+                $oForm->aInputs['checker_params_preg']['value'] = '';
+                break;
+
+            case 'all':
+                $oForm->aInputs['checker_func']['value'] = '';
+                $oForm->aInputs['checker_params']['value'] = '';
+                $oForm->aInputs['checker_params_length_min']['value'] = '';
+                $oForm->aInputs['checker_params_length_max']['value'] = '';
+                $oForm->aInputs['checker_params_preg']['value'] = '';
+                $oForm->aInputs['checker_error']['value'] = '';
+        }
+    }
+
+    protected function unsetCheckerFields(&$oForm, $sCheckerFunc = 'all')
+    {
+        switch($sCheckerFunc) {
+            case 'length':
+                unset(
+                $oForm->aInputs['checker_params_length_min'],
+                $oForm->aInputs['checker_params_length_max']
+                );
+                break;
+                
+            case 'date_range':
+                unset(
+                    $oForm->aInputs['checker_params_length_min'],
+                    $oForm->aInputs['checker_params_length_max'],
+                    $oForm->aInputs['checker_params_required']
+                );
+                break;
+            case 'preg':
+                unset($oForm->aInputs['checker_params_preg']);
+                break;
+            case 'params':
+                unset(
+                    $oForm->aInputs['checker_params_length_min'],
+                    $oForm->aInputs['checker_params_length_max'],
+                    $oForm->aInputs['checker_params_preg']
+                );
+                break;
+            case 'all':
+                unset(
+                    $oForm->aInputs['checker_func'],
+                    $oForm->aInputs['checker_params'],
+                    $oForm->aInputs['checker_params_length_min'],
+                    $oForm->aInputs['checker_params_length_max'],
+                    $oForm->aInputs['checker_params_preg'],
+                    $oForm->aInputs['checker_error']
+                );
+        }
+    }
+
+    protected function genFieldName($sObject, $sPrefix = 'field')
+    {
+        $aFields = array();
+        $this->oDb->getInputs(array('type' => 'by_object_name_filter', 'object' => $sObject, 'name_filter' => $sPrefix . '%'), $aFields, false);
+
+        for($iIndex = 1; true; $iIndex++)
+            if(!in_array($sPrefix . $iIndex, $aFields))
+                break;
+
+        return $sPrefix . $iIndex;
+    }
+
+    protected function onCheckField($sType, &$oForm)
+    {
+        $bAlter = false;
+
+        //--- Process field 'required' and related 'checker' fields.
+        if(isset($oForm->aInputs['required']))
+            $this->updateCheckerFields($sType, $oForm);
+
+        //--- Process field 'db_pass' and related dependencies.
+        if(isset($oForm->aInputs['db_pass']) && $oForm->aInputs['db_pass']['type'] == 'select') {
+            $sDbPass = $oForm->getCleanValue('db_pass');
+
+            $this->aForm['inputs']['value']['db']['pass'] = $sDbPass;
+
+            if(!empty($this->aDbPassDependency[$sDbPass])) {
+                $this->aParams['table_field_type'] = $this->aDbPassDependency[$sDbPass]['alter'];
+                $bAlter = true;
+            }
+        }
+
+        return $bAlter;
+    }
+
+    protected function onSubmitField(&$oForm)
+    {
+        //--- Process field value.
+        if(isset($oForm->aInputs['value']['db']))
+            $oForm->aInputs['value']['db']['pass'] = $oForm->getCleanValue('db_pass');
+
+        //--- Process field values.
+        if(isset($oForm->aInputs['values']['db'])) 
+            $this->onSubmitFieldValues($oForm);
+
+        //--- Process field 'html' flag.
+        if(isset($oForm->aInputs['html'])) {
+            $iHtml = (int)$oForm->getCleanValue('html');
+            BxDolForm::setSubmittedValue('db_pass', $iHtml == 0 ? 'XssMultiline' : 'XssHtml', $oForm->aFormAttrs['method']);
+        }
+
+        //--- Process field checker.
+        $sCheckerFunc = $oForm->getCleanValue('checker_func');
+
+        $aCheckerParams = array();
+        if(!empty($sCheckerFunc)) {
+            if(isset($oForm->aInputs['checker_params_length_min'], $oForm->aInputs['checker_params_length_max'])) {
+                $aCheckerParams['min'] = $oForm->getCleanValue('checker_params_length_min');
+                $aCheckerParams['max'] = $oForm->getCleanValue('checker_params_length_max');
+            }
+
+            if(isset($oForm->aInputs['checker_params_preg']))
+                $aCheckerParams['preg'] = $oForm->getCleanValue('checker_params_preg');
+            
+            if(isset($oForm->aInputs['checker_params_required']))
+                $aCheckerParams['required'] = $oForm->getCleanValue('checker_params_required');
+        }
+
+        unset($oForm->aInputs['checker_params_length_min'], $oForm->aInputs['checker_params_length_max'], $oForm->aInputs['checker_params_preg'], $oForm->aInputs['checker_params_required']);
+        BxDolForm::setSubmittedValue('checker_params', !empty($aCheckerParams) ? serialize($aCheckerParams) : '', $oForm->aFormAttrs['method']);
+
+        //--- Process field attrs.
+        $aAttrs = array();
+        if(isset($oForm->aInputs['attrs_min'], $oForm->aInputs['attrs_max'], $oForm->aInputs['attrs_step'])) {
+            $aAttrs['min'] = $oForm->getCleanValue('attrs_min');
+            $aAttrs['max'] = $oForm->getCleanValue('attrs_max');
+            $aAttrs['step'] = $oForm->getCleanValue('attrs_step');
+        } else if(isset($oForm->aInputs['attrs_src']))
+            $aAttrs['src'] = $oForm->getCleanValue('attrs_src');
+
+        unset($oForm->aInputs['attrs_min'], $oForm->aInputs['attrs_max'], $oForm->aInputs['attrs_step'], $oForm->aInputs['attrs_src']);
+        BxDolForm::setSubmittedValue('attrs', serialize($aAttrs), $oForm->aFormAttrs['method']);
+    }
+
+    protected function onSubmitFieldValues(&$oForm)
+    {
+    	$sValues = $oForm->getCleanValue('values');
+		if(is_string($sValues) && strpos($sValues, BX_DATA_LISTS_KEY_PREFIX) === false)
+        	BxDolForm::setSubmittedValue('values', serialize(explode("\n", $sValues)), $oForm->aFormAttrs['method']);
+    }
+}
+
+class BxBaseStudioFormsFieldBlockHeader extends BxBaseStudioFormsField
+{
+    protected $sType = 'block_header';
+
+    public function init()
+	{
+		parent::init();
+
+		$this->aForm = array(
+            'form_attrs' => array(
+                'id' => '',
+                'action' => '',
+                'method' => 'post'
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_form_inputs',
+                    'key' => 'id',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_submit'
+                ),
+            ),
+            'inputs' => array (
+                'module' => array(
+                    'type' => 'hidden',
+                    'name' => 'module',
+                    'caption' => _t('_adm_form_txt_field_module'),
+                    'value' => 'custom',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'object' => array(
+                    'type' => 'hidden',
+                    'name' => 'object',
+                    'caption' => _t('_adm_form_txt_field_object'),
+                    'value' => '',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'name' => array(
+                    'type' => 'hidden',
+                    'name' => 'name',
+                	'caption' => _t('_adm_form_txt_field_name'),
+                    'value' => '',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    )
+                ),
+                'type' => array(
+                    'type' => 'hidden',
+                    'name' => 'type',
+                    'value' => $this->sType,
+                    'db' => array (
+                        'pass' => 'Xss',
+                    )
+                ),
+                'db_pass' => array(
+                    'type' => 'hidden',
+                    'name' => 'db_pass',
+                    'value' => $this->sDbPass,
+                    'db' => array (
+                        'pass' => 'Xss',
+                    )
+                ),
+                'type_display' => $this->getFieldTypesSelector('type_display', $this->sType),
+                'caption_system' => array(
+                    'type' => 'text_translatable',
+                    'name' => 'caption_system',
+                    'caption' => _t('_adm_form_txt_field_caption_system'),
+                    'info' => _t('_adm_form_dsc_field_caption_system'),
+                    'value' => '_sys_form_txt_field',
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => 'LengthTranslatable',
+                        'params' => array(3,100, 'caption_system'),
+                        'error' => _t('_adm_form_err_field_caption_system'),
+                    ),
+                ),
+                'caption' => array(
+                    'type' => 'text_translatable',
+                    'name' => 'caption',
+                    'caption' => _t('_adm_form_txt_field_caption'),
+                    'info' => _t('_adm_form_dsc_field_caption_block_header'),
+                    'value' => '_sys_form_txt_field',
+                    'required' => '',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    )
+                ),
+                'collapsed' => array(
+                    'type' => 'checkbox',
+                    'name' => 'collapsed',
+                    'caption' => _t('_adm_form_txt_field_collapsed'),
+                    'info' => _t('_adm_form_dsc_field_collapsed'),
+                    'value' => '1',
+                    'required' => '0',
+                    'db' => array (
+                        'pass' => 'Int',
+                    )
+                ),
+                'controls' => array(
+                    'name' => 'controls',
+                    'type' => 'input_set',
+                    array(
+                        'type' => 'submit',
+                        'name' => 'do_submit',
+                        'value' => _t('_adm_form_btn_field_add'),
+                    ),
+                    array (
+                        'type' => 'reset',
+                        'name' => 'close',
+                        'value' => _t('_adm_form_btn_field_cancel'),
+                        'attrs' => array(
+                            'onclick' => "$('.bx-popup-applied:visible').dolPopupHide()",
+                            'class' => 'bx-def-margin-sec-left',
+                        ),
+                    )
+                )
+            )
+        );
+	}
+}
+
+class BxBaseStudioFormsFieldValue extends BxBaseStudioFormsFieldBlockHeader
+{
+    protected $sType = 'value';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aForm['inputs']['caption']['info'] = _t('_adm_form_dsc_field_caption');
+
+        $aFields = array(
+            'value' => array(
+                'type' => 'text',
+                'name' => 'value',
+                'caption' => _t('_adm_form_txt_field_value_custom_text'),
+                'info' => '',
+                'value' => '',
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+        );
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'caption', $aFields);
+
+        unset(
+            $this->aForm['inputs']['collapsed']
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldText extends BxBaseStudioFormsFieldBlockHeader
+{
+    protected $sType = 'text';
+    protected $aCheckFunctions = array('avail', 'length', 'preg', 'email');
+    protected $sDbPass = 'Xss';
+
+    protected $aFieldUnique;
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_alter'] = true;
+        $this->aParams['table_field_type'] = 'varchar(255)';
+
+        $this->aForm['inputs']['caption']['info'] = _t('_adm_form_dsc_field_caption');
+
+        $this->aFieldUnique = array(
+            'type' => 'switcher',
+            'name' => 'unique',
+            'caption' => _t('_adm_form_txt_field_unique'),
+            'info' => _t('_adm_form_dsc_field_unique'),
+            'value' => '1',
+            'required' => '0',
+            'attrs' => array(
+                'id' => 'bx-form-field-unique'
+            ),
+            'db' => array (
+                'pass' => 'Int',
+            )
+        );
+
+        $aFields = array(
+            'value' => array(
+                'type' => 'text',
+                'name' => 'value',
+                'caption' => _t('_adm_form_txt_field_value_default'),
+                'info' => '',
+                'value' => '',
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+            'info' => array(
+                'type' => 'textarea_translatable',
+                'name' => 'info',
+                'caption' => _t('_adm_form_txt_field_info'),
+                'info' => _t('_adm_form_dsc_field_info'),
+                'value' => '_sys_form_txt_field',
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+            'required' => array(
+                'type' => 'switcher',
+                'name' => 'required',
+                'caption' => _t('_adm_form_txt_field_required'),
+                'info' => _t('_adm_form_dsc_field_required'),
+                'value' => '1',
+                'required' => '0',
+                'attrs' => array(
+                    'id' => 'bx-form-field-required',
+                    'onchange' => $this->getJsObject() . ".onCheckRequired(this)"
+                ),
+                'db' => array (
+                    'pass' => 'Int',
+                )
+            ),
+            'unique' => $this->aFieldUnique
+        );
+
+        $aFields = array_merge($aFields, $this->getCheckerFields());
+
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'caption', $aFields);
+        unset($this->aForm['inputs']['collapsed']);
+    }
+}
+
+class BxBaseStudioFormsFieldPassword extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'password';
+    protected $aCheckFunctions = array('avail', 'length', 'preg');
+    
+    public function init()
+	{
+		parent::init();
+
+		unset($this->aForm['inputs']['unique']);
+	}
+}
+
+class BxBaseStudioFormsFieldTextarea extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'textarea';
+    protected $aCheckFunctions = array('avail', 'length', 'preg');
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_field_type'] = 'text';
+
+        $this->aForm['inputs']['value']['type'] = $this->sType;
+
+        $aFields = array(
+            'html' => array(
+                'type' => 'select',
+                'name' => 'html',
+                'caption' => _t('_adm_form_txt_field_html'),
+                'info' => _t('_adm_form_dsc_field_html'),
+                'value' => '0',
+                'values' => array(
+                    array('key' => '0', 'value' => _t('_adm_form_txt_field_html_none')),
+                    array('key' => '1', 'value' => _t('_adm_form_txt_field_html_standard')),
+                    array('key' => '2', 'value' => _t('_adm_form_txt_field_html_full')),
+                    array('key' => '3', 'value' => _t('_adm_form_txt_field_html_mini')),
+                ),
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Int',
+                )
+            ),
+        );
+
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'info', $aFields);
+        unset($this->aForm['inputs']['unique']);
+    }
+}
+
+class BxBaseStudioFormsFieldDatepicker extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'datepicker';
+    protected $aCheckFunctions = array('date','date_range');
+    protected $sDbPass = 'DateTs';
+    protected $aDbPassDependency = array(
+        'Date' => array('alter' => 'date'),
+    	'DateTs' => array('alter' => 'int(11)'),
+    	'DateUtc' => array('alter' => 'int(11)'),
+    );
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_field_type'] = 'int(11)';
+
+        $this->aForm['inputs']['value']['type'] = $this->sType;
+        $this->aForm['inputs']['value']['db']['pass'] = $this->sDbPass;
+
+        $aFields = array(
+            'db_pass' => array(
+                'type' => 'select',
+                'name' => 'db_pass',
+                'caption' => _t('_adm_form_txt_field_db_pass'),
+                'info' => _t('_adm_form_dsc_field_db_pass'),
+                'value' => $this->sDbPass,
+                'values' => array(
+                    array('key' => 'Date', 'value' => _t('_adm_form_txt_field_db_pass_date')),
+                    array('key' => 'DateTs', 'value' => _t('_adm_form_txt_field_db_pass_date_ts')),
+                    array('key' => 'DateUtc', 'value' => _t('_adm_form_txt_field_db_pass_date_utc')),
+                ),
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+        );
+
+        unset($this->aForm['inputs']['db_pass']);
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'controls', $aFields, false);
+    }
+}
+
+class BxBaseStudioFormsFieldDatetime extends BxBaseStudioFormsFieldDatepicker
+{
+    protected $sType = 'datetime';
+    protected $aCheckFunctions = array('date_time');
+    protected $sDbPass = 'DateTimeTs';
+    protected $aDbPassDependency = array(
+        'DateTime' => array('alter' => 'datetime'),
+    	'DateTimeTs' => array('alter' => 'int(11)'),
+    	'DateTimeUtc' => array('alter' => 'int(11)'),
+    );
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aForm['inputs']['value']['db']['pass'] = $this->sDbPass;
+
+        $this->aForm['inputs']['db_pass']['values'] = array(
+            array('key' => 'DateTime', 'value' => _t('_adm_form_txt_field_db_pass_date_time')),
+            array('key' => 'DateTimeTs', 'value' => _t('_adm_form_txt_field_db_pass_date_time_ts')),
+            array('key' => 'DateTimeUtc', 'value' => _t('_adm_form_txt_field_db_pass_date_time_utc')),
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldCheckbox extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'checkbox';
+    protected $aCheckFunctions = array('avail', 'length', 'preg');
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aForm['inputs']['value']['type'] = 'hidden';
+        $this->aForm['inputs']['value']['caption'] = _t('_adm_form_txt_field_value_checkbox');
+        $this->aForm['inputs']['value']['value'] = '1';
+        $this->aForm['inputs']['value']['info'] = _t('_adm_form_dsc_field_value_checkbox');
+
+        $aFields = array(
+            'checked' => array(
+                'type' => 'checkbox',
+                'name' => 'checked',
+                'caption' => _t('_adm_form_txt_field_checked'),
+                'value' => '1',
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Int',
+                )
+            )
+        );
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'value', $aFields);
+        unset($this->aForm['inputs']['unique']);
+    }
+}
+
+class BxBaseStudioFormsFieldSwitcher extends BxBaseStudioFormsFieldCheckbox
+{
+    protected $sType = 'switcher';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aForm['inputs']['checked']['caption'] = _t('_adm_form_txt_field_checked_switcher');
+    }
+}
+
+class BxBaseStudioFormsFieldFile extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'file';
+    protected $aCheckFunctions = array('avail', 'length', 'preg');
+    protected $sDbPass = '';
+
+    public function init()
+	{
+		parent::init();
+
+        unset(
+            $this->aForm['inputs']['value'],
+            $this->aForm['inputs']['unique']
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldFiles extends BxBaseStudioFormsFieldFile
+{
+    protected $sType = 'files';
+
+    public function init()
+    {
+        parent::init();
+
+        $aFields = array(
+            'values' => array(
+                'type' => 'hidden',
+                'name' => 'values',
+                'value' => array(),
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+            'value' => array(
+                'type' => 'checkbox_set',
+                'name' => 'value',
+                'caption' => _t('_adm_form_txt_field_value_files'),
+                'info' => '',
+                'value' => array(),
+            	'values' => array(),
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            )
+        );
+
+        $aUploaders = array(
+            'sys_simple' => '_sys_uploader_simple_title', 
+            'sys_html5' => '_sys_uploader_html5_title'
+        );
+        foreach($aUploaders as $sObject => $sTitle) {
+            $aUploader = BxDolUploaderQuery::getUploaderObject($sObject);
+            if(empty($aUploader) || !is_array($aUploader) || (int)$aUploader['active'] == 0)
+                continue;
+
+            $aFields['values']['value'][$sObject] = $sTitle;
+            $aFields['value']['values'][$sObject] = _t($sTitle);
+        }
+        $aFields['values']['value'] = !empty($aFields['values']['value']) && is_array($aFields['values']['value']) ? serialize($aFields['values']['value']) : '';
+
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'required', $aFields);
+    }
+
+    protected function onSubmitField(&$oForm)
+    {
+    	//--- Process field value.
+        if(isset($oForm->aInputs['value']['db'])) 
+        	$this->onSubmitFieldValue($oForm);
+
+		parent::onSubmitField($oForm);
+    }
+
+	protected function onSubmitFieldValue(&$oForm)
+    {
+    	$mixedValue = $oForm->getCleanValue('value');
+		if(is_array($mixedValue))
+			BxDolForm::setSubmittedValue('value', serialize($mixedValue), $oForm->aFormAttrs['method']);
+    }
+
+    /*
+     * Note. Value of Values should be saved as is in case of 'Files' field.
+     */
+	protected function onSubmitFieldValues(&$oForm)
+    {
+		return;
+    }
+}
+
+class BxBaseStudioFormsFieldNumber extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'number';
+    protected $aCheckFunctions = array('avail', 'length', 'preg');
+    protected $sDbPass = 'Int';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_field_type'] = 'int(11)';
+
+        $this->aForm['inputs']['value']['db']['pass'] = 'Int';
+        $this->aForm['inputs']['value']['checker'] = array (
+            'func' => 'preg',
+            'params' => array('/^\d*?$/'),
+            'error' => _t('_adm_form_err_field_value_number'),
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldSlider extends BxBaseStudioFormsFieldNumber
+{
+    protected $sType = 'slider';
+    protected $aCheckFunctions = array('avail', 'length');
+
+    public function init()
+	{
+		parent::init();
+
+        $aFields = array(
+            'attrs' => array(
+                'type' => 'hidden',
+                'name' => 'attrs',
+                'value' => '',
+                'db' => array (
+                    'pass' => 'Xss',
+                ),
+            ),
+            'attrs_min' => array(
+                'type' => 'text',
+                'name' => 'attrs_min',
+                'caption' => _t('_adm_form_txt_field_attrs_min'),
+                'info' => _t('_adm_form_dsc_field_attrs_min'),
+                'value' => '1',
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Int',
+                )
+            ),
+            'attrs_max' => array(
+                'type' => 'text',
+                'name' => 'attrs_max',
+                'caption' => _t('_adm_form_txt_field_attrs_max'),
+                'info' => _t('_adm_form_dsc_field_attrs_max'),
+                'value' => '100',
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Int',
+                )
+            ),
+            'attrs_step' => array(
+                'type' => 'text',
+                'name' => 'attrs_step',
+                'caption' => _t('_adm_form_txt_field_attrs_step'),
+                'info' => _t('_adm_form_dsc_field_attrs_step'),
+                'value' => '1',
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Int',
+                )
+            ),
+        );
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'value', $aFields);
+        unset($this->aForm['inputs']['unique']);
+    }
+}
+
+class BxBaseStudioFormsFieldDoublerange extends BxBaseStudioFormsFieldSlider
+{
+    protected $sType = 'doublerange';
+    protected $aCheckFunctions = array('avail', 'length');
+    protected $sDbPass = 'Xss';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_field_type'] = 'varchar(255)';
+
+        $this->aForm['inputs']['value']['db']['pass'] = 'Xss';
+        $this->aForm['inputs']['value']['checker'] = array (
+            'func' => 'preg',
+            'params' => array('/^(\d+-\d+)?$/'),
+            'error' => _t('_adm_form_err_field_value_doublerange'),
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldHidden extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'hidden';
+    protected $aCheckFunctions = array('avail', 'length', 'preg', 'date', 'date_time', 'email');
+    protected $sDbPass = '';
+
+    public function init()
+	{
+		parent::init();
+
+        unset(
+            $this->aForm['inputs']['caption'],
+            $this->aForm['inputs']['info'],
+            $this->aForm['inputs']['required'],
+            $this->aForm['inputs']['unique'],
+            $this->aForm['inputs']['checker_func'],
+            $this->aForm['inputs']['checker_params'],
+            $this->aForm['inputs']['checker_params_length_min'],
+            $this->aForm['inputs']['checker_params_length_max'],
+            $this->aForm['inputs']['checker_params_required'],
+            $this->aForm['inputs']['checker_params_preg'],
+            $this->aForm['inputs']['checker_error']
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldButton extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'button';
+    protected $sDbPass = '';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_alter'] = false;
+
+        $this->aForm['inputs']['value']['type'] = 'text_translatable';
+        $this->aForm['inputs']['value']['caption'] = _t('_adm_form_txt_field_value_button');
+        $this->aForm['inputs']['value']['info'] = _t('_adm_form_dsc_field_value_button');
+        $this->aForm['inputs']['value']['value'] = '_sys_form_txt_field';
+
+        unset(
+            $this->aForm['inputs']['caption'],
+            $this->aForm['inputs']['info'],
+            $this->aForm['inputs']['required'],
+            $this->aForm['inputs']['unique'],
+            $this->aForm['inputs']['checker_func'],
+            $this->aForm['inputs']['checker_params'],
+            $this->aForm['inputs']['checker_params_length_min'],
+            $this->aForm['inputs']['checker_params_length_max'],
+            $this->aForm['inputs']['checker_params_required'],
+            $this->aForm['inputs']['checker_params_preg'],
+            $this->aForm['inputs']['checker_error']
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldReset extends BxBaseStudioFormsFieldButton
+{
+    protected $sType = 'reset';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aForm['inputs']['value']['info'] = _t('_adm_form_dsc_field_value_reset');
+    }
+}
+
+class BxBaseStudioFormsFieldSubmit extends BxBaseStudioFormsFieldButton
+{
+    protected $sType = 'submit';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aForm['inputs']['value']['info'] = _t('_adm_form_dsc_field_value_submit');
+    }
+}
+
+class BxBaseStudioFormsFieldImage extends BxBaseStudioFormsFieldButton
+{
+    protected $sType = 'image';
+
+    public function init()
+	{
+		parent::init();
+
+        $aFields = array(
+            'attrs' => array(
+                'type' => 'hidden',
+                'name' => 'attrs',
+                'value' => '',
+                'db' => array (
+                    'pass' => 'Xss',
+                ),
+            ),
+            'attrs_src' => array(
+                'type' => 'text',
+                'name' => 'attrs_src',
+                'caption' => _t('_adm_form_txt_field_attrs_src'),
+                'info' => _t('_adm_form_dsc_field_attrs_src'),
+                'value' => '',
+                'required' => '0',
+                'db' => array (
+                    'pass' => 'Xss',
+                )
+            ),
+        );
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'value', $aFields);
+
+        unset(
+            $this->aForm['inputs']['value']
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldSelect extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'select';
+    protected $aCheckFunctions = array('avail', 'length', 'preg');
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aForm['inputs']['value']['type'] = 'select';
+        $this->aForm['inputs']['value']['values'] = array(
+            array('key' => '', 'value' => _t('_adm_form_txt_field_value_select_value'))
+        );
+        $this->aForm['inputs']['value']['attrs'] = array(
+            'id' => 'adm-form-field-add-value'
+        );
+
+        $aFields = array(
+            'values' => array(
+                'type' => 'select',
+                'name' => 'values',
+                'caption' => _t('_adm_form_txt_field_values'),
+                'info' => '',
+                'value' => '',
+                'values' => array(
+                    array('key' => '', 'value' => _t('_adm_form_txt_field_values_select_list'))
+                ),
+                'required' => '1',
+                'attrs' => array(
+                    'onChange' => $this->getJsObject() . ".onChangeValues(0, this)"
+                ),
+                'db' => array (
+                    'pass' => 'Xss',
+                ),
+                'checker' => array (
+                    'func' => 'avail',
+                    'params' => array(),
+                    'error' => _t('_adm_form_err_field_values'),
+                ),
+            )
+        );
+
+        $aLists = array();
+        $this->oDb->getLists(array('type' => 'all'), $aLists, false);
+        foreach($aLists as $aList)
+            $aFields['values']['values'][] = array('key' => BX_DATA_LISTS_KEY_PREFIX . $aList['key'], 'value' => _t($aList['title']));
+
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'caption', $aFields);
+        unset($this->aForm['inputs']['unique']);
+    }
+}
+
+class BxBaseStudioFormsFieldRadioSet extends BxBaseStudioFormsFieldSelect
+{
+    protected $sType = 'radio_set';
+    protected $aCheckFunctions = array('avail', 'length', 'preg');
+}
+
+class BxBaseStudioFormsFieldSelectMultiple extends BxBaseStudioFormsFieldSelect
+{
+    protected $sType = 'select_multiple';
+    protected $aCheckFunctions = array('avail', 'length', 'preg');
+    protected $sDbPass = 'Set';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_field_type'] = 'bigint(20)';
+
+        $this->aForm['inputs']['value']['type'] = 'select_multiple';
+        $this->aForm['inputs']['value']['values'] = array();
+        $this->aForm['inputs']['value']['db']['pass'] = 'Set';
+        $this->aForm['inputs']['values']['values'] = array(
+            array('key' => '', 'value' => _t('_adm_form_txt_field_values_select_list'))
+        );
+        $this->aForm['inputs']['values']['attrs']['onChange'] = $this->getJsObject() . ".onChangeValues(1, this)";
+
+        $aLists = array();
+        $this->oDb->getLists(array('type' => 'all_for_sets'), $aLists, false);
+        foreach($aLists as $aList)
+            $this->aForm['inputs']['values']['values'][] = array('key' => BX_DATA_LISTS_KEY_PREFIX . $aList['key'], 'value' => _t($aList['title']));
+    }
+}
+
+class BxBaseStudioFormsFieldCheckboxSet extends BxBaseStudioFormsFieldSelectMultiple
+{
+    protected $sType = 'checkbox_set';
+    protected $aCheckFunctions = array('avail', 'length', 'preg');
+}
+
+class BxBaseStudioFormsFieldCustom extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'custom';
+    protected $sDbPass = '';
+
+	public function init()
+	{
+		parent::init();
+
+        unset(
+            $this->aForm['inputs']['required'],
+            $this->aForm['inputs']['unique'],
+            $this->aForm['inputs']['checker_func'],
+            $this->aForm['inputs']['checker_params'],
+            $this->aForm['inputs']['checker_params_length_min'],
+            $this->aForm['inputs']['checker_params_length_max'],
+            $this->aForm['inputs']['checker_params_required'], 
+            $this->aForm['inputs']['checker_params_preg'],
+            $this->aForm['inputs']['checker_error']
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldInputSet extends BxBaseStudioFormsFieldCustom
+{
+    protected $sType = 'input_set';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_alter'] = false;
+
+        $aFields = array(
+            'values' => array(
+                'type' => 'value',
+                'name' => 'values',
+                'caption' => _t('_adm_form_txt_field_values'),
+                'value' => ''
+            )
+        );
+        $this->aForm['inputs'] = $this->addInArray($this->aForm['inputs'], 'value', $aFields);
+
+        unset(
+            $this->aForm['inputs']['value']
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldCaptcha extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'captcha';
+    protected $aCheckFunctions = array('captcha');
+    protected $sDbPass = '';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_alter'] = false;
+
+        $this->aForm['inputs']['required'] = array(
+            'type' => 'hidden',
+            'name' => 'required',
+            'value' => '1',
+            'db' => array (
+                'pass' => 'Int',
+            )
+        );
+
+        $this->aForm['inputs']['checker_func']['type'] = 'hidden';
+        $this->aForm['inputs']['checker_func']['value'] = 'captcha';
+        $this->aForm['inputs']['checker_error']['info'] = _t('_adm_form_dsc_field_checker_error_captcha');
+
+        unset(
+            $this->aForm['inputs']['value'],
+            $this->aForm['inputs']['unique'],
+            $this->aForm['inputs']['checker_func']['tr_attrs']['style'],
+            $this->aForm['inputs']['checker_error']['tr_attrs']['style']
+        );
+    }
+}
+
+class BxBaseStudioFormsFieldLocation extends BxBaseStudioFormsFieldText
+{
+    protected $sType = 'location';
+    protected $aCheckFunctions = array();
+    protected $sDbPass = '';
+
+    public function init()
+	{
+		parent::init();
+
+        $this->aParams['table_alter'] = false;
+
+        unset(
+            $this->aForm['inputs']['required'],
+            $this->aForm['inputs']['unique'],
+            $this->aForm['inputs']['checker_func'],
+            $this->aForm['inputs']['checker_params'],
+            $this->aForm['inputs']['checker_params_length_min'],
+            $this->aForm['inputs']['checker_params_length_max'],
+            $this->aForm['inputs']['checker_params_required'],
+            $this->aForm['inputs']['checker_params_preg'],
+            $this->aForm['inputs']['checker_error']
+        );
+    }
+}
+/** @} */

+ 599 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/studio/template/scripts/BxBaseStudioPermissionsLevels.php

@@ -0,0 +1,599 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+class BxBaseStudioPermissionsLevels extends BxDolStudioPermissionsLevels
+{
+    public static $iBinMB = 1048576;
+
+    protected $sUrlPage;
+
+    function __construct($aOptions, $oTemplate = false)
+    {
+        parent::__construct($aOptions, $oTemplate);
+
+        $this->_aOptions['actions_single']['edit']['attr']['title'] = _t('_adm_prm_btn_level_edit');
+        $this->_aOptions['actions_single']['delete']['attr']['title'] = _t('_adm_prm_btn_level_delete');
+
+        $this->sUrlPage = BX_DOL_URL_STUDIO . 'builder_permissions.php?page=levels';
+    }
+
+    public function performActionAdd()
+    {
+        $sAction = 'add';
+
+        $oForm = $this->_getFormObject($sAction);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            if(($iId = $this->_getAvailableId()) === false) {
+                echoJson(array('msg' => _t('_adm_prm_err_level_id')));
+                return;
+            }
+
+            $mixedIcon = '';
+            if(!empty($_FILES['Icon_image']['tmp_name'])) {
+                $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+
+                $mixedIcon = $oStorage->storeFileFromForm($_FILES['Icon_image'], false, 0);
+                if($mixedIcon === false) {
+                    echoJson(array('msg' => _t('_adm_prm_err_level_icon_image') . $oStorage->getErrorString()));
+                    return;
+                }
+
+                $oStorage->afterUploadCleanup($mixedIcon, 0);
+            }
+
+            if(empty($mixedIcon))
+                $mixedIcon = $oForm->getCleanValue('Icon');
+
+            if(empty($mixedIcon))
+                $mixedIcon = 'acl-unconfirmed.png';
+
+            BxDolForm::setSubmittedValue('Icon', $mixedIcon, $oForm->aFormAttrs['method']);
+
+            $fQuotaSize = round($oForm->getCleanValue('QuotaSize'), 1);
+            BxDolForm::setSubmittedValue('QuotaSize', self::$iBinMB * $fQuotaSize, $oForm->aFormAttrs['method']);
+
+            $fQuotaMaxFileSize = round($oForm->getCleanValue('QuotaMaxFileSize'), 1);
+            BxDolForm::setSubmittedValue('QuotaMaxFileSize', self::$iBinMB * $fQuotaMaxFileSize, $oForm->aFormAttrs['method']);
+
+            $iId = (int)$oForm->insert(array('ID' => $iId, 'Icon' => $mixedIcon, 'Order' => $this->oDb->getLevelOrderMax() + 1));
+            if($iId != 0) {
+                $aLevel = array();
+                $this->oDb->getLevels(array('type' => 'by_id', 'value' => (int)$iId), $aLevel, false);
+
+            	$iActionsFrom = (int)$oForm->getCleanValue('Actions');
+            	if($iActionsFrom > 0) {
+            		$aActions = array();
+            		$this->oDb->getActions(array('type' => 'by_level_id', 'value' => $iActionsFrom), $aActions, false);
+            		foreach($aActions as $aAction)
+            			$this->oDb->switchAction($iId, $aAction['id'], true, array(
+            				'AllowedCount' => $aAction['allowed_count'],
+            				'AllowedPeriodLen' => $aAction['allowed_period_len'],
+            				'AllowedPeriodStart' => $aAction['allowed_period_start'],
+            				'AllowedPeriodEnd' => $aAction['allowed_period_end'],
+            				'AdditionalParamValue' => $aAction['additional_param_value']
+            			));
+            	}
+
+                // create system event
+                bx_alert('acl', 'added', $iId, 0, array('level' => $aLevel));
+
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iId);
+            }
+            else
+                $aRes = array('msg' => _t('_adm_prm_err_level_create'));
+
+            echoJson($aRes);
+        }
+        else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('adm-prm-level-create-popup', _t('_adm_prm_txt_level_create_popup'), $this->_oTemplate->parseHtmlByName('prm_add_level.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    public function performActionEdit()
+    {
+        $sAction = 'edit';
+
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            $iId = (int)bx_get('id');
+            if(!$iId) {
+                echoJson(array());
+                exit;
+            }
+
+            $aIds = array($iId);
+        }
+
+        $iId = $aIds[0];
+
+        $aLevel = array();
+        $iLevel = $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iId), $aLevel);
+        if($iLevel != 1 || empty($aLevel)){
+            echoJson(array());
+            exit;
+        }
+
+        $oForm = $this->_getFormObject($sAction, $aLevel);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $bIconImageCur = is_numeric($aLevel['icon']) && (int)$aLevel['icon'] != 0;
+            $bIconImageNew = !empty($_FILES['Icon_image']['tmp_name']);
+
+            $sIconFont = $oForm->getCleanValue('Icon');
+            $bIconFont = !empty($sIconFont);
+
+            if($bIconImageCur && ($bIconImageNew || $bIconFont)) {
+                $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+                if(!$oStorage->deleteFile((int)$aLevel['icon'], 0)) {
+                    echoJson(array('msg' => _t('_adm_prm_err_level_icon_image_remove')));
+                    return;
+                }
+            }
+
+            $sIcon = $sIconFont;
+            if($bIconImageNew) {
+                $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+                $sIcon = $oStorage->storeFileFromForm($_FILES['Icon_image'], false, 0);
+                if($sIcon === false) {
+                    echoJson(array('msg' => _t('_adm_prm_err_level_icon_image') . $oStorage->getErrorString()));
+                    return;
+                }
+
+                $oStorage->afterUploadCleanup($sIcon, 0);
+            } else if($bIconImageCur && !$bIconFont)
+                $sIcon = $aLevel['icon'];
+
+            BxDolForm::setSubmittedValue('Icon', $sIcon, $oForm->aFormAttrs['method']);
+
+            $fQuotaSize = round($oForm->getCleanValue('QuotaSize'), 1);
+            BxDolForm::setSubmittedValue('QuotaSize', self::$iBinMB * $fQuotaSize, $oForm->aFormAttrs['method']);
+
+            $fQuotaMaxFileSize = round($oForm->getCleanValue('QuotaMaxFileSize'), 1);
+            BxDolForm::setSubmittedValue('QuotaMaxFileSize', self::$iBinMB * $fQuotaMaxFileSize, $oForm->aFormAttrs['method']);
+
+            if($oForm->update($iId) !== false) {
+                $aLevel = array();
+                $this->oDb->getLevels(array('type' => 'by_id', 'value' => (int)$iId), $aLevel, false);
+
+                // create system event
+                bx_alert('acl', 'edited', $iId, 0, array('level' => $aLevel));
+
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iId);
+            }
+            else
+                $aRes = array('msg' => _t('_adm_prm_err_level_edit'));
+
+            echoJson($aRes);
+        }
+        else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox('adm-prm-level-edit-popup', _t('_adm_prm_txt_level_edit_popup', _t($aLevel['name'])), $this->_oTemplate->parseHtmlByName('prm_add_level.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    public function performActionDelete()
+    {
+        $iAffected = 0;
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            echoJson(array());
+            exit;
+        }
+
+        $aIdsAffected = array ();
+        foreach($aIds as $iId) {
+            if(!$this->delete($iId))
+                continue;
+
+            $aIdsAffected[] = $iId;
+            $iAffected++;
+        }
+
+        echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t('_adm_prm_err_level_delete')));
+    }
+
+    public function performActionDeleteIcon()
+    {
+        $sAction = 'delete_icon';
+
+        $aIds = bx_get('ids');
+        if(empty($aIds[0])) {
+            echoJson(array());
+            exit;
+        }
+
+        $iId = (int)$aIds[0];
+
+        $aLevel = array();
+        $iLevel = $this->oDb->getLevels(array('type' => 'by_id', 'value' => $iId), $aLevel);
+        if($iLevel != 1 || empty($aLevel)){
+            echoJson(array());
+            exit;
+        }
+
+        if(is_numeric($aLevel['icon']) && (int)$aLevel['icon'] != 0)
+            if(!BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES)->deleteFile((int)$aLevel['icon'], 0)) {
+                echoJson(array());
+                exit;
+            }
+
+        if($this->oDb->updateLevels($aLevel['id'], array('icon' => '')) !== false)
+            echoJson(array('grid' => $this->getCode(false), 'blink' => $iId, 'preview' => $this->_getIconPreview($aLevel['id']), 'eval' => $this->getJsObject() . ".onDeleteIcon(oData)"));
+    }
+
+    public function getJsObject()
+    {
+        return 'oBxDolStudioPermissionsLevels';
+    }
+
+    public function getCode($isDisplayHeader = true)
+    {
+        return $this->_oTemplate->parseHtmlByName('prm_levels.html', array(
+            'content' => parent::getCode($isDisplayHeader),
+            'js_object' => $this->getJsObject(),
+            'page_url' => $this->sUrlPage,
+            'grid_object' => $this->_sObject,
+            'params_divider' => $this->sParamsDivider
+        ));
+    }
+
+    protected function _addJsCss()
+    {
+        parent::_addJsCss();
+        $this->_oTemplate->addJs(array('jquery.form.min.js', 'permissions_levels.js'));
+
+        $oForm = new BxTemplStudioFormView(array());
+        $oForm->addCssJs();
+    }
+    protected function _getCellSwitcher ($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(in_array($aRow['ID'], array(MEMBERSHIP_ID_NON_MEMBER, MEMBERSHIP_ID_ACCOUNT, MEMBERSHIP_ID_STANDARD, MEMBERSHIP_ID_UNCONFIRMED, MEMBERSHIP_ID_PENDING, MEMBERSHIP_ID_SUSPENDED)))
+            return parent::_getCellDefault('', $sKey, $aField, $aRow);;
+
+        return parent::_getCellSwitcher($mixedValue, $sKey, $aField, $aRow);
+    }
+    protected function _getCellIcon ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = $this->_oTemplate->getImage($mixedValue, array('class' => 'bx-prm-level-icon'));
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellActionsList ($mixedValue, $sKey, $aField, $aRow)
+    {
+        $aActions = array();
+        $iActions = $this->oDb->getActions(array('type' => 'by_level_id', 'value' => $aRow['ID']), $aActions);
+
+        $sLink = BX_DOL_URL_STUDIO . 'builder_permissions.php?page=actions&level=' . $aRow['ID'];
+        $mixedValue = $this->_oTemplate->parseLink($sLink, _t('_adm_prm_txt_n_actions', $iActions), array(
+            'title' => _t('_adm_prm_txt_manage_actions')
+        ));
+
+        return parent::_getCellDefault ($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellQuotaSize ($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault ($mixedValue > 0 ? _t_format_size($mixedValue) : '&infin;', $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellQuotaNumber ($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault ($mixedValue > 0 ? $mixedValue : '&infin;', $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellQuotaMaxFileSize ($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault ($mixedValue > 0 ? _t_format_size($mixedValue) : '&infin;', $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionDelete ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if ($sType == 'single' && $aRow['Removable'] != 'yes')
+            return '';
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getIconPreview($iId, $sIconImage = '', $sIconFont = '')
+    {
+        $bIconImage = !empty($sIconImage);
+        $bIconFont = !empty($sIconFont);
+
+        return $this->_oTemplate->parseHtmlByName('prm_level_icon_preview.html', array(
+            'id' => $iId,
+            'bx_if:show_icon_empty' => array(
+                'condition' => !$bIconImage && !$bIconFont,
+                'content' => array()
+            ),
+            'bx_if:show_icon_image' => array(
+                'condition' => $bIconImage,
+                'content' => array(
+                    'js_object' => $this->getJsObject(),
+                    'url' => $sIconImage,
+                    'id' => $iId
+                )
+            ),
+            'bx_if:show_icon_font' => array(
+                'condition' => $bIconFont,
+                'content' => array(
+                    'icon' => $sIconFont
+                )
+            )
+        ));
+    }
+
+    protected function _getFormObject($sAction, $aLevel = array())
+    {
+    	bx_import('BxTemplStudioFormView');
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'adm-prm-level-create',
+                'action' => BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction,
+                'method' => BX_DOL_STUDIO_METHOD_DEFAULT,
+                'enctype' => 'multipart/form-data',
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_acl_levels',
+                    'key' => 'ID',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => 'do_submit'
+                ),
+            ),
+            'inputs' => array (
+                'id' => array(
+                    'type' => 'hidden',
+                    'name' => 'id',
+                    'value' => isset($aLevel['id']) ? (int)$aLevel['id'] : 0,
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                ),
+                'Active' => array(
+                    'type' => 'hidden',
+                    'name' => 'Active',
+                    'value' => 'yes',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'Purchasable' => array(
+                    'type' => 'hidden',
+                    'name' => 'Purchasable',
+                    'value' => 'no',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'Removable' => array(
+                    'type' => 'hidden',
+                    'name' => 'Removable',
+                    'value' => 'yes',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                ),
+                'Name' => array(
+                    'type' => 'text_translatable',
+                    'name' => 'Name',
+                    'caption' => _t('_adm_prm_txt_level_name'),
+                    'info' => _t('_adm_prm_dsc_level_name'),
+                    'value' => '_adm_prm_txt_level',
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => 'LengthTranslatable',
+                        'params' => array(3, 100, 'Name'),
+                        'error' => _t('_adm_prm_err_level_name'),
+                    ),
+                ),
+                'Description' => array(
+                    'type' => 'textarea_translatable',
+                    'name' => 'Description',
+                    'caption' => _t('_adm_prm_txt_level_description'),
+                    'info' => _t('_adm_prm_dsc_level_description'),
+                    'value' => isset($aLevel['description']) ? $aLevel['description'] : '_adm_prm_txt_level',
+                	'code' => 1,
+                    'db' => array (
+                        'pass' => 'XssHtml',
+                    )
+                ),
+                'QuotaSize' => array(
+                    'type' => 'text',
+                    'name' => 'QuotaSize',
+                    'caption' => _t('_adm_prm_txt_level_quota_size'),
+                    'info' => _t('_adm_prm_dsc_level_quota_size'),
+                    'value' => isset($aLevel['quota_size']) ? round($aLevel['quota_size'] / self::$iBinMB, 1) : '0',
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Float',
+                    ),
+                    'checker' => array (
+                        'func' => 'preg',
+                        'params' => array('/^[0-9\.]+$/'),
+                        'error' => _t('_adm_prm_err_level_quota_size'),
+                    ),
+                ),
+                'QuotaMaxFileSize' => array(
+                    'type' => 'text',
+                    'name' => 'QuotaMaxFileSize',
+                    'caption' => _t('_adm_prm_txt_level_quota_max_file_size'),
+                    'info' => _t('_adm_prm_dsc_level_quota_max_file_size'),
+                    'value' => isset($aLevel['quota_max_file_size']) ? round($aLevel['quota_max_file_size'] / self::$iBinMB, 1) : '',
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Float',
+                    ),
+                    'checker' => array (
+                        'func' => 'preg',
+                        'params' => array('/^[0-9\.]+$/'),
+                        'error' => _t('_adm_prm_err_level_quota_max_file_size'),
+                    ),
+                ),
+                'QuotaNumber' => array(
+                    'type' => 'text',
+                    'name' => 'QuotaNumber',
+                    'caption' => _t('_adm_prm_txt_level_quota_number'),
+                    'info' => _t('_adm_prm_dsc_level_quota_number'),
+                    'value' => isset($aLevel['quota_number']) ? (int)$aLevel['quota_number'] : '0',
+                    'required' => '1',
+                    'db' => array (
+                        'pass' => 'Int',
+                    ),
+                    'checker' => array (
+                        'func' => 'preg',
+                        'params' => array('/^[0-9]+$/'),
+                        'error' => _t('_adm_prm_err_level_quota_number'),
+                    ),
+                ),
+                'Icon' => array(
+                    'type' => 'text',
+                    'name' => 'Icon',
+                    'caption' => _t('_adm_prm_txt_level_icon'),
+                    'info' => _t('_adm_prm_dsc_level_icon'),
+                    'value' => '',
+                    'required' => '0',
+                    'db' => array (
+                        'pass' => 'Xss',
+                    ),
+                    'checker' => array (
+                        'func' => '',
+                        'params' => array(),
+                        'error' => _t('_adm_prm_err_level_icon'),
+                    ),
+                ),
+                'Icon_image' => array(
+                    'type' => 'file',
+                    'name' => 'Icon_image',
+                    'caption' => _t('_adm_prm_txt_level_icon_image'),
+                    'info' => _t('_adm_prm_dsc_level_icon_image'),
+                    'value' => '',
+                    'checker' => array (
+                        'func' => '',
+                        'params' => '',
+                        'error' => _t('_adm_prm_err_level_icon_image'),
+                    ),
+                ),
+                'Icon_preview' => array(
+                    'type' => 'custom',
+                    'name' => 'Icon_preview',
+                    'caption' => _t('_adm_prm_txt_level_icon_image_old'),
+                    'content' => ''
+                ),
+                'Actions' => array(
+                    'type' => 'select',
+                    'name' => 'Actions',
+                    'caption' => _t('_adm_prm_txt_level_actions_copy'),
+                    'values' => array(
+                		array('key' => 0, 'value' => _t('_sys_txt_empty'))
+                	),
+                ),
+                'controls' => array(
+                    'name' => 'controls',
+                    'type' => 'input_set',
+                    array(
+                        'type' => 'submit',
+                        'name' => 'do_submit',
+                        'value' => _t('_adm_prm_btn_level_add'),
+                    ),
+                    array (
+                        'type' => 'reset',
+                        'name' => 'close',
+                        'value' => _t('_adm_prm_btn_level_cancel'),
+                        'attrs' => array(
+                            'onclick' => "$('.bx-popup-applied:visible').dolPopupHide()",
+                            'class' => 'bx-def-margin-sec-left',
+                        ),
+                    )
+                )
+            )
+        );
+
+        $aLevels = array(); 
+        $this->oDb->getLevels(array('type' => 'all_pair'), $aLevels, false);
+        foreach($aLevels as $iId => $sName)
+        	$aForm['inputs']['Actions']['values'][] = array('key' => $iId, 'value' => _t($sName));
+
+        switch($sAction) {
+            case 'add':
+                unset($aForm['inputs']['id']);
+                unset($aForm['inputs']['Icon_preview']);
+
+                $aForm['form_attrs']['id'] .= 'create';
+                break;
+
+            case 'edit':
+                unset($aForm['inputs']['Active']);
+                unset($aForm['inputs']['Purchasable']);
+                unset($aForm['inputs']['Removable']);
+                unset($aForm['inputs']['Name']);
+                unset($aForm['inputs']['Actions']);
+
+                $aForm['form_attrs']['id'] .= 'edit';
+                $aForm['inputs']['Icon_image']['caption'] = _t('_adm_prm_txt_level_icon_image_new');
+                $aForm['inputs']['controls'][0]['value'] = _t('_adm_prm_btn_level_save');
+
+                $sIconImage = $sIconFont = "";
+                if(!empty($aLevel['icon'])) {
+                    if(is_numeric($aLevel['icon']) && (int)$aLevel['icon'] != 0) {
+                        $oStorage = BxDolStorage::getObjectInstance(BX_DOL_STORAGE_OBJ_IMAGES);
+
+                        $sIconImage = $oStorage->getFileUrlById((int)$aLevel['icon']);
+                    } else {
+                        $sIconFont = $aLevel['icon'];
+                        $aForm['inputs']['Icon']['value'] = $sIconFont;
+                    }
+                }
+
+                $aForm['inputs']['Icon_preview']['content'] = $this->_getIconPreview($aLevel['id'], $sIconImage, $sIconFont);
+                break;
+        }
+
+        return new BxTemplStudioFormView($aForm);
+    }
+
+    protected function _getAvailableId()
+    {
+        $aLevels = array();
+        $this->oDb->getLevels(array('type' =>'all_order_id'), $aLevels, false);
+
+        $iId = 1;
+        foreach($aLevels as $aLevel) {
+            if($iId != (int)$aLevel['id'])
+                break;
+
+            $iId++;
+        }
+
+        return $iId <= BX_DOL_STUDIO_PERMISSIONS_LEVEL_ID_INT_MAX ? $iId : false;
+    }
+}
+
+/** @} */

+ 180 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/css/media-phone.css

@@ -0,0 +1,180 @@
+
+html.bx-media-phone {
+    font-size:85%;
+}
+
+.bx-media-phone .bx-def-media-phone-hide {
+    display:none !important;
+}
+.bx-media-phone .bx-def-media-phone-show-block {
+    display:block;
+}
+
+
+.bx-media-phone .bx-def-constraint-all *,
+.bx-media-phone .bx-def-constraint-list ol, .bx-def-constraint-list ul,
+.bx-media-phone .bx-def-constraint-pre pre,
+.bx-media-phone .bx-def-constraint-object object,
+.bx-media-phone .bx-def-constraint-iframe iframe,
+.bx-media-phone .bx-def-constraint-img img {
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+    max-width:100% !important;
+    overflow:hidden !important;
+}
+.bx-media-phone .bx-def-constraint-img img {
+    height:auto !important;
+}
+.bx-media-phone .bx-def-constraint-pre pre {
+    overflow:scroll !important;
+}
+
+
+/* default.css */ 
+
+.bx-media-phone .bx-def-page-width {
+    width:auto;
+}
+
+.bx-media-phone .bx-page-wrapper.bx-def-padding-sec-leftright {
+	padding-left: 0px;
+	padding-right: 0px;
+}
+
+.bx-media-phone #bx-content-container.bx-def-padding-sec-left {
+	padding-left: 0px;
+}
+
+.bx-media-phone #bx-content-container.bx-def-padding-sec-right {
+	padding-right: 0px;
+}
+
+.bx-media-phone #bx-content-container .bx-db-container.bx-def-border-block {
+	border-left-width: 0px;
+	border-right-width: 0px;
+}
+
+/* general.css */
+
+.bx-media-phone #bx-logo-container {
+    margin:0 3rem;
+}
+.bx-media-phone #bx-footer-wrapper {
+	height: auto;
+}
+
+/* common.css */
+
+/* make popup fullscreen */
+.bx-media-phone body.bx-popup-visible {
+    height: 100vh;
+    overflow: hidden;
+}
+
+.bx-media-phone .bx-popup-responsive.bx-popup-trans-wrapper:not(.bx-popup-image-wrapper),
+.bx-media-phone .bx-popup-responsive:not(.bx-popup-image-wrapper) .bx-popup-trans,
+.bx-media-phone .bx-popup-responsive:not(.bx-popup-image-wrapper):not(.bx-popup-chain).bx-popup-applied,
+.bx-media-phone .bx-popup-responsive .bx-popup-box {
+    position:fixed !important;
+    top:0 !important;
+    left:0 !important;
+    bottom:0 !important;
+    right:0 !important;
+    width:100%;
+    width:100vw;
+    height:100%;
+    height:100vh;
+    overflow:auto;
+}
+
+.bx-media-phone .bx-popup-responsive,
+.bx-media-phone .bx-popup-responsive .bx-popup-trans > div,
+.bx-media-phone .bx-popup-responsive .bx-popup-box > div {
+    border-radius:0;
+    -moz-border-radius:0;
+    -webkit-border-radius:0;
+}
+
+.bx-media-phone .bx-popup-responsive.bx-popup-image-wrapper:not(.foo) .bx-popup-border,
+.bx-media-phone .bx-popup-responsive.bx-popup-trans-wrapper:not(.foo) .bx-popup-border,
+.bx-media-phone .bx-popup-responsive.bx-popup-box-wrapper:not(.foo) .bx-popup-border {
+    border:none;
+    border-radius:0;
+    -moz-border-radius:0;
+    -webkit-border-radius:0;
+}
+
+.bx-media-phone .bx-popup-responsive.bx-popup-image-wrapper .bx-popup,
+.bx-media-phone .bx-popup-responsive.bx-popup-trans-wrapper .bx-popup,
+.bx-media-phone .bx-popup-responsive.bx-popup-box-wrapper .bx-popup {
+	-moz-box-shadow:none;
+	-webkit-box-shadow:none;
+    box-shadow:none;
+}
+
+.bx-media-phone .bx-popup-width {
+    width:100%;
+}
+
+.bx-media-phone .bx-popup-responsive#bx-popup-alert,
+.bx-media-phone .bx-popup-responsive#bx-popup-confirm,
+.bx-media-phone .bx-popup-responsive#bx-popup-prompt {
+    max-width: none;
+}
+
+/* page layouts */
+
+.bx-media-phone .bx-layout-col {
+    float:none !important;
+    width:auto !important;
+}
+
+.bx-media-phone .bx-layout-cell {
+    padding:0 !important;
+}
+
+/* menu.css */
+
+.bx-media-phone ul.bx-menu-toolbar li a span.bx-menu-toolbar-item-title,
+.bx-media-phone #bx-menu-toolbar-2-container ul.bx-menu-toolbar li a span.bx-menu-toolbar-item-title {
+    display:none;
+}
+
+/* loading indicator */
+.bx-media-phone .bx-btn .bx-loading-ajax-btn {
+	margin-top: 2px;
+	margin-bottom: 2px;
+}
+.bx-media-phone .bx-btn.bx-btn-small .bx-loading-ajax-btn {
+    margin-top: 0px;
+    margin-bottom: 0px;
+}
+
+/* grid.css */
+
+html.bx-media-phone .bx-grid-table-wrapper {
+    overflow-x:scroll;
+}
+html.bx-media-phone .bx-grid-table {
+    min-width:720px;
+}
+html.bx-media-phone .bx-grid-table-wrapper2:before,
+html.bx-media-phone .bx-grid-table-wrapper2:after {
+    display:block;
+}
+
+
+/* score.css */
+.bx-media-phone div.bx-score-by-list div.bx-score-by-list-cnt {
+	max-height: none;
+}
+
+/* general.css Post Form */
+.bx-media-phone .sys-cpf-submenu .sys-cpf-submenu-item > div {
+    display: block;
+    text-align: center;
+}
+

+ 166 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/form_field_location.html

@@ -0,0 +1,166 @@
+
+<div class="bx-form-input-location-cont bx-clearfix">
+    __input__
+    <bx_if:auto_input>
+        <div id="__id_status__" class="bx-def-margin-sec-left">__location_string__</div>
+    </bx_if:auto_input>
+</div>
+
+<input type="hidden" name="__name___lat" value="__lat__" />
+<input type="hidden" name="__name___lng" value="__lng__" />
+<input type="hidden" name="__name___country" value="__country__" />
+<input type="hidden" name="__name___state" value="__state__" />
+<input type="hidden" name="__name___city" value="__city__" />
+<input type="hidden" name="__name___zip" value="__zip__" />
+<input type="hidden" name="__name___street" value="__street__" />
+<input type="hidden" name="__name___street_number" value="__street_number__" />
+
+<script language="javascript" type="text/javascript" src="https://maps.google.com/maps/api/js?libraries=places&language=__lang__&key=__key__"></script>
+<script>
+
+    var oBxLocationHashMap = {
+        '__name___lat': '',
+        '__name___lng': '',
+        '__name___city': 'locality',
+        '__name___state': 'administrative_area_level_1',
+        '__name___country': 'country',
+        '__name___zip': 'postal_code',
+        '__name___street': 'route',
+        '__name___street_number': 'street_number'
+    };
+
+    var bx_location_reset = function () {
+        for (sKey in oBxLocationHashMap)
+            $('[name="' + sKey + '"]').val('');
+    };
+
+    var bx_location_process_address = function (o) {
+        var i, sKey, sCountry, sCity;
+
+        for (i in o) {
+            for (sKey in oBxLocationHashMap) {
+
+                var sName = oBxLocationHashMap[sKey];
+                
+                if ('locality' == sName && -1 == o[i].types.indexOf(sName))
+                    sName = 'postal_town';
+                
+                if ('administrative_area_level_1' == sName && -1 == o[i].types.indexOf(sName))
+                    sName = 'administrative_area_level_2';
+
+                if (o[i].types.indexOf(sName) > -1) {
+                    var sIndex = 'route' == sName || 'locality' == sName ? 'long_name' : 'short_name';
+                    $('[name="' + sKey + '"]').val(o[i][sIndex]);
+                }
+                
+                if (o[i].types.indexOf('locality') > -1)
+                    sCity = o[i].short_name;
+                else if (o[i].types.indexOf('country') > -1)
+                    sCountry = o[i].long_name;
+            }
+        }
+        if ('undefined' != typeof(sCity) && sCity.length)
+            $('#__id_status__').html(sCity + ", " + sCountry);
+        else
+            $('#__id_status__').html(sCountry);
+    };
+
+    var bx_location_geolocate = function () {
+        if (navigator.geolocation) {
+            navigator.geolocation.getCurrentPosition(function(position) {
+                var geolocation = {
+                    lat: position.coords.latitude,
+                    lng: position.coords.longitude
+                };
+                var circle = new google.maps.Circle({
+                    center: geolocation,
+                    radius: position.coords.accuracy
+                });
+                glBxAutocomplete__name__.setBounds(circle.getBounds());
+            });
+        }
+    }
+
+    $('input[name=__name__][type=checkbox]').on('change', function () {
+
+        var callbackOnLocationReset = function (error) {
+            var sKey;
+            for (sKey in oBxLocationHashMap) 
+                $('[name="' + sKey + '"]').val('');
+            $('#__id_status__').html("<bx_text_js:_sys_location_undefined />");
+        }
+
+        var callbackOnLocationSucess = function (position) {
+            bx_location_reset ();
+
+            $('[name="__name___lat"]').val(position.coords.latitude);
+            $('[name="__name___lng"]').val(position.coords.longitude);
+
+            var latlng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
+            var geocoder = new google.maps.Geocoder();
+
+            geocoder.geocode({'latLng': latlng}, function (results, status) {
+                if (status == google.maps.GeocoderStatus.OK) {
+                    bx_location_process_address(results[0]['address_components']);
+                } 
+                else {
+                    $('#__id_status__').html(status);
+                }
+            });
+
+        }
+
+        if ($(this).is(':checked')) {
+            $('#__id_status__').html('<bx_text_js:_sys_location_determining />');
+            if (navigator.geolocation)
+                navigator.geolocation.getCurrentPosition(callbackOnLocationSucess, callbackOnLocationReset);
+            else
+                $('#__id_status__').html('<bx_text_js:_sys_location_geolocation_not_supported_by_browser />');
+        } else {
+            callbackOnLocationReset();
+        }
+    });
+
+    var fInitCallback = function () {
+
+        var callbackFillInAddress = function () {
+            // Get the place details from the autocomplete object.
+            var place = glBxAutocomplete__name__.getPlace();
+
+            bx_location_reset ();
+
+            $('[name="__name___lat"]').val(place.geometry.location.lat);
+            $('[name="__name___lng"]').val(place.geometry.location.lng);
+            
+            bx_location_process_address(place.address_components);
+        }
+        
+        $('input[name=__name__][type=text]').on('focus', bx_location_geolocate);
+
+        $('input[name=__name__][type=text]').on('blur', function () {
+            if (!$(this).val().length)
+                bx_location_reset ();
+        });
+
+        if ($('input[name=__name__][type=text]').size()) {
+
+            // Create the autocomplete object, restricting the search to geographical location types.
+            glBxAutocomplete__name__ = new google.maps.places.Autocomplete(
+                /** @type {!HTMLInputElement} */(document.getElementById('__id_input__'))
+            );
+
+            // When the user selects an address from the dropdown, populate the address fields in the form.
+            glBxAutocomplete__name__.addListener('place_changed', callbackFillInAddress);
+        }
+
+    }
+    
+    $(document).ready(function () {        
+        if ('undefined' === typeof(google))
+            bx_get_scripts(['https://maps.google.com/maps/api/js?libraries=places&language=__lang__&key=__key__'], fInitCallback);
+        else
+            fInitCallback();
+    });
+
+</script>
+

+ 72 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseCalendar.php

@@ -0,0 +1,72 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * Calendar objects representation.
+ * @see BxDolCalendar
+ */
+class BxBaseCalendar extends BxDolCalendar
+{
+    protected $_bJsCssAdded = false;
+    protected $_oTemplate;
+    protected $_aOptions;
+
+    public function __construct ($aOptions, $oTemplate = null)
+    {
+        parent::__construct ();        
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+
+        $oConfig = BxTemplConfig::getInstance();
+        
+        $aOptionsDefault = array(
+            'timezone' => 'local',
+            'eventColor' => $oConfig->aLessConfig['bx-color-active'],
+            'eventTextColor' => $oConfig->aLessConfig['bx-font-color-default'],
+        );
+        $this->_aOptions = array_merge($aOptionsDefault, $aOptions);
+    }
+
+    public function display ($sTemplate = 'calendar.html')
+    {
+        $this->_addJsCss();
+        return $this->_oTemplate->parseHtmlByName($sTemplate, array(
+            'id' => 'bx_events_calendar_' . genRndPwd (8, false),
+            'options' => json_encode($this->_aOptions),
+        ));
+    }
+
+    /**
+     * Add css/js files which are needed for display and functionality.
+     */
+    protected function _addJsCss()
+    {
+        if ($this->_bJsCssAdded)
+            return;
+
+        $this->_oTemplate->addCss(array(
+            BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'fullcalendar/|fullcalendar.min.css',
+        ));
+
+        $aCalendarLangs = array('af' => 1, 'ar-dz' => 1, 'ar-kw' => 1, 'ar-ly' => 1, 'ar-ma' => 1, 'ar-sa' => 1, 'ar-tn' => 1, 'ar' => 1, 'bg' => 1, 'bs' => 1, 'ca' => 1, 'cs' => 1, 'da' => 1, 'de-at' => 1, 'de-ch' => 1, 'de' => 1, 'el' => 1, 'en-au' => 1, 'en-ca' => 1, 'en-gb' => 1, 'en-ie' => 1, 'en-nz' => 1, 'es-do' => 1, 'es-us' => 1, 'es' => 1, 'et' => 1, 'eu' => 1, 'fa' => 1, 'fi' => 1, 'fr-ca' => 1, 'fr-ch' => 1, 'fr' => 1, 'gl' => 1, 'he' => 1, 'hi' => 1, 'hr' => 1, 'hu' => 1, 'id' => 1, 'is' => 1, 'it' => 1, 'ja' => 1, 'ka' => 1, 'kk' => 1, 'ko' => 1, 'lb' => 1, 'lt' => 1, 'lv' => 1, 'mk' => 1, 'ms-my' => 1, 'ms' => 1, 'nb' => 1, 'nl-be' => 1, 'nl' => 1, 'nn' => 1, 'pl' => 1, 'pt-br' => 1, 'pt' => 1, 'ro' => 1, 'ru' => 1, 'sk' => 1, 'sl' => 1, 'sq' => 1, 'sr-cyrl' => 1, 'sr' => 1, 'sv' => 1, 'th' => 1, 'tr' => 1, 'uk' => 1, 'vi' => 1, 'zh-cn' => 1, 'zh-tw' => 1);
+        $sCalendarLang = BxDolLanguages::getInstance()->detectLanguageFromArray ($aCalendarLangs, 'en', true);
+            
+        $this->_oTemplate->addJs(array(
+            'fullcalendar/fullcalendar.min.js',
+            'fullcalendar/locale/' . $sCalendarLang . '.js',
+        ));
+        
+        $this->_bJsCssAdded = true;
+    }    
+}
+
+/** @} */

+ 94 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseMenuFooter.php

@@ -0,0 +1,94 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * Site main menu representation.
+ */
+class BxBaseMenuFooter extends BxTemplMenu
+{
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+    }
+
+    public function getMenuItems ()
+    {
+        $aItems = parent::getMenuItems();
+        foreach($aItems as $iKey => $aItem) {
+            switch($aItem['name']) {
+                case 'switch_language':
+                    $sTitle = _t('_sys_menu_item_title_switch_language_mask', $aItems[$iKey]['title'], genFlag());
+
+                    $aItems[$iKey]['title'] = $sTitle;
+                    $aItems[$iKey]['bx_if:title']['condition'] = (bool)$sTitle;
+                    $aItems[$iKey]['bx_if:title']['content']['title'] = $sTitle;
+                    break;
+
+                case 'switch_template':
+                	$aTemplates = get_templates_array(true, true);
+
+                	$sTemplateName = $this->_oTemplate->getCode();                    
+                    $sTemplateTitle = isset($aTemplates[$sTemplateName]) ? $aTemplates[$sTemplateName] : '';
+
+                    $sTitle = _t('_sys_menu_item_title_switch_template_mask', $aItems[$iKey]['title'], $sTemplateTitle);
+
+                    $aItems[$iKey]['title'] = $sTitle;
+                    $aItems[$iKey]['bx_if:title']['condition'] = (bool)$sTitle;
+                    $aItems[$iKey]['bx_if:title']['content']['title'] = $sTitle;
+                    break;
+
+                case 'powered_by':
+                    $sTitleAttr = bx_html_attribute(_t('_sys_txt_powered_by'));
+
+                    $aItems[$iKey]['title_attr'] = $sTitleAttr;
+                    $aItems[$iKey]['bx_if:image']['content']['title_attr'] = $sTitleAttr;
+                    break;
+            }
+
+            $bTitleAttr = isset($aItems[$iKey]['title_attr']);
+            $bTitleAttrImage = isset($aItems[$iKey]['bx_if:image']['content']['title_attr']);
+            if(!$bTitleAttr || !$bTitleAttrImage) {
+                $sTitleAttr = bx_html_attribute(strip_tags($aItems[$iKey]['title']));
+
+                if(!$bTitleAttr)
+                    $aItems[$iKey]['title_attr'] = $sTitleAttr;
+
+                if(!$bTitleAttrImage)
+                    $aItems[$iKey]['bx_if:image']['content']['title_attr'] = $sTitleAttr;
+            }
+        }
+
+        return $aItems;
+    }
+
+    protected function _isVisible ($a)
+    {
+        if(!parent::_isVisible($a))
+            return false;
+
+        $bResult = true;
+        switch ($a['name']) {
+            case 'switch_language':
+                $aLanguages = BxDolLanguagesQuery::getInstance()->getLanguages(false, true);
+                if(count($aLanguages) <= 1)
+                    $bResult = false;
+                break;
+
+            case 'switch_template':
+                $aTemplates = get_templates_array(true, true);
+                if(count($aTemplates) <= 1)
+                    $bResult = false;
+                break;
+        }
+
+        return $bResult;
+    }
+}
+
+/** @} */

+ 85 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseMenuSwitchLanguage.php

@@ -0,0 +1,85 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * Site main menu representation.
+ */
+class BxBaseMenuSwitchLanguage extends BxTemplMenu
+{
+    protected $_sType;
+
+    public function __construct ($aObject, $oTemplate)
+    {
+        parent::__construct ($aObject, $oTemplate);
+
+        $this->_sType = str_replace('sys_switch_language_', '', $this->_sObject);
+    }
+
+    public function getMenuItems ()
+    {
+        $this->loadData();
+
+        return parent::getMenuItems();
+    }
+
+    protected function loadData()
+    {
+        $aLanguages = BxDolLanguagesQuery::getInstance()->getLanguages(false, true);
+        if(empty($aLanguages) || !is_array($aLanguages) || count($aLanguages) < 2)
+            return;
+
+        $sLanguage = BxDolLanguages::getInstance()->getCurrentLangName();
+        $this->setSelected('', $sLanguage);
+
+        list($sPageLink, $aPageParams) = $this->{'getBaseUrl' . bx_gen_method_name($this->_sType)}();
+
+        $oPermalink = BxDolPermalinks::getInstance();
+		$sMethod = 'getItemTitle' . bx_gen_method_name($this->_sType);
+
+        $aItems = array();
+        foreach( $aLanguages as $sName => $sLang ) {
+            $aPageParams['lang'] = $sName;
+
+            $aItems[] = array(
+                'id' => $sName,
+                'name' => $sName,
+                'class' => '',
+                'title' => $this->$sMethod($sName, $sLang),
+                'target' => '_self',
+                'icon' => '',
+                'link' => bx_html_attribute($oPermalink->permalink(bx_append_url_params($sPageLink, $aPageParams))),
+                'onclick' => ''
+            );
+        }
+
+        $this->_aObject['menu_items'] = $aItems;
+    }
+
+    protected function getBaseUrlPopup()
+    {
+        return bx_get_base_url_popup();
+    }
+
+    protected function getBaseUrlInline()
+    {
+        return bx_get_base_url_inline();
+    }
+
+    protected function getItemTitlePopup($sName, $sTitle)
+    {
+    	return genFlag($sName) . ' ' . $sTitle;
+    }
+
+    protected function getItemTitleInline($sName, $sTitle)
+    {
+    	return genFlag($sName);
+    }
+}
+
+/** @} */

+ 252 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseSearchExtended.php

@@ -0,0 +1,252 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * Extended Search objects representation.
+ * 
+ * @see BxDolSearchExtended
+ */
+class BxBaseSearchExtended extends BxDolSearchExtended
+{
+    protected $_sFormClassName;
+    protected $_sFormClassPath;
+
+    protected $_oForm;
+    protected $_oTemplate;
+
+    protected $_bJsMode;
+
+    public function __construct ($aObject, $oTemplate = null)
+    {
+        parent::__construct ($aObject);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else if(!empty($this->_aObject['module']))
+            $this->_oTemplate = BxDolModule::getInstance($this->_aObject['module'])->_oTemplate;
+        else 
+            $this->_oTemplate = BxDolTemplate::getInstance();
+
+        $this->_sFormClassName = '';
+        $this->_sFormClassPath = '';
+
+        $this->_oForm = null;
+
+        $this->_bJsMode = false;
+    }
+
+    public function getForm($aParams = array())
+    {
+        if(!$this->isEnabled())
+            return '';
+
+        $oForm = $this->prepareForm($aParams);
+        return $oForm->getCode();
+    }
+
+    /**
+     * Get search results from search form or from custom condition
+     *
+     * @param $aParams['cond'] custom condition to pass instead of form submission, 
+     *        conditions are key&value pair, where 'key' is form input name and 
+     *        'value' is the term to search for
+     * @param $aParams['start'] position of first record to display 
+     * @param $aParams['per_page'] number of items per page
+     * @param $aParams['template'] custom unit templates to use
+     * @return HTML string with search results
+     */ 
+    public function getResults($aParams = array())
+    {
+        if(!$this->isEnabled())
+            return '';
+
+        $bJsMode = isset($aParams['js_mode']) ? (bool)$aParams['js_mode'] : $this->_bJsMode;
+        $bCondition = !empty($aParams['cond']) && is_array($aParams['cond']);
+
+        $iStart = !empty($aParams['start']) ? $aParams['start'] : 0;
+        $iPerPage = !empty($aParams['per_page']) ? $aParams['per_page'] : 0;
+        unset($aParams['start'], $aParams['per_page']);
+
+        $sUnitTemplate = !empty($aParams['template']) ? $aParams['template'] : '';
+
+        $oForm = $this->prepareForm($aParams);
+        if($bCondition) {
+            $mixedSubmitName = $oForm->aParams['db']['submit_name'];
+            if(is_array($mixedSubmitName))
+                $mixedSubmitName = array_pop($mixedSubmitName);
+            $aParams['cond'][$mixedSubmitName] = 1;
+
+            $oForm->aFormAttrs['method'] = BX_DOL_FORM_METHOD_SPECIFIC;
+            $oForm->aParams['csrf']['disable'] = true;
+
+            $oForm->initChecker(array(), $aParams['cond']);
+        }
+
+        if(!$oForm->isSubmittedAndValid()) 
+            return '';
+
+        $oContentInfo = BxDolContentInfo::getObjectInstance($this->_aObject['object_content_info']);
+        if(!$oContentInfo)
+            return '';
+
+        $aParamsSearch = array();
+        foreach($this->_aObject['fields'] as $aField) {
+            $sValue = $oForm->getCleanValue($aField['name']);
+            if(empty($sValue) || (is_array($sValue) && bx_is_empty_array($sValue)))
+                continue;
+
+            $aParamsSearch[$aField['name']] = array(
+                'type' => $aField['search_type'],
+                'value' => $sValue,
+                'operator' => $aField['search_operator']
+            );
+
+            if(!$bCondition)
+                $aParams['cond'][$aField['name']] = $sValue;
+        }
+
+        if(empty($aParamsSearch) || !is_array($aParamsSearch))
+            return '';
+
+        if (!$iPerPage) {
+            bx_import('BxDolSearch');
+            $iPerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT;
+        }
+
+        $aResults = false;
+        bx_alert('search', 'get_data', 0, false, array('object' => $this->_aObject, 'search_params' => $aParamsSearch, 'search_results' => &$aResults));
+    	if($aResults === false)
+    	    $aResults = $oContentInfo->getSearchResultExtended($aParamsSearch, $iStart, $iPerPage + 1);
+
+    	if(empty($aResults) || !is_array($aResults))
+    	    return '';
+
+        if(!empty($aParams['cond']) && is_array($aParams['cond']))
+            $aParams['cond'] = urlencode(serialize($aParams['cond']));
+
+        $aPaginate = array('start' => $iStart, 'per_page' => $iPerPage);
+        if(!$bJsMode) {
+            $aParams['start'] = '{start}';
+            $aParams['per_page'] = '{per_page}';
+            list($sPageLink, $aPageParams) = bx_get_base_url_inline($aParams);
+
+            $aPaginate['page_url'] = BxDolPermalinks::getInstance()->permalink(bx_append_url_params($sPageLink, $aPageParams));
+        }
+        else
+            $aPaginate['on_change_page'] = "return !loadDynamicBlockAutoPaginate(this, '{start}', '{per_page}', " . bx_js_string(json_encode($aParams)) . ");";            
+
+        $oPaginate = new BxTemplPaginate($aPaginate);
+        $oPaginate->setNumFromDataArray($aResults);
+
+        $bTmplVarsPaginate = $iStart || $oPaginate->getNum() > $iPerPage;
+        $aTmplVarsPaginate = $bTmplVarsPaginate ? array('paginate' => $oPaginate->getSimplePaginate()) : array();
+
+    	$sResults = '';
+    	foreach($aResults as $iId)
+    	    $sResults .= $oContentInfo->getContentSearchResultUnit($iId, $sUnitTemplate);   	
+
+        return $this->_oTemplate->parseHtmlByName('search_extended_results.html', array(
+            'code' => $sResults,
+            'bx_if:show_paginate' => array(
+                'condition' => $bTmplVarsPaginate,
+                'content' => $aTmplVarsPaginate
+            )
+        ));
+    }
+
+    protected function &prepareForm($aParams = array())
+    {
+        if(!empty($this->_oForm) && $this->_oForm instanceof BxDolForm)
+            return $this->_oForm;
+
+        $sForm = 'sys_search_extended_' . $this->_sObject;
+        $sFormSubmit = 'search' . $this->_sObject;
+
+        list($sPageLink, $aPageParams) = bx_get_base_url_inline();
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => $sForm,
+                'name' => $sForm,
+                'action' => BxDolPermalinks::getInstance()->permalink(bx_append_url_params($sPageLink, array('i' => $aPageParams['i']))),
+                'method' => 'post'
+            ),
+            'params' => array(
+                'db' => array(
+                    'table' => '',
+                    'key' => '',
+                    'uri' => '',
+                    'uri_title' => '',
+                    'submit_name' => $sFormSubmit
+                ),
+            ),
+            'inputs' => array()
+        );
+
+        foreach ($this->_aObject['fields'] as $aField) {
+            if((int)$aField['active'] == 0)
+                continue;
+
+            if (in_array($aField['search_type'], array('checkbox_set', 'select_multiple')) && (isset($aField['values']['']) || isset($aField['values'][0]))) {
+                unset($aField['values']['']);
+                unset($aField['values'][0]);
+            }
+            
+            $aAttrs = array();
+            
+            if(in_array($aField['search_type'], array('datepicker_range_age')) && isset($aField['search_value']) && $aField['search_value'] != ''){
+                $aTmp = BxDolService::callSerialized($aField['search_value']);
+                if (isset($aTmp['min']) && isset($aTmp['max'])){
+                    $aField['search_value'] = $aTmp['min'] . '-' . $aTmp['max'];
+                    $aAttrs = array('min' => $aTmp['min'], 'max' => $aTmp['max'], 'step' => 1);
+                }
+            }
+            
+            if(in_array($aField['search_type'], array('datepicker_range')) && isset($aField['search_value']) && $aField['search_value'] != ''){
+                $aField['search_value'] = '';
+            }
+           
+            $aForm['inputs'][$aField['name']] = array(
+                'type' => $aField['search_type'],
+                'name' => $aField['name'],
+                'caption' => _t($aField['caption']),
+            	'info' => _t($aField['info']),
+                'values' => $aField['values'],
+                'value' => $aField['search_value'],
+                'attrs' => $aAttrs,
+                'db' => array(
+                    'pass' => !empty($aField['pass']) ? $aField['pass'] : 'Xss'
+                )
+            );
+        }
+
+        $aForm['inputs']['search'] = array(
+            'type' => 'submit',
+            'name' => $sFormSubmit,
+            'value' => _t('_Search')
+        );
+
+        $sClass = 'BxTemplSearchExtendedForm';
+        if(!empty($this->_sFormClassName)) {
+            $sClass = $this->_sFormClassName;
+            if(!empty($this->_sFormClassPath))
+                require_once(BX_DIRECTORY_PATH_ROOT . $this->_sFormClassPath);
+        }
+
+        $bJsMode = isset($aParams['js_mode']) ? (bool)$aParams['js_mode'] : $this->_bJsMode;
+        $bCondition = !empty($aParams['cond']) && is_array($aParams['cond']);
+
+        $this->_oForm = new $sClass($aForm, $this->_oTemplate);
+        $this->_oForm->initChecker(!$bJsMode && $bCondition ? $aParams['cond'] : array());
+
+        return $this->_oForm;
+    }
+}
+
+/** @} */

+ 74 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/files/template/scripts/BxBaseSearchExtendedServices.php

@@ -0,0 +1,74 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaBaseView UNA Base Representation Classes
+ * @{
+ */
+
+/**
+ * System services related to Extended Search.
+ * 
+ * @see BxDolSearchExtended
+ */
+class BxBaseSearchExtendedServices extends BxDol
+{
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    public function serviceGetForm($aParams)
+    {
+        $this->prepareParams($aParams);
+        
+        if(empty($aParams['object']))
+            return '';
+
+        $oSearch = BxDolSearchExtended::getObjectInstance($aParams['object']);
+        if(!$oSearch || !$oSearch->isEnabled())
+            return '';
+
+        return $oSearch->getForm($aParams);
+    }
+
+    public function serviceGetResults($aParams)
+    {
+        $this->prepareParams($aParams);
+
+        if(empty($aParams['object']))
+            return '';
+
+        $oSearch = BxDolSearchExtended::getObjectInstance($aParams['object']);
+        if(!$oSearch || !$oSearch->isEnabled())
+            return '';
+
+        $sResults = $oSearch->getResults($aParams);
+
+        return !empty($sResults) ? $sResults : (isset($aParams['show_empty']) && (bool)$aParams['show_empty'] ? MsgBox(_t('_Empty')) : ''); 
+    }
+    
+    public function prepareParams(&$aParams)
+    {
+        if(empty($aParams['object']) && bx_get('object') !== false)
+            $aParams['object'] = bx_process_input(bx_get('object'), BX_DATA_TEXT);
+
+        if(!isset($aParams['show_empty']) && bx_get('show_empty') !== false)
+            $aParams['show_empty'] = (bool)bx_get('show_empty');
+
+        if(empty($aParams['template']) && bx_get('template') !== false)
+            $aParams['template'] = bx_process_input(bx_get('template'), BX_DATA_TEXT);
+
+        if(empty($aParams['cond']) && bx_get('cond') !== false)
+            $aParams['cond'] = unserialize(urldecode(bx_process_input(bx_get('cond'), BX_DATA_TEXT)));
+
+        if(!isset($aParams['start']) && bx_get('start') !== false)
+            $aParams['start'] = (int)bx_get('start');
+
+        if(!isset($aParams['per_page']) && bx_get('per_page') !== false)
+            $aParams['per_page'] = (int)bx_get('per_page');
+    }
+}
+
+/** @} */

+ 6 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/script.php

@@ -0,0 +1,6 @@
+<?php
+
+    if (!$this->oDb->isFieldExists('sys_objects_view', 'module'))
+        $this->oDb->query("ALTER TABLE `sys_objects_view` ADD `module` varchar(32) NOT NULL default '' AFTER `name`");
+
+    return true;

+ 6 - 0
upgrade/files/9.0.0.RC11-9.0.0.RC12/sql.sql

@@ -0,0 +1,6 @@
+
+
+-- Last step is to update current version
+
+UPDATE `sys_modules` SET `version` = '9.0.0-RC12' WHERE (`version` = '9.0.0.RC11' OR `version` = '9.0.0-RC11') AND `name` = 'system';
+