Table of Contents

What is Bitrix Framework?

Bitrix Framework - is a PHP-based platform for the development of web applications. It was also used to create Bitrix24.

Bitrix24 is a ready-made product that enables the creation of the corporate portal of a company with the possibility to customize standard functionality according to a company’s needs.

Unlike Zend Framework, during the deployment of Bitrix Framework you obtain a set of classes as well as an advanced administrator’s interface.

The basic package includes an extensive set of components that ensures the quick deployment and implementation of projects.

Products based on Bitrix Framework are released in several versions. When studying the system and reviewing lessons you must be sure that your local installation has a module that you experiment with. Module-by-module comparison of versions: for Bitrix Site Manager and for Bitrix24.

Note: the training course will include examples from the tasks that normally have to be solved by developers of various products. The mechanisms that used to solve these tasks may be applied in any other product created on Bitrix Framework. To this extent, the words site and corporate portal used in this course may be considered synonyms.

For Those Who Switched to Bitrix Framework from Other Platforms

The programmers who switched to Bitrix Framework from other platforms and CMS face additional difficulties caused by the “pressure” of previous experience.

In order to learn how to work in Bitrix Framework efficiently, you should try to understand the way other things are implemented in this system rather than compare the things you know from other systems. The “comparative” method is of no use for this course. Omit your old knowledge and study the new system using only your knowledge of PHP and site building rather than comparing ideologies and technologies. This will make it easier for you to grasp. You may indulge in comparison later, after you have mastered Bitrix Framework.

The direct comparison of Bitrix Framework and other systems does not always work. And yet these questions do arise. That is why we will include some opinions given by the partners of Bitrix and programmers working on Bitrix Framework.

Bitrix Framework and Drupal

The main structural unit of CMS Drupal is a node. As a matter of fact, any page of a website on Drupal (except for service) is either a list of node previews or a complete display of one node. Any page may be displayed together with additional blocks, but they are secondary in relation to the node anyway.

In Bitrix Framework the ideology of infoblocks is implemented. Infoblocks are structurally similar to a table in a database. Infoblock is a collection of objects with the same set of properties.

All infoblocks are equal in the sense that any infoblock (or even several infoblocks) may be used for displaying both in the primary area of the page and in additional areas. Thus, a node in CMS Drupal is just a particular case of infoblock, and this system actually has only one infoblock while Bitrix Framework may have an unlimited number of them.

Bitrix Framework and Joomla

  • The site templates in Bitrix Framework more or less correspond in concept to the site templates in Joomla.
  • Creation of a site template for Bitrix Framework using a ready-made layout consists in the selection of blocks and the placement of components instead of these blocks. Then, these components are set up for a data source and output templates are edited for them in accordance with the site layout.
  • Unlike Joomla, the Bitrix Framework site template has no positions for numbered modules. Therefore, a programmer cannot indicate from the administration section as to which model must be allocated to which position simply by changing a number. The placement of components in Bitrix Framework is different.
  • Modules in Bitrix Framework constitute a backbone for the integration of all the functions necessary for programmers in a single place and their separation by versions. Extensions perform the same function in Joomla.
  • A module in Joomla is a component in Bitrix Framework. A component obtains data from somewhere and displays them as necessary. A component template is responsible for proper displaying.

    One component may have several templates that display information differently. For example, if you need to display news and articles on the site, you should create two folders on the disk, put the News integrated component into each folder, and set them up for a data source. After that, you can revise templates.

  • Dynamic and static data are separated in Bitrix Framework. There are clearly dynamic blocks, for example a catalog of goods. There are static blocks. And there are mixed blocks. However, in order to display dynamic information, e.g., a catalog of goods, you must build a “house” for it – a folder on the disc where a “catalog” integrated component will be located and process references to dynamic information.

Glossary

Glossary

Each platform uses its own terminology. In order to find your way around documentation and training courses, when communicating with developers it is advisable to explain your difficulties in a language that is understandable to specialists instead of using lay terms. A glossary of system terms and several general terms extensively used in the system is provided below for your convenience.


TermDescription
Program instanceA copy of the product consisting of the product’s source code and only one copy of the structure and database tables included in the product and also any product’s instruction for use.
PortalOne set of files stored in the directory /bitrix/modules/ and one copy of the base. The portal comprises one or more sites. The following expressions may be used as synonyms of this term: “product instance,” “one system installation,” and “one system copy.”
SiteA set of the following notions:
  • Database account is created in the Sites administrative menu and includes the following main parameters:
    • Identifier – a set of symbols identifying the site;
    • Domain name – one or more domain names of the site;
    • Site folder – a path to the catalog where the public part of the site will be stored;
    • Language of the site;
    • Date and time format;
    • URL - a default protocol and domain name (for example: http://www.site.com);
    • DocumentRoot - if multi-siting is organized on different domains, this parameter must contain a path through the server file system to the site root;
    • Template connection conditions – each site may have one or more templates for reflecting scripts of its public part, each of such templates may be connected according to a certain condition;
  • Public part – is a set of scripts (pages) located in the “site folder” and belonging to this site;
  • Settings – each module may have a number of settings associated with the site; for example the settings of the Information Blocks module represent the binding of an information block to a certain site, and the settings of the Helpdesk module determine the binding of a status, category, etc. to the site.
Site templateThe synonyms are site design and site skin. Several different templates can be used to display one site. A site template includes the following:
  • a set of files in the directory /bitrix/templates/template_ID/, where template_ID is an ID field in the form of a site template editing. The structure of this directory is as follows:
    • /components/ - directory with components belonging to a certain module;
    • /lang/ - language files belonging both to this template in general and to separate components;
    • /images/ - a directory with images used in this template;
    • /page_templates/ - a directory with page templates and their description stored in the file .content.php;
    • /include_areas/ - a directory with content files of the areas to be included;
    • header.php - is a prologue of this template;
    • footer.php - is an epilogue of this template;
    • styles.css - CSS styles applied on the site pages when this template is used;
    • template_styles.css - CSS styles used in the template itself;
    • .menu type.menu_template.php - a template for displaying menu of a relevant type;
    • chain_template.php - a default template for display of navigational chain;
    • and also a number of other auxiliary random files included in this template.
Site sectionA directory in the server file system. Site structure in Bitrix Framework is a server file system, which is why the files constitute site pages and, accordingly, directories constitute site sections.
Component This is a part of a certain module. It represents a logically completed code stored in one file which accepts a number of parameters, performs a number of actions, and displaying a result (for example, in the form of HTML code). It is strongly recommended to use components for the organization of information display both in public and in administrative parts. The term component comprises the following elements:
  • master file connects using the function CMain::IncludeFile which represents a PHP script implementing the logic of component;
  • language files - are used in component to display its result in different languages;
  • description - is a PHP code used by visual editor to manage component and its parameters.
Nowadays, version 2.0 of the components is used. The use of components from 1.0 is not recommended but they are still used on the sites built on earlier versions of the system.
Update systemThe technology SiteUpdate permits the following:
  • Download product updates;
  • Upload new modules and updates for existing modules to extend their functionality;
  • Upload language files and install new languages;
  • Register licenses for additional sites.
The data shall be downloaded from the site of Bitrix, Inc. through the web interface of the administrative section of the product. During the update only core of the product is modified (files contained in the folders /bitrix/modules/, /bitrix/tools/, /bitrix/admin/, and /bitrix/components/bitrix/). The update does not affect the public part of the portal completely eliminating the risk of data loss.

The system is updated in several steps:

  • The update system automatically requests a license key of the product;
  • Next, a check of the available updates is performed;
  • Then, a user is offered to select updates for downloading;
  • After that, selected updates are downloaded.
All updates and new modules are compressed first and then downloaded to the owner’s site.
Component pathThis is used in the function CMain::IncludeFile as a first parameter and represents a path to the master file of a component in respect to one of the following directories (in order of priority):
  • /bitrix/templates/site_template_ID/;
  • /bitrix/templates/.default/;
  • /bitrix/modules/module_ID/install/templates/
Where:
Site_template_ID is an identifier of the current site template,
Module_ID - is an identifier of the module to which the component belongs.
Areas to Be Included This is a specially selected area on a site page that may be edited independently from the primary contents of the page. It is implemented using a special software component.
LanguageIt is an account in the database that is accessible for editing in the administrative menu on the Interface Languages page with the following fields: Both in the public and administrative parts, language is primarily used in work with language files.
In the administrative part, the language determines the time and date format and page encoding (in the public part these parameters are determined by the site settings).
Language fileThis is a PHP script containing translations of language phrases into a certain language. This script consists of a $MESS array with keys that are identifiers of language phrases and values that are translations into a relevant language. For each language there is a certain set of language files which, as a rule, are stored in the directories /lang/.
As a rule, language files are used in the administrative scripts of modules and in components.
The module Localization is intended for work with language files.
Page templateThis is a file stored in one of the directories:
  • /bitrix/templates/site_template_ID/page_templates/
  • /bitrix/templates/.default/page_templates/
This file represents a blank for a public page. As a rule, it is used for creating a new page in the Site Explorer module. For the creation of the procedure for page template sorting of the file .content.php located in one of the aforementioned directories is used.
Prologue

Generally, this term means the top left part of a page.

For the public part the prologue of the relevant site template is stored in the file /bitrix/templates/site template ID/header.php.

For the administrative part the prologue is stored in the file /bitrix/modules/main/interface/prolog_admin.php.

In its turn, the prologue may be divided into a service and a visual part. In the service part, all the necessary classes are connected, connection with the base is established, and a number of service instances of objects such as $USER, $APPLICATION, etc. are created. In the visual part, top left part of the page is displayed.

If an undivided prologue must be connected in the public part, we use the following code:

require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");

If due to any reasons we have to divide the prologue into a service (prolog_before.php) and a visual (prolog_after.php) part, we use the following codes:

require($_SERVER["DOCUMENT_ROOT"].
"/bitrix/modules/main/include/prolog_before.php");
...
require($_SERVER["DOCUMENT_ROOT"].
"/bitrix/modules/main/include/prolog_after.php");
Epilogue

Generally, this term means the bottom right part of a page.

For the public part, the epilogue of the relevant site template is stored in the file /bitrix/templates/site template ID/footer.php.

For the administrative part, the epilogue is stored in the file /bitrix/modules/main/interface/epilog_admin.php.

In its turn, the epilogue may be divided into a service and a visual part. Some actions like sending mail messages, execution of OnAfterEpilog event handlers, etc. are performed in the service part. In the visual part, the bottom right part of the page is displayed.

If an undivided epilogue must be connected in the public part, we use the following code:

require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");

If due to any reasons we have to divide the epilogue into a visual (epilog_before.php) and a service (epilog_after.php) part, we use the following codes:

require($_SERVER["DOCUMENT_ROOT"].
"/bitrix/modules/main/include/epilog_before.php");
...
require($_SERVER["DOCUMENT_ROOT"].
"/bitrix/modules/main/include/epilog_after.php");
Page bodyPage body is a part of the PHP/HTML code located in the script between the connections of the prologue and epilogue. Page body is not a part of the site template and represents the individual contents of a public or administrative page.
Navigation chain (breadcrumbs)It is a design element intended primarily for improving navigation on the site. Navigation chain is displayed in the visual part of the prologue and consist of headers of site sections with relevant links to them. In addition to automatically added section headers you can also add arbitrary points to a navigation chain.
Administrative partThis is a system section containing an interface for managing system modules and structure, contents, visitors, and other components of the site. It consists of numerous scripts in which the administrative prologue and epilogue are connected. Their connection means the following:
  • This page does not belong to any site;
  • At the same time it belongs to a module;
  • It has a strictly determined administrative interface;
  • All localization parameters of this page depend on a selected current language;
  • Additional verification of rights established in the settings of a relevant module is used on this page.
Public partThis is a system section available for displaying to site visitors. Generally, this term means numerous scripts in which the prologue and epilogue of one of the site templates are connected. Their connection means the following:
  • This page belongs to a site;
  • It has an interface of a current site template;
  • All localization parameters of this page depend on the current site.
Revision mode A mode when the current page of the public section is displayed in a special form: the components used, areas to be included, areas to be revised, etc. are marked. Each such area has a set of buttons to quickly switch to the editing of this page element.
Index page (file)This is a file name to be used by web server if the requested URL ends with a slash and contains no file name. There are different ways to establish the index page search order for different web servers, for example:
  • Apache - in the file httpd.conf, parameter DirectoryIndex;
  • IIS - in site properties, tab Documents > Enable default content page;
Unfortunately, the value of this parameter is unavailable in PHP. That is why the function GetDirIndex must be used to determine the index page in the code.
UserAn entry in the database containing parameters of a registered user with such mandatory fields as:
  • Login;
  • Password;
  • E-Mail,

and also a number of additional fields containing a user’s personal information, information about a user’s work, and administrative notes.

Registration data (login and password) indicated by the user during registration or obtained by the user automatically (e.g., after importing) will be used for authorization in the system. Following registration, the user is allocated to a specific group and receives the right to access portal resources in accordance with the rights granted to such group.

Group of usersA number of portal users having certain rights to access and manage portal resources (e.g., users of the Moderators group have the right to read and edit forum messages). Groups of users are managed on the page Group of users in the administrative section (Control Panel > Settings > Manage users > User groups).
Multilanguage interface The functionality is implemented by using language files containing a translation of the phrases into the relevant languages for the following:
  • Administrative section;
  • Error messages;
  • Visual components;
  • Relevant areas in the portal template;
  • etc.
Necessary language files are connected in accordance with the current interface language. As a result, messages are displayed to the user in a language selected by the user. Switching between the languages of the administrative interface is possible using relevant buttons on the administrative panel.
Meta tagA meta tag is one of the HTML elements that permit you to set information about a page. For example, to indicate page encoding, its keywords, author, and to give a short description of the page. As a rule, the contents of meta tags are used for service purposes, for example by web crawlers indexing the site. Meta tag is determined inside the tag <head>.
LocalizationMeans provision of information translated into a relevant language, in the appropriate encoding of this language, and using appropriate data format characteristic for such a language (e.g., date, time, monetary units, numbers, etc.).
HostThis is applicable to functions of the main module, this term means a domain name or IP address that may be used to access a site.
Domain name (domain)Strictly speaking, it is one of the fields of the DNS (domain name service) table that contains a strongly structured name of the Internet site given according to specific rules. The principal task of the DNS table is the association of domain names with the site's IP addresses. An example of a domain name: bitrixsoft.com.
IP addressIt is a “name” of the computer in the network given according to the rules of TCP/IP protocols. IP address consists of 4 octets. One part of these octets identifies the subnet where the computer is located, and another part identifies this computer directly within a relevant subnet. An example of an IP address: 198.63.210.79.
Accept-LanguageA set of languages installed in a browser of a site’s visitor. For example, in MS Internet Explorer they may be installed in the menu Tools > Internet Options > General > Languages. In Mozilla Firefox: Tools > Options > General > Languages.
Unix-format timeA number of seconds passed since January 1, 1970, accurate up to a microsecond. Nowadays, the Unix-format time may be fixed only up to the year 2038.
Rights in Unix systemsThree types of rights – read, write, and execute are supported in the operating systems similar to Unix (which are presently predominate for Internet servers). These rights are allocated to each file or directory. The rights are repeated thrice: for the file owner, for the users’ group to which the owner belongs, and for all other users. As a rule, the rights are indicated in numerical format:
  • 4 - read;
  • 2 - write;
  • 1 - execute.

The sum of these numbers gives a final set of rights; e.g. 6 is read and write but without execute; 7 – all rights; 5 – read and execute.

Thus, e.g., the right 764 will mean: 7 – all rights for the file owner, 6 – read and write for the users’ group to which the file owner belongs, and 4 – read for all other users.

In PHP, all rights are indicated as octal numbers, and they must be given indicating prefix 0 without fail. Example: 0755.

Root-related pathA path to a file starting from the catalog indicated in the parameter DocumentRoot in the web server settings established according to the rules of forming URL addresses. Example: /en/about/index.php.
Full pathIncludes protocol, domain, port, and a root-related path to a page (catalog). Example: http://www.bitrixsoft.com/en/about/index.php.
Absolute pathAn absolute path to a file includes a DocumentRoot and a root-related path.
DocumentRootA path to the site root in the server file system. It shall be set in the web server settings, for example:
  • for Apache – in the file httpd.conf, parameter DocumentRoot;
  • for IIS – in the site properties; tab Home Directory > Local Path.
SubmitSubmission of HTML form data to server.
SessionThis term shall mean a PHP session. The session may open at the time of visiting the site and close when the browser window is closed. Also, a new session opens once a user has gone through authorization, and closes when a user terminates the authorization session (logs off). One “site visit” can be considered a synonym for the “session” term.
HTML-safe typeAs a rule, this term shall apply to the text where the following replacements have been made:
SourceResult
<&lt;
>&gt;
"&quot;
&&amp;

Such replacements permit to display text inside HTML code without the risk that it will be interpreted by a browser as part of such HTML code.
Variable bindingAs applicable to SQL requests, this term means the binding of variable names (or table fields) with their values. As a rule, such technology is used for the BLOB, CLOB, LONG, etc. type fields intended for storage of big volumes of data.
DumpAs applicable to the data base, this term means the downloading of contents and, possibly, the table structure into a file in a specific format for their further upload back to the same or any other base. Each base has its own utilities for dumping. For example, in MySQL the mysqldump utility permits downloading in the format of regular SQL requests.
As applicable to variables, dump means displaying the structure and contents of a variable as a text.
cronIn operating systems similar to Unix, the cron utility permits to arrange for script execution according to a clearly established schedule.
BufferingThis term in PHP means such a mode that all outgoing data flow from PHP script (e.g., HTML code) is tentatively stored in memory without being submitted to the user’s browser. Buffering in PHP may be activated using the function ob_start. Later on, buffering may be deactivated e.g. by using the function ob_end_flush; in this case, all accumulated data will be sent to the browser. Buffering permits you to arbitrarily manipulate outgoing data flow and serves as a basis for the deferred function technology.
Persistent connectionWhen the connection with the base is created, a descriptor is set up in the memory for this connection. If the connection is normal, then after the execution of a script this descriptor is eliminated; and if the connection is persistent, the descriptor remains and can be used by other processes, if necessary. The advantage of a persistent connection is that it normally works faster. But there is also a drawback: the number of opened persistent connections is limited in the database settings and in case this limit is exceeded the visitor will not be able to enter the site until new connections are released.
Mail eventThis is a mail message that has its own type and is sent using relevant mail template. Mail event initiates fields of mail event type with specific values. The procedure for layout of these fields in a letter and also text of the letter is determined by mail template.
The class CEvent is intended for the creation of a mail event. It is used in the mail system.
Mail templateThis determines text of mail message and also procedure for layout of fields (placeholders) set in the type of mail event.
Mail templates are available in the administrative section on the page E-mail templates (Control Panel > System settings > Email Events > E-Mail templates).
The class CEventMessage is intended for manipulation of mail templates. It is used in the mail system.
Mail event typeDetermines a set of special fields (placeholders) that may be used in a mail template. At the time of the creation of a mail event these fields will be initiated with specific values.
The class CEventType is intended for the manipulation of mail events. It is used in the mail system.
CustomizationChange of behavior of a component or component template according to specific tasks.
API (SDK)Each system module contains a set of high-level functions for data retrieval in the public section of the site and a set of classes with low-level methods for more specialized work with module data. Detailed information on the API of each module is presented in the documentation for developers.

Production Architecture

In the course of its development, any software must be consistent with the initially set purpose. This task is solved by architecture design. Product architecture is an approach to design that guarantees the software will meet its designated purpose.

Software architecture is a structure of a software or computing system that includes software components, properties of these components visible from the outside, and also the relations between them.

The architecture of Bitrix Framework solves the following tasks:

  • Continuity. Each new release of the products supports all previous solutions and technology. It permits to upgrade site products created on virtually any previous version.
  • Unity of operating principles with any version and any solution based on the system.
  • Security. The architecture permits creating a sufficient level of security for sites of any purpose.
  • Scalability. There are no limits on project development according to an increase of the contents, services, and number of users.
  • Performance. System speed depends on the quality of setup of its elements, i.e. performance is mostly affected by a level of competence of the project developer and hosting capacities.
  • System development flexibility by third party developers. The architecture imposes no restrictions on the creation of own modules, components, and solutions.

Production Architecture

MVC Architecture for Bitrix Framework

MVC Template for Bitrix Framework:

  • Model is API;
  • View is templates;
  • Controller is a component.

Solid lines are direct connections; Dashed lines are indirect connections.

Structure

Bitrix Framework has a sophisticated and convenient structure that was rightly appreciated by numerous programmers and partners of the company. By levels of architecture, the structure may be described as follows:

Bitrix Framework:
  • module
  • components
  • files of pages
  • site:
  • template
  • components
  • page
  • component:
  • call
  • parameters
  • template
  • page:
  • header
  • workarea
  • footer
  • Bitrix Framework Structure Elements

    Modules

    Module is a model of data and API for access to these data. Static method of module classes may be fetched in components, templates, and other modules. Also, class instances may be created inside the Bitrix Framework context.

    Several dozen system modules contain a set of functions necessary for the implementation of a global, big task – a web form, operation of an Internet store, organization of a social network, and others. Modules also contain tools for a site administrator to manage these functions.

    Attention! Interference with system operation at the level of kernel and modules is strongly discouraged.

    Product kernel means files located in the directory /bitrix/modules/ and also files of system components: /bitrix/components/bitrix/.

    Components

    Component is a controller and view to be used in the public section. The component manipulates the data using the API of one or several modules. Component template (view) displays the data on the page.

    Components form part of modules but are responsible for solving a narrower, particular task – e.g., display a list of news or goods. It is recommended to amend the product code at the component level. Programmers can also modify them as they deem fit, use their own suggestions and use an unlimited number of templates on each of the components. One site page may contain several components; also, they may be included in the site template. Thus, a programmer has an opportunity to build up a site as a construction kit and after that improve the necessary components to obtain the result needed both functionally and visually.

    In order to work with API you just have to understand component structure of Bitrix Framework.

    Note: A module is a set of certain contents. A component is something that manages these contents.

    Let us take the module of Information Blocks as an example. This module represents a set of tables in database and php classes that can perform certain operations with data from tables (e.g., CIBlockElement::GetList() or CIBlockElement::GetByID ()). Meanwhile, News details is a component that has own settings (to show data, image, etc.) and works with methods of php classes of a module.

    Page

    A page is a PHP file consisting of prologue, page body (main working area), and epilogue. Site page is formed dynamically based on a page template used, data displayed by components, and statistical information located on the page.

    Structure of files

    Files and Database

    Bitrix Framework is based on files, and it gives more freedom to a site developer. Since a file in the system is just an executable file, it can execute anything, be it a programmer’s own PHP code or standard components, in any order. Curiously enough, such complete freedom may confuse a beginning developer, but things will get better as the developer gets more experience.

    Note: execution of PHP is a great advantage of a static page of Bitrix Framework.

    Files can be amended both in FTP and SSH with no need for additional tools of the database management system. They can be easily copied, moved, backed up, etc. Strictly speaking, you can store all the content in the database. But for simple static sites it will mean a clear complication and slowdown.

    File implementation seems problematic because such a system is expected to have tens of thousands of files on the disk. Normally that is not so. Dynamic information (news, catalog of goods, and articles) are stored in database by the module of Information blocks. Then for the display, for example, of 10,000 goods in the Internet store, the one and only physical page (file) is used. In this file, a component of infoblocks is retrieved that, in its turn, selects and displays goods from the database.

    E.g., for a catalog of goods a folder indeed must be created on the disk, but it will be the only folder, e.g., /catalog/. Then a complex component must be put there, and further on the pages of goods may look like, for example: http://***.com/catalog/1029.html. Obviously, these addresses will be “artificial” and subject to processing by the system. No files need to be created in the folder /catalog/ for them.

    However, for each item of goods, a file will be created in cache so that the server will not have to make requests to the database in case of a subsequent visit of the customer.

    With proper skill, the public part may consist of a dozen of physical files. All content may be presented in infoblocks, including the menu. But normally it is more convenient to edit static pages (e.g., About the company) as a file and not as a database entry. But if there are too many such static pages it may be a good idea to structure them and store them in infoblocks rather than on the disk.

    The system size is rather large since its composition includes numerous components that are necessary for the quick start and operation of the administrative part. Components are not consolidated because the system is modular. Modules, components, and templates have a certain structure. It is important both for system updates and the development of own components.

    A large number of files is characteristic of similar systems. (ZendFramework has the same particularity.) If hosting is configured properly, this problem will be solved by php precompilators. The amount of place allocated by the host and big number of system files are of critical importance.

    Summary. File system instead of database is chosen as a tool for storing the site structure due to the following reasons:

    • A file gives more freedom to the site developer because file in the system is just an executable file.
    • It is easier to manage. This representation is based on a structure of static HTML pages located in different folders. By making certain improvements (implementing a small amount of PHP code) to such a site we can have a project working on Bitrix Framework in no time.
    • To a certain extent it is a tradition that was of a great importance at the initial stage of CMS development.
    • Such representation is consistent with the experience of content managers who work with local file systems (folders and files).

    Site structure may also be organized in the database (infoblocks) but management of hierarchy in relational database is not very convenient.

    Let us consider the use of files in Bitrix Framework in these examples:

    1. File system and menu. The menu in files permits you to not connect the database where it is not really needed. The same applies to the properties of pages and sections and also file access rights. Theoretically, it is possible to build up an informational site with no requests to the database at all. Such a site will work faster, especially on shared hosting. There are also some advantages: when copying a section, the menu, access rights, and section properties are also automatically copied.
    2. File system and users. Users from the administrative section have access to core files and other program files. But users are different. E.g., Bitrix, Inc. technical support. If a web developer is not sure of their users, such a web developer can always prohibit users editing both PHP code and entire sections (cores). According to the modern concept of Bitrix Framework, the public part shall have no PHP code, everything must be encapsulated in components. In this case, a user edits either bare static page or sets up a component.
    3. File system and language versions. It would be difficult to support language information in a database. Since information in language files changes very rarely, it is easier to edit a line in a language file than to store these static phrases in the base. And, once again, the database is slow and excessive.

    File Structure

    The file structure of Bitrix Framework is organized in such a way so that the program components of the product core are separated from users’ files and also from the files determining external representation of the site. This particularity makes it possible to:

    • Avoid undesirable modification of the product core when working with system files.
    • Rule out the possibility of change of the public part of the site during downloads of product updates.
    • Set up external view of the site according to virtually any kind of task.

    The system in its entirety is located in the catalog /bitrix/ that contains the following subcatalogs and files:

    • /admin/ - administrative scripts;
    • /cache/ - cache files;
    • /activities/ - action folders for business processes;
    • /components/ - a folder for system and user’s components;
    • /gadgets/ - gadget folders;
    • /js/ - JavaScript files of modules;
    • /stack_cache/ - stack cache files;
    • /themes/ - themes of the administrative section;
    • /wizards/ - folders of wizards;
    • /images/ -images used both by the system in general and by separate modules;
    • /managed_cache/ - managed cache;
    • /modules/ - catalog with system modules where each subcatalog has its own strictly determined structure
    • /php_interface/ - auxiliary service catalog containing the following catalogue and files:
      • dbconn.php - database connection parameters;
      • init.php - additional portal parameters;
      • after_connect.php - is connected immediately after database connection was established;
      • dbconn_error.php - is connected in case of an error when database connection is being created;
      • dbquery_error.php - is connected in case of an error during the execution of an SQL query;
      • /site_ID/init.php - additional parameters of a site; the file is connected immediately after a special constant with the site identifier (SITE_ID) was determined;
    • /templates/ - a catalog with site and component templates, consists of the following subcatalogs:
      • /.default/ - a subcatalog with general files used by any given template by default; the structure of this catalog is similar to the structure of the catalog containing a specific template;
      • /site template ID/ - a subcatalog with site template containing the following subcatalogs and files:
        • /components/ - catalogue with customized component templates;
        • /lang/ - language files belonging both to this template in general and to separate components;
        • /images/ - catalog with images of this template;
        • /page_templates/ - catalog with templates of pages and their description stored in the file .content.php. When a user creates new page, they can choose a template from this catalog;
        • header.php - prologue for this template;
        • footer.php - epilogue for this template;
        • styles.css - CSS styles of a template;
    • /tools/ - during installation additional pages are copied to this catalog; these pages can be directly used on any pages of the site: help, calendar, image view, etc.;
    • /updates/ - a catalog automatically created by the update system;
    • header.php - a standard file connecting, in its turn, a specific prologue of the current site template; this file must be used on all pages of the public part;
    • footer.php - a standard file connecting, in its turn, a specific epilogue of the current site template; this file must be used on all pages of the public part;
    • license_key.php - a file containing a license key;
    • spread.php - a file used by the main module to transfer visitor’s cookies to additional domains of different sites;
    • redirect.php - a file used by the Statistics module to record link click events;
    • rk.php - a file used by the Advertising module by default to record banner click events;
    • stop_redirect.php - a file used by the Statistics module to issue any message to the visitor who is on the stop list;
    • activity_limit.php - a file used by the Statistics module to issue a message to the bot in case the bot exceeds the activity limit.

    Depending on the version used some catalogs and files may be unavailable.

    Access Rights

    Two levels of assignment of access privileges are supported in the Bitrix Framework system:

    • Access to files and catalogs.
    • Rights within the module logic.

    Access to Files and Catalogs

    This level of rights is verified in the prologue and is set using a special file .access.php containing a PHP array of the following format:

    $PERM[file/catalog][user group ID] = "access right ID";
    Where:
    • File/catalog – a file or catalog name for which access rights are assigned;
    • User group ID – user group ID to which this right applies (the symbol * may also be used which means – for all groups);
    • Access right ID – presently the following values are supported (in ascending order):
      • D - denied (in case of file query the access will always be denied);
      • R - read (in case of file query the access will be permitted);
      • U - document flow (the file may be edited in the document flow mode);
      • W - write (the file may be edited directly);
      • X - full access (means the right to “write” and modification of access rights).

    In the administrative part of the site the access rights to files and catalogs may be granted using Site Explorer.

    If a user belongs to several groups, the maximum right from all access rights set for these groups shall be selected.

    If the level of rights is not expressly set for the current file or catalogue, the level of rights set for the superior catalogs shall be selected.

    Example 1

    File /dir/.access.php

    <?
       $PERM["index.php"]["2"] = "R";
       $PERM["index.php"]["3"] = "D";
    ?>

    Attempting to open the page /dir/index.php, a user from the ID=3 group will have the access right D (denied), a user from the ID=2 group will have the right R (read), and a user who belongs to both groups will have the maximum access level – R (read).

    Example 2

    File /.access.php

    <?
       $PERM["admin"]["*"] = "D";
       $PERM["admin"]["1"] = "R";
       $PERM["/"]["*"] = "R";
       $PERM["/"]["1"] = "W";
    ?>

    File /admin/.access.php

    <?
       $PERM["index.php"]["3"] = "R";
    ?>

    Attempting to access the page /admin/index.php a user from the ID=3 group will have access and access to a user from the ID=2 group will be denied. All visitors will have access to the page /index.php.



    Rights within Module Logic

    As to regular static public pages, only the file and catalog access level 1 is applied to them.

    If a user has at least the right R (read) to a file and if this file is a functional part of a certain module, the 2nd level of rights set in the settings of the relevant module shall be verified. For example: when visiting the page List of queries in the technical support, the administrator can see all the queries, an employee of the technical support – only those that such an employee is responsible for, and a regular user sees only their own queries. This is the way the access right works as a part of the Technical support module logic.

    Two methodologies are used to assign 2nd level access privileges (level of rights within the module logic):

    • Methodology of right;
    • Methodology of role.

    Their difference consists in the following. If a user has several rights, the maximum right is selected. And if a user has several roles, such a user will have combined capacities of these roles, accordingly.

    The modules that support roles can be seen in the filter Module on the page Control Panel > Settings > Manage users > Access levels in the Administrative section. Rights are used in all other modules and in all other settings of the system.

    Example:

    • Rights. If you belong to groups for which the rights of Full administrative access and, for example, View of statistics without financial indicators are set in the Statistics module, you will have the maximum right – Full administrative access.
    • Roles. If you belong to groups for which the roles of Client technical support and Demo access are set in the Technical support module, you will simultaneously have the capacities of these two roles. I.e., you will be able to see all the queries in the demo access mode and at the same time create your own queries as a client of the technical support.

    A Site in Terms of Bitrix Framework

    Site is a set of the following:

    • Database account that is created in the administrative menu on the page Site list (Control Panel > Settings > System settings > Websites > Websites) and includes the following main parameters:
      • Identifier – a set of symbols identifying the site;
      • Domain name – one or more domain names of the site;
      • Site Folder – a path to the catalogue where the public part of the site will be stored;
      • Site language;
      • Data format;
      • Time format;
      • URL - protocol and domain name by default (for example, http://www.site.com);
      • DocumentRoot - this parameter shall be filled in if a multi-site system is used;
      • Template connection conditions: each site may have one or more templates for displaying pages of the public part, and each such template may be connected, subject to a certain condition.
    • Public part – a set of scripts (pages) located in the site folder and belonging to this site.
    • Settings. Each module may have a number of settings connected with the site; e.g. such settings for the Information blocks module consist in binding an information block to a certain site, and for the Technical support module – in the binding of a status, category, etc. to a site.

    There is the possibility to create and support an unlimited number of sites in Bitrix Framework based on a single product instance. The multi-site system has the following features:

    • Standard rights to managing site modules;
    • Standard set of users’ accounts for all sites;
    • Standard statistics system for all sites.

    This function will be described in more detail in the chapter Multiple Sites

    Structure

    Site structure within Bitrix Framework:

    • Template determines the presentation of site to users. There are component templates and site templates.
    • Components set data retrieval.
    • Page is an element of site structure.

    This chapter is dedicated to Page and Site template as elements of a structure. Components are covered in a separate chapter.

    Page Execution and Site Template

  • Site Template
  • Page Structure
  • Page Templates
  • Page Properties
  • Parameters
  • Execution Procedure
  • Site Template

    The synonyms of the term site template are: "site design" or "site skin". Several templates may be used to display a site. The site template includes:

    • set of files in the catalog /bitrix/templates/site template ID/
      where site template ID - the "ID" field in the site template edit form ("Site template" admin menu item). Below is the structure for such catalog:
      • header.php - template prolog;
      • footer.php - template epilog;
      • styles.css - styles content and included areas. These styles can be applied in a visual editor;
      • .menu type.menu_template.php - template for corresponding site settings menu;
      • chain_template.php - default template for showing navigation chain;
      • /components/ - catalog with components belonging to a specific module;
      • /lang/ - language files for both this template and individual components;
      • /images/ - catalog with images for this site template;
      • /page_templates/ - catalog with page templates and their description stored in the file .content.php;
      • as well as other auxiliary and arbitrary files included into this template.

    You apply a specific template to site in the site settings form, inside Templates section. You can use various conditions to apply a template.

    If section property "phone" is "Y"

    $APPLICATION->GetDirProperty("phone")=="Y"

    If current section is "/en/catalog/phone/"

    $APPLICATION->GetCurDir()=="/en/catalog/phone/"

    If current user - administrator

    $USER->IsAdmin()

    Note. You can connect prolog/epilog at the page without template:

    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_after.php");

    You can enter your code between these connections.

    The site template is also can be selected by calling CSite::GetCurTemplate. Its algorithm can be described as follows:

      All site templates are chosen in the following order:
    • those containing the PHP condition (the highest priority);
    • sort weight (the less is the value the higher is the priority);

      Thus, templates containing either the PHP condition or the less sort weight value, will have the highest priority.

        Then, the loop iterates on all the chosen templates. If the template contains the PHP condition, it is evaluated and if the result is "true", the path /bitrix/templates/site template ID/ is checked for existence. It this path exists, the loop breaks and the function CSite::GetCurTemplate returns this site template ID. In case none of the templates could be matched, the default template .default.php is returned.

      When specifying the PHP condition, it should be considered that the check is implemented by the function which has its own scope of variables. That's why the global variables should be specified by using the array $GLOBALS, the request variables - through the array $_REQUEST (or $_GET, $_POST) etc.

      The current site template is determined at the end of the prologue service section (prolog_before.php). Hence, the following can be used:

    • $APPLICATION object of the CMain class;
    • $USER object of the CUser class;
    • all the system constants of the type A;
    • all the standard PHP arrays: $GLOBALS, $_REQUEST, $_SERVER, $_SESSION, $_ENV, $_COOKIE etc.
    • When specifying the PHP condition for choosing the site template, it should be considered that the use of section properties is allowed, while page properties are not allowed. This is because the section properties are stored in a separate file .section.php, and the page properties are specified usually in a page body, after the including the prologue service section.

      Note: In the public section, the current site template ID is stored in the constant SITE_TEMPLATE_ID.

      Page Structure

      A Page is a PHP file consisting of a prolog page body (main working area), and epilog:
      • header
      • workarea
      • footer

      A site page is formed dynamically based on the page template used, data retrieved by the components, and the statistic information located on the page. The creation of site templates and the allocation of components in them are taken care of by site developers.

      Generally all site pages have the following structure:

      • Top – header. As a rule, includes the top and left part of the design with a static information (logo, slogan, etc.), top horizontal menu, and left menu (if they are stipulated by design). Dynamic informational materials may be included.
      • Main working area – work area. The page working area where the proper informational materials of the site are located. Both a physical file and a dynamic code created by the system based on complex components may be connected as the Main working area.

        If a physical file is connected as the Main working area, such a page is called static. If a dynamic code is connected, such a page is called dynamic.

      • Bottom – footer. As a rule, includes a static information (contact details, information about author and owner of the site, etc.), low horizontal menu, and right menu (if they are stipulated by design). Informational materials may be included.

      Note: For more details about the page structure, see the lesson Design Template.

      Top and bottom parts of the design are formed based on the site design template. I.e. the information displayed in these areas is determined by the parameters of the site template.

      Generally, the page structure looks like the following:

      <?
      // prolog connection
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      ?>
      page body
      <?
      // epilog connection
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
      ?>

      Thanks to the technology of deferred functions a part of visual elements displayed in the prolog may be set in the page body. These are such elements as:

      The key feature of this technology consists in its possibility to defer the performance of certain functions by performing them in the epilog, with the results of their performance substituted to the aforementioned code.

      A number of tasks cannot be resolved using the technology of deferred functions, for example when certain actions must be performed in the Prolog with values that in the previous example would be set in the page body (for example, page properties). In this case, the prolog must be divided into a service and a visual part, and the values must be set between them.

      It is achieved as follows:

      <?
      // connection of the service part of the prolog
      require($_SERVER["DOCUMENT_ROOT"]."/api_help/main/reference/prolog_before.php");
      
      // here, for example, a page property may be set
      // using the function $APPLICATION->SetPageProperty
      // and then process it in the visual part of the epilog
      
      // connection of a visual part of the prolog
      require($_SERVER["DOCUMENT_ROOT"]."/api_help/main/reference/prolog_after.php");
      ?>
      Page contents
      <?
      // connection of the epilog
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
      ?>

      The following occurs in the service part of the prolog:

      • Database connection;
      • Execution of agents;
      • Initialization of service constants;
      • Verification of access rights to files and catalogues;
      • Connection of necessary modules;
      • Execution of event handlers OnPageStart and OnBeforeProlog;
      • A number of other necessary actions.

      The service part of the prolog has a particularity that it does not display any data (does not send the header to the browser).

      In the visual part of the prolog the file /bitrix/templates/site template ID/header.php is connected, where site template ID is the identifier of the current site template. This file stores top left part of the current site template.

      The epilog may also be divided into a visual and a service part:

      <?
      // connection of the service part of the prolog
      require($_SERVER["DOCUMENT_ROOT"]."/api_help/main/reference/prolog_before.php");
      
      // connection of the visual part of the prolog
      require($_SERVER["DOCUMENT_ROOT"]."/api_help/main/reference/prolog_after.php");
      
      ?>
      Page contents
      <?
      
      // connection of the visual part of the epilog
      require($_SERVER["DOCUMENT_ROOT"]."/api_help/main/reference/epilog_before.php");
      
      // connection of the service part of the epilog
      require($_SERVER["DOCUMENT_ROOT"]."/api_help/main/reference/epilog_after.php");
      ?>

      In the visual part of the epilog the file /bitrix/templates/site template ID/footer.php is connected, where site template ID is the identifier of the current site template. This file stores bottom right part of the current site template. In addition to this, a number of invisible IFRAMEs used by the technology of redirection of visitors is displayed.

      The following occurs in the service part of the epilog:

      • Sending of mail messages;
      • Execution of event handlers;
      • Database disconnection;
      • A number of other service actions.

      Tasks often occur when there is no need to connect visual parts of the prolog and epilog. In this case, the connection of service parts of the prolog and epilog will suffice.

      <?
      // connection of the service part of the prolog
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
      ?>
      Page body
      <?
      // connection of the service part of the epilog
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_after.php");
      ?>

      For the correct operating of the system, the service parts of the prolog and epilog must be connected.


      Templates

      Page template is a PHP file wherein the contents are strictly consistent with the rules of forming the page structure. The templates may be used to create a new page.

      Page templates are stored in the following catalogs:

      • /bitrix/templates/.default/page_templates/;
      • /bitrix/templates/site template ID/page_templates/.

      Each such catalog may contain the proper page template files and also the service file .content.php of which the principal task is to store descriptions and the procedure for sorting page templates. This information is stored in the $TEMPLATE array of which its structure is presented below:

      Array
      (
          [fine name] => Array
              (
                  [name] => header of the page template
                  [sort] => sorting index
              )
      
      )

      The following algorithm is used during the formation of the list of page templates:

      • Connect site template ID for the current site that connects without PHP condition;
      • Connect the following file one by one:
        • /bitrix/templates/.default/page_templates/.content.php
        • /bitrix/templates/site template ID/page_templates/.content.php
        Each of these files will contain a description of its own $TEMPLATE array. After the connection of these files we will have a single $TEMPLATE array. Then, the following shall be performed for each element of this array which represents a description of one template of a page:
        • Verify physical existence of the page template
        • If it does exist, we add it to the list of templates
      • Sort out the resulting list of templates by sorting index (see $TEMPLATE array structure above).

      Properties

      The section properties are stored in the file .section.php of the relevant catalog (site section). The page properties are set, as a rule, either in the page body or between the service and visual parts of the prolog.

      The section properties are automatically inherited by all subsections and pages of this section. If necessary, you can edit the properties of any separate page of the section by correcting its parameters as needed.

      The following functions are used in work with the properties:

      Property Setting Methods

      CMain::SetPageProperty - sets the page property.

      <?
          $APPLICATION->SetPageProperty("keywords", "web, development, programming");
          ?>

      CMain::SetDirProperty - sets the section property.

      <?
          $APPLICATION->SetDirProperty("keywords", "design, web, site");
          ?>

      Show Property

      CMain::ShowProperty - displays a page or section property using technology of deferred functions.

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <title><?$APPLICATION->ShowProperty("page_title")?></title>
      </head>
      <body link="#525252" alink="#F1555A" vlink="#939393" text="#000000">
      ...
      

      Get Property Value

      CMain::GetProperty - returns a page or section property.

      <?
          $keywords = $APPLICATION->GetProperty("keywords");
          if (strlen($keywords)>0) echo $keywords;
          ?>

      CMain::GetPageProperty - returns a page property.

      <?
          $keywords = $APPLICATION->GetPageProperty("keywords");
          if (strlen($keywords)>0) echo $keywords;
          ?>

      CMain::GetPagePropertyList - returns an array of all the page properties.

      <?
          $arProp = $APPLICATION->GetPagePropertyList();
          foreach($arProp as $key=>$value)
          	echo '';
          ?>

      CMain::GetDirProperty - returns a section property.

      <?
          $keywords = $APPLICATION->GetDirProperty("keywords");
          if (strlen($keywords)>0) echo $keywords;
          ?>

      CMain::GetDirPropertyList - returns array of section properties collected recursively up to the site root.

      <?
          $arProp = $APPLICATION->GetDirPropertyList();
          foreach($arProp as $key=>$value)
          	echo '';
          ?>

      Working with Meta Tags

      Page and section properties are used to work with meta tags. The following functions are used to work with them:

      CMain::ShowMeta - displays a page property or a section property with an HTML tag frame using a deferred function feature.

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <?$APPLICATION->ShowMeta("keywords_prop", "keywords")?>
      <?$APPLICATION->ShowMeta("description_prop", "description")?>
      </head>
      <body link="#525252" alink="#F1555A" vlink="#939393" text="#000000">
      ...
      

      CMain::GetMeta - returns a page property or a section property with an HTML tag frame:

      <?
          $meta_keywords = $APPLICATION->GetMeta("keywords_prop", "keywords");
          if (strlen($meta_keywords)>0) echo $meta_keywords;
          ?>

      Parameters

      Page parameters are intended to translate parameters into module functions in order to change their standard behavior. E.g., if it is necessary to deactivate the memorization of the last page in a session (when using a page by page navigation) or to change a standard data display mode in the functions of the Information Blocks module.

      Page parameters are accessible only within a page. They cannot be saved either in the database or in a session.

      The class CPageOption is intended for working with page parameters.

      Module IDNameParameterDescriptionDefault value
      main Main modulenav_page_in_sessionIf the value is “Y,” the last open page in a page by page navigation will be memorized in the session; if the value is “N,” the last page will not be memorized.Y
      iblock Information blocksFORMAT_ACTIVE_DATESIf the value is FULL, the dates referring to an element of an information block (fields ACTIVE_FROM and ACTIVE_TO) will be returned in full format (time included); if the value is SHORT – in short format (without time).SHORT

      Examples of use:

      <?
      CPageOption::SetOptionString("main", "nav_page_in_session", "N");
      ?>
      <?
      CPageOption::SetOptionString("iblock", "FORMAT_ACTIVE_DATES", "FULL");
      ?>

      Execution Procedure

      General page execution procedure is as follows:

      • Service part of the prolog;
      • Visual part of the prolog;
      • Page body;
      • Visual part of the epilog;
      • Service part of the epilog.

      Component and template parameters can be accessed from the component and template program modules as $arParams array. The result of work of the component program module is $arResult array submitted to the component template entry. The regular echo operator streams the resulting HTML code (and it gets incorporated into a proper place within the page).

      During the work on a component and template it is possible to use the functionality of Bitrix Framework modules which, in their turn, may access the product database.

      Page Execution Procedure

      Page is a PHP file, consisting of a prolog, page body (main work area) and epilog:

      • header (/bitrix/header.php)
      • workarea
      • footer (/bitrix/footer.php)

      Page has a certain structure, properties and parameters. It can use its own templates.

      Sequence of page execution:

      No.OperationDefined constants and variables Note
      1.Prolog service part (/bitrix/modules/main/include/prolog_before.php)
      1.1 Connection
      /bitrix/php_interface/dbconn.php
        Connected file must contain definitions of variables for connecting to a database, constants for debugging and access permissions.
      File with core D7 settings is connected upon a first config query.
      1.2 Connecting with database $DB In case of connection errors, connects the file /bitrix/php_interface/dbconn_error.php instead.
      1.3 Connecting
      /bitrix/php_interface/after_connect.php
        Connected file can contain operations, necessary for executing immediately after establishing database connection.
      1.4 Defining a current site $APPLICATION, SITE_ID, SITE_DIR, SITE_SERVER_NAME, SITE_CHARSET, FORMAT_DATE, FORMAT_DATETIME, LANGUAGE_ID,
      Defines all classes and functions of the main module.
      If at this moment, you have a defined constant with SITE_ID, this site won't be defined as per current folder and domain name but the rest of constants will be defined for this site.
      1.5 Connecting
      /bitrix/php_interface/init.php
        Can contain initialization for event handlers. Connecting extra functions - shared for all sites.
      1.6 Connecting
      /bitrix/php_interface/site ID/init.php
        Contains parameters, function definitions for specific site.
      1.7 Opening session All session variables $_SESSION  
      1.8 Event OnPageStart
      1.9 Defining user, user authorization, session completion, registration (depending on the query parameters) $USER  
      1.10 Defining current site template SITE_TEMPLATE_ID  
      1.11 Event OnBeforeProlog    
      1.12 Verifying level 1 access permissions   In case permissions are insufficient, prints autherization form and page finishes execution.
      1.13 Starting result buffering   After initiating the buffering, you can display online, before setting cookies and otherwise set cookies before displaying online.
      1.14 Event OnProlog    
      2. Prolog visual section (/bitrix/modules/main/include/prolog_after.php)
      2.1 Connecting
      /bitrix/templates/Site template ID/header.php
         
      3. Page body
      4. Epilog visual section (/bitrix/modules/main/include/epilog_before.php)
      4.1 Connecting
      /bitrix/templates/Site template ID/footer.php
         
      4.2 Calling the function CMain::ShowSpreadCookieHTML   This function prints set of invisible IFRAMEs for deploying cookies between sites
      5. Epilog service section (/bitrix/modules/main/include/epilog_after.php)
      5.1 Event OnEpilog    
      5.2 Page buffering finalization   Completing and printing buffer initialized as per para. 1.14
      5.3 Event OnAfterEpilog    
      5.4 Checking agents    
      5.5 Sending emails   You can find more information about emails in "Email system"
      5.6 Finishing connection with database $DB variable is no longer accessible  

      Language and Language Files

      Language is an account in the database that is available for editing in the administrative menu on the page Control Panel > Settings > System settings > Language Parameters with the following main fields:
      • identifier;
      • name;
      • date format;
      • time format;
      • encoding.

      Both in the public and in the administrative parts the language is used first of all in order to select a specific language file.

      Language determines the time and date format and page encoding in the administrative part. In the public part, these parameters are determined by the site settings.

      Language Files

      Language file is a PHP script storing translations of language phrases to a specific language.

      This script contains $MESS arrays containing language phrase identifiers with values that are translations to a relevant language.

      An example of a language file for the German language:

      <?
      $MESS ['SUP_SAVE'] = "Speichern";
      $MESS ['SUP_APPLY'] = "Anwenden";
      $MESS ['SUP_RESET'] = "Zurücksetzen";
      $MESS ['SUP_EDIT'] = "Bearbeiten";
      $MESS ['SUP_DELETE'] = "Löschen";
      ?>

      An example of a language file for the English language:

      <?
      $MESS ['SUP_SAVE'] = "Save";
      $MESS ['SUP_APPLY'] = "Apply";
      $MESS ['SUP_RESET'] = "Reset";
      $MESS ['SUP_EDIT'] = "Change";
      $MESS ['SUP_DELETE'] = "Delete";
      ?>

      Each language has its own set of language files stored in subcatalogs /lang/ of the system or module file structure.

      As a rule, language files are used in the administrative scripts of modules or in components and are connected accordingly using one of the following functions:

      For the sake of search convenience and the further modification of language phrases, the page parameter show_lang_files=Y may be used, which allows to quickly find and correct any language phrase using the module of Localization.

      Examples

      All dictionary arrays can be viewed using a simple command:

      <? echo'
      ';print_r($MESS);echo'
      '; ?>

      In order to obtain the name of the month in 2 cases instead of its sequence number:

      <?
         echo $MESS['MONTH_'.date('n')]; // June
         echo $MESS['MONTH_'.date('n').'_S']; // July
      
      ?>

      The same procedure is applicable to the days of the week, names of the countries, etc.

      Processing Techniques

      Here, the general information on processing techniques and principles fed into the system is provided. The course describes those of them that are most frequently used. For information about other processing techniques and principles, please refer to the documentation.

      Bitrix Framework Processing Techniques:

      • Control Panel toolbar – process technique that makes site administration easier.
      • Agents – process technique that permits launch PHP functions with a preset frequency.
      • Caching – permits caching resource intensive code parts while at the same time increasing site performance.
      • Deferred functions – a processing technique that permits you to set certain page elements (header, additional points of a navigation chain, meta tags, buttons in control panel, and CSS styles) directly in a page body.
      • Event handling – processing technique that permits you to change the execution of an API function using events.
      • External authorization – processing technique that consists in using own checking algorithms and/or external databases for storing users.
      • Mail subsystem – permits to send emails using preset mail templates.
      • Transfer of visitors – a processing technique that permits to synchronize, where possible, a set of cookies for different sites that have different domain names and that belong to the same portal.

      Control Panel toolbar

      Control Panel toolbar – an HTML code that may be displayed to an authorized user if such user has sufficient rights to perform the operations listed in the control panel. HTML code represents an area with buttons at the very top of the page. Each of these buttons is intended for a specific operation.

      The panel is connected using the function of CMain::ShowPanel. This function applies a processing technique of deferred functions that permits you to add buttons to the panel directly in the page body.

      A button may be added to the panel using the function of CMain::AddPanelButton. The same function may be used in the script /bitrix/php_interface/include/add_top_panel.php that will be automatically connected upon the invocation of the panel.

      The control panel has two main modes:

      • Site - a mode for work on the site contents.
      • Control Panel - administrative section for a fully functional control of the entire Internet project.

      Agents

      Agents


      Agents – process technique that permits you to launch arbitrary PHP functions (agents) with a preset frequency. Technically, agent - is an entry in a special table with the following data:
      • which code is to be executed,
      • when to execute,
      • period of execution,
      • how to set time for the next agent launch (strictly periodic or non-periodic agent).

      The system automatically checks the agent's availability, requiring to be launched and executes it (if required) at the end of each page upload, after passing content to a browser.

      How agent's availability was checked before the Main module version 20.5.0 release.

      Note: Time accuracy of the agent launching directly depends on the steadiness and density of the site traffic. Actual agent launch time is usually a little later than specified time of the agent. Moment of launch occurs when somebody has visited a site page.
      If you need to arrange for launching of any PHP functions at a definitely preset time, you have to use the standard utility cron available from most hosting providers.

      In addition, resource intensive operations should not be assigned to agents, and there is a background launch option executed by cron.

      In order for an agent to be executed at a set time, it must be registered in the system using the method of CAgent::AddAgent. An agent’s registration may be deleted using the function of CAgent::RemoveAgent.

      If the agent function belongs to a module, this module will be automatically connected before the execution of such an agent function. Namely, the file /bitrix/modules/module ID/include.php will be connected. In this case, you have to make sure that the agent function will be available after the connection of this file.

      If an agent function doesn't belong to any module, it must be located in the file /bitrix/php_interface/init.php. This file is automatically connected in the prologue.

      The particularity of agent function creation is that the agent function must return a PHP code as function's return value that will be used during the next execution of this function.

      List of agents, employed in the system is available at the Agents page (Settings > System Settings > Agents).

      Periodic and non-periodic agents

      Historically, agents are called "periodic" and "non-periodic" which also can be named as "recurring" and "non-recurring".

      The user interface within the admin panel for the agent settings likewise has remained the same:

      Type of agent depends on the software developer who wrote the agent's code. This developer can create an agent that could repeat an infinite amount of times. Or only 2-3 times depending on set conditions. Example of agent function recurring infinitely:

      // example of agent function
      
      function TestAgentPeriod()
      {
         AddMessage2Log( "Periodic BX_CRONTAB:".BX_CRONTAB." BX_CRONTAB_SUPPORT:".BX_CRONTAB_SUPPORT );   
         return "TestAgentPeriod();";
      }
      
      
      function TestAgentNotPeriod()
      {
         AddMessage2Log( "Non-periodic BX_CRONTAB:".BX_CRONTAB." BX_CRONTAB_SUPPORT:".BX_CRONTAB_SUPPORT );
         return "TestAgentNotPeriod();";
      }

      Type of agent is defined by the method for calculating time of the next agent start:

      • For Periodic, assigned time for the next start is calculated as follows:
        Deprecated time set for agent + interval

        This method allows launching the agent for a precise number of times. For example, old email event cleaning agent launches once a day or an agent with daily site visit report.

      • Non-periodic, assigned time for the next start is calculated as follows:
        Time of termination of the last agent launch (executed agent's return) + interval

        For example, agent for ratings recalculating is launched once per hour. However, if there were no visitors during a day, it's more logical to execute agent once or move assigned time of the next launch for an hour later after this launch. In case of strictly periodical agent, this code would have been executed all 24 hours per day.

        Absolute majority of agents are non-periodic.

      Limits

      When using this technology, please take into account the following:

      • The variable USER is unavailable in agents. To be more previce, it can be created at the hosting's server, however there's no guarantee that this would be an object of CUser class.
        If required, it's recommended to create the object $USER within the agent, perform an action with it and the destroy it.
      • You cannot perform authorization in agents using the method Authorize.
      • The constant SITE_ID is unavailable, because the an agent can be executed also at the section's admin page.
      • You cannot determine beforehand which language is used at the multi-language sites.
      • Agents are executed in a single thread mode with blocking at MySQL for 10 minutes. New agent call is possible only after the previous call has been processed. Database blocking can be lost, if connection with it has been severed. Ten minutes standby period - is an additional protection from repeated launch for agents that didn't perform correctly.



      Examples of agents

        Example for creating an agent

      In case when you need to dynamically add specific agents, use the agent API. Be advised, that it's easier to add one or two agents manually.

      Go to the page Settings > Product Settings > Agents and click Add an Agent button on the context panel:

      Some clarifications regarding parameters can be found below:

      • Date of the last run - shows time of the most recent launch (when editing the agent);
      • Date and time of the next run – time for agent start (in case of non-periodic agent); agent is executed once at this time;
      • module - this module will be automatically connected, specifically connecting the file /bitrix/modules/module_ID/include.php. In this case, ensure that the agent function will be available after connecting this file;
      • Agent function - this is the main field, with the function named testAgent();
      • User ID – this is the filter for execution on the hit for specific user фильтр выполнения на хите для определенного пользователя;

      The function itself will look as follows:

      function testAgent()
      {
              mail('mail@gmail.com', 'Agent', 'Agent');
              return "testAgent();";
      }

      You can dd the function to the file /bitrix/php_interface/init.php.

      To activate the agent, execute the following code in the admin section PHP console:

      CAgent::AddAgent("testAgent();");

      In case of a new email, the agent is triggered and you can add you own function.

        Simple agent examples

      <?
      // add agent for the "Statistics" module
      CAgent::AddAgent(
          "CStatistic::CleanUpStatistics_2();", // function name
          "statistic",                          // module identifier
          "N",                                  // agent is non-critical for the number launches
          86400,                                // launch interval - 1 per day
          "07.04.2005 20:03:26",                // date of launch first check
          "Y",                                  // agent is active
          "07.04.2005 20:03:26",                // date of first launch
          30);
      ?>
      <?
      // add agent for the "Helpdesk" module
      CAgent::AddAgent(
          "CTicket::AutoClose();",  // function name
          "support",                // module identifier
          "N",                      // agent is non-critical for the number launches
          86400,                    // launch interval - 1 per day
          "",                       // date of launch first check - current
          "Y",                      // agent is active
          "",                       // date of first launch - current
          30);
      ?>
      <?
      // add a random agent not belonging to any module
      CAgent::AddAgent("My_Agent_Function();");
      ?>
      
      <?
      // file /bitrix/php_interface/init.php
      
      function My_Agent_Function()
      {
         // execute some actions
         return "My_Agent_Function();";
      }
      ?>
      <?
      // add a random agent, belonging to the module
      // with identifier my_module
      
      CAgent::AddAgent(
         "CMyModule::Agent007(1)", 
         "my_module", 
         "Y", 
          86400);
      ?>
      
      <?
      // this agent will be launched precisely 7 times with the period once per day, 
      // after it will be deleted from the agent table.
      
      Class CMyModule
      {
         public static function Agent007($cnt=1) : string
         {
            echo "Hello!";
            if($cnt>=7)
               return "";
            return "CMyModule::Agent007(".($cnt+1).");";
         }
      }>

        Currency rates

      Practical example: Update a currency rate at the site. It's recommended to assign an agent to cron.

      <?// Currency rate update
      
      function AgentGetCurrencyRate()
      {
         global $DB;
      
         // connect the 'currency' module
         if(!CModule::IncludeModule('currency'))
            return "AgentGetCurrencyRate();";
      
         $arCurList = array('USD', 'EUR');
         $bWarning = False;
         $rateDay = GetTime(time(), "SHORT", LANGUAGE_ID);
         $QUERY_STR = "date_req=".$DB->FormatDate($rateDay, CLang::GetDateFormat("SHORT", SITE_ID), "D.M.Y");
         $strQueryText = QueryGetData("www.cbr.ru", 80, "/scripts/XML_daily.asp", $QUERY_STR, $errno, $errstr);
      
         // this string is needed only for site having the encoding utf-8
         $strQueryText = iconv('windows-1251', 'utf-8', $strQueryText);
      
         if (strlen($strQueryText) <= 0)
            $bWarning = True;
      
         if (!$bWarning)
         {
            require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/classes/general/xml.php");
            $objXML = new CDataXML();
            $objXML->LoadString($strQueryText);
            $arData = $objXML->GetArray();
            $arFields = array();
            $arCurRate["CURRENCY_CBRF"] = array();
      
            if (is_array($arData) && count($arData["ValCurs"]["#"]["Valute"])>0)
            {
               for ($j1 = 0; $j1<count($arData["ValCurs"]["#"]["Valute"]); $j1++)
               {
                      $arFields = array(
                          "CURRENCY" => $arData["ValCurs"]["#"]["Valute"][$j1]["#"]["CharCode"][0]["#"],
                        'DATE_RATE' => $rateDay,
                        'RATE' => DoubleVal(str_replace(",", ".", $arData["ValCurs"]["#"]["Valute"][$j1]["#"]["Value"][0]["#"])),
                        'RATE_CNT' => IntVal($arData["ValCurs"]["#"]["Valute"][$j1]["#"]["Nominal"][0]["#"]),
                     );
                      CCurrencyRates::Add($arFields);
                }
      
          }
      }
      
      return "AgentGetCurrencyRate();";
      }?>

      Specified code is added to the file /bitrix/php_interface/init.php. Do not forget to add the agent AgentGetCurrencyRate(); at the page Settings > System Settings > Agents.

        Items without prices

      Agent that checks the availability of iblock items without completed prices.

        Let's develop a function that checks iblock items for completed prices. If such items are available, you need to send an email to the administrator regarding its amount and to add a corresponding entry into event log.
      1. Create:
        • Email event and template.
        • 'Agent', that will launch the function once per day.
      2. Make sure that agent is launched, log entry is created and email are received in the mailbox.
      3. Function must be located in a separate file that is connected in init.php.
      function AgentChekPrice()
      {
      	if(CModule::IncludeModule("iblock"))
      	{
      		$arSelect = Array("ID", "NAME", "PROPERTY_PRICE");
      		$arFilter = Array("IBLOCK_ID"=> 2, "PROPERTY_PRICE" => false);
      		$rsResCat = CIBlockElement::GetList(Array(), $arFilter, false, false, $arSelect);
      		$arItems = array();
      		while($arItemCat = $rsResCat->GetNext())
      		{
      			$arItems[] = $arItemCat;
      		}
      	
      		CEventLog::Add(array(
      				"SEVERITY" => "SECURITY",
      				"AUDIT_TYPE_ID" => "CHECK_PRICE",
      				"MODULE_ID" => "iblock",
      				"ITEM_ID" => "",
      				"DESCRIPTION" => "Price check, no prices for ".count($arItems)." items",
      		));
      	
      		if(count($arItems) > 0)
      		{
      			$arFilter = Array(
      					"GROUPS_ID" => Array(2)
      			);
      			$rsUsers = CUser::GetList(($by="personal_country"), ($order="desc"), $arFilter);
      			$arEmail = array();
      			while($arResUser = $rsUsers->GetNext())
      			{
      				$arEmail[] = $arResUser["EMAIL"];
      			}
      
      			if(count($arEmail) > 0)
      			{
      				$arEventFields = array(
      						"TEXT" => "Price check, no prices for ".count($arItems)." items",
      						"EMAIL" => implode(", ", $arEmail),
      				);
      				CEvent::Send("INFO_PRICE", "s1", $arEventFields);
      			}
      		}
      	}
      	
      	return "AgentChekPrice();";
      }

      Caching

      Caching is a technology which enables to cache the results of rarely updated and resource consuming code areas (for example, those handling the database).

      Performance

      In case of a large database, a performance problem may occur due to the following reasons:

      • Access to this information array for read or write results in competitive requests;
      • The requests are fast in themselves, but there are so many of them that the database starts building a queue of them;
      • The requests are slow and complicated, and they are also very frequent.

      Multilevel caching is used precisely to relieve the most loaded places in terms of resources and time. Each caching technique may be used for each component separately by choosing an optimal option for a specific case.

      Note: Until the developer decides on the caching strategy and on what they want to obtain from it, a blind activation of caching might bring no visible results.

      If we take an Internet store as an example, then for each item of goods a file in cache memory will be created so that the server will not have to send requests to the database in case of any future queries of the buyer.

      Caching

      Caching is a process technique that permits caching the outputs of rarely updated and resource-intensive parts of the code (for example, those actively working with the database).

      Two classes of caching are created to implement this:

      • CPageCache - is a class for caching HTML result of script execution;
      • CPHPCache - is a class for caching PHP variables and HTML result of script execution.

      The Bitrix Framework system includes different caching techniques:

      • Component caching (or Autocaching) – all dynamic components used to create web pages have an incorporated caching control support.

        In order to use this technique it is sufficient to turn on autocaching using a button on the administrative panel. In this case, all of the components with activated autocaching mode will create cache and switch to a work mode without database queries.

      • Uncontrolled caching is a possibility to set caching rules for resource-intensive parts of pages. Caching results are stored as files in the catalog /bitrix/cache/. If the caching time has not expired, instead of a resource-intensive code a preliminary created cache file will be connected.

        Caching is called uncontrolled because cache is not rebuilt automatically after the modification of source data, but operates during the specified time after creation, which is set in the Component parameters.

        The right use of caching permits to significantly increase the general performance of the site. However, it must be taken into account that an unreasonable use of caching may result in a serious increase of the /bitrix/cache/ catalog size.

      • Controlled cache automatically updates component cache in case of data change.
      • HTML cache should better be activated for a rarely changing section that is regularly visited by anonymous visitors. The technique is simple in operation, does not require that the user track changes, protects with a disc quota from data overload and autorecover operability in case the quota is exceeded or the data are changed.
      • Menu caching. A special algorithm is used for menu caching that takes into account the fact that the majority of visitors are unregistered visitors.

        Menu cache is controlled and updated during menu editing or a change of access rights to files and folders through an administrative interface and API.

      Main caching settings are located on the page Caching settings (Control Panel > Settings > System settings > Cache Settings).

      Note: In the D7 core, the caching settings are set in a special file.

      Blocking mode for caching system

      What is caching "blocking mode"

      Starting from main 24.0.0 caching has "blocking mode". This mode:

      • creates cache in a single flow only (for example, user hits cache)
      • remaining flows get deprecated cache value until a new value is generated
      • if there's no old value available, each flow generates data as usual

      In some cases such approach significantly reduces load - see the load test example below.

      Example of load test

      "Blocking mode" operation time

      Blocking mode is automatically triggered if at least a single condition is satisfied:

      • Cache expired: if cache has expired, but кеш устарел, но прошло не больше определенного времени с момента его истечения
      • Cache was deleted: if cache was deleted using a key, but less than 60 seconds have expired (time limit is configurable)

        // Deleting cache via key with legacy value saved
        $cache = Bitrix\Main\Data\Cache::createInstance(['actual_data' => false]);
        $cache->clean($key, $dir);
        
        // Creating cache with old keys supported
        $cache = Bitrix\Main\Data\Cache::createInstance(['actual_data' => false]);
        

      For all cache queries to be in blocking mode, specify use_lock => true:

      // Memcache
          'cache' => [
              'value' => [
                  'type' => [
                      'class_name' => '\\Bitrix\\Main\\Data\\CacheEngineMemcache',
                      'extension' => 'memcache'
                  ],
      
                  'memcache' => ['host' => '127.0.0.1', 'port' => '11211',],
                  'use_lock' => true,
      
                  // 'servers' => [
                  //    0 => ['host' => '127.0.0.1', 'port' => 11211, 'weight' => 1],
                  //    1 => ['host' => '10.100.0.59', 'port' => 11211, 'weight' => 1],
                  //    2 => ['host' => 'unix:///var/run/memcached/memcached.sock', 'port' => '0'],
                  //],
      
                  'sid' => 'bxMemcache'
              ]
          ],
      

      Caching mechanism operation

      Standard cache mechanism executes in the following cases:

      • cache created or removed without specified parameter ['actual_data' => false]
      • cache has expired for a longer period than the specified time ttl * $ttlMultiplier
      • cache cleared by tag or by folder
      • cache is cleared by key, but more than 60 seconds have expired since it was deleted

      Note: by default, components support "blocking mode".

      Related documentation

      • Class CPHPCache in Developer documentation
      • Class CPageCache in Developer documentation
      • Class Cache in D7 documentation

      Component caching (Managed cache mode)

      Component caching is one of cache types available in Bitrix Framework.

      Components must use caching in order to process the client’s request faster and to reduce the server load. As a rule, the information that is not dependent on a specific visitor must be cached. For example, the list of website news is the same for all visitors. That is why it makes no sense to select the data from the database each time.

      All dynamic components that are used in order to create web pages have an embedded support of cache control. In order to use the technique, it is sufficient to activate auto cache by clicking a button on the administrative panel. This feature comes in handy at the developing stage when auto cache can be deactivated to make the work easier, and activated again before delivery of the project. In this case, all of the components with auto cache mode activated in their settings will create caches and will entirely switch to a work mode that does not involve database queries.

      Attention! When using the Managed cache mode (Components cache), the information retrieved by the components is updated according to the parameters of separate components.

      Managed cache control is located on the Component caching tab ( Control Panel > Settings > System settings > Cache Settings ):

      Note: When component auto cache mode is activated, the components with the caching setting Auto + Managed will be switched to a work mode with caching.

      In order to update the contents of cached objects on a page, you can::

      1. Go to the required page and update its contents using the button Refresh cache on the control panel.
      2. In the Edit mode use the flush cache buttons on panels of specific components.
      3. Use automatic cache reset upon the expiry of caching time; for this, select the caching mode Cache or Auto + Managed in the component settings.
      4. Use automatic cache reset when data are changed; for this, select the caching mode Auto + Managed in the component settings.
      5. Go to the settings of the selected components and switch them to the work mode without caching.

      Note: For additional information about component caching, please refer to the lesson Caching in Own Components.


      Events

      Sometimes it is necessary to adjust the execution process of an API function. However, if the function is changed, these changes will be lost after the next update. For these cases, an event system has been developed. During the execution of certain API functions specific function invocations called event handlers are set at specific points.

      Note: Event handlers should be handled very carefully. Since the event model in Bitrix Framework is rich enough, hard-to-find errors may appear in the handler’s code without due care. They may seriously unnerve the developer.

      Invocation of a handler registering function determines which handler functions must be invoked in a place (in case of which event). At present, there are two such handler registering functions – AddEventHandler and RegisterModuleDependences. The set of events for each module is described in the documentation for each module. Here is, for example, the link to the main module events.

      RegisterModuleDependences - is a function for the registration of handlers located in modules and used for interaction among system modules. This function must be invoked once during module installation; after that the event handler function will be automatically invoked at a specific time, having connected the module itself first.

      It is deleted using UnRegisterModuleDependences during the elimination of a module.

      Example

      // compression module handler functions are connected twice – at the head and in the foot of each page
      RegisterModuleDependences("main", "OnPageStart", "compression", "CCompress", "OnPageStart", 1);
      RegisterModuleDependences("main", "OnAfterEpilog", "compression", "CCompress", "OnAfterEpilog");
      
      // empty handler registers advertising module installer
      // should an OnBeforeProlog event occur, the advertising module will be simply connected on each page
      // which will make it possible to execute its API functions without preliminary connection in a page body
      RegisterModuleDependences("main", "OnBeforeProlog", "advertising");

      Each module may provide other modules with an interface for implicit interaction – an event set. Such interaction permits making modules independent from one another to the fullest extent. The module knows nothing about the functioning particulars of the other module, but may interact with it through the event interface.

      AddEventHandler - function is intended for the registration of arbitrary handlers that are not located in modules. This function must be invoked before an event occurs on the pages where such an event must be handled. E.g., if an event must be handled on all pages where it occurs, the function may be invoked in /bitrix/php_interface/init.php.

      Example

      // handler registration in /bitrix/php_interface/init.php
      AddEventHandler("main", "OnBeforeUserLogin", "MyOnBeforeUserLoginHandler");
      function MyOnBeforeUserLoginHandler($arFields)
      {
         if(strtolower($arFields["LOGIN"])=="guest")
         {
             global $APPLICATION;
             $APPLICATION->throwException("The user with the login name Guest cannot be authorized.");
             return false;
         }
      }

      Differences in the Use of Functions

      The actions to be performed using events must be physically written somewhere, and they must be set to respond to a specific event.

      RegisterModuleDependences performs registration in the database, and AddEventHandler in the file init.php. I.e. the use of the first function results in an additional load on the database. It should be used in situations when the actions performed must be fixed once and for all precisely in the database.



      As a rule, events are divided by place of occurrence and purpose into the following group:

      • Those intended for the cancellation of the further execution of a method. E.g., the event OnBeforeUserDelete permits to cancel the elimination of a user subject to specific conditions (availability of critically linked objects), the event OnBeforeUserLogin permits to prohibit a user’s authorization.
      • Those permitting execution in specific methods upon the completion of their execution. E.g., OnAfterUserLogin – after verification of login and password, the event OnUserDelete – permits to eliminate linked objects immediately before the elimination of a user from the database.
      • Those occurring during the execution of a page in order to enable their code in specific places on a page. For example, OnBeforeProlog (for more details, see procedure for page execution).

      The list and description of events accessible to modules are located in the Documentation for the developer.

      Deferred functions

      Deferred functions are a process technique that permits to set the page header, navigation chain points, CSS styles, additional buttons in the control panel, meta tags, etc. with the help of the functions used directly in the page body. Relevant function outputs are displayed in the prologue, i.e. up the code they were set.

      The processing technique was created first of all in order to be used in components that are usually displayed in the page body, but at the same time a page header, a navigation chain point, and a control panel button, etc. may be added inside these components. Deferred functions cannot be used in the files of the component template template.php and result_modifier.php (because the results of their execution are cached).

      Components may be connected inside a deferred function but in this case CSS and js files must be connected manually.

      Note: There is a number of new functions that may work under the conditions of caching (SetViewTarget and EndViewTarget). But such functions are new and are not described in the documentation; hence they are rather considered an exception.

      Operation Algorithm of This Processing Technique:

      1. Any outgoing flow from PHP script is buffered.
      2. As soon as one of the following methods is found in the code:
        • CMain::ShowTitle(),
        • CMain::ShowCSS(),
        • CMain::ShowNavChain(),
        • CMain::ShowProperty(),
        • CMain::ShowMeta(),
        • CMain::ShowPanel()

        or other function ensuring the deferral of the execution of any function:

        1. All previously buffered content is memorized in the next element of the A stack memory;
        2. An empty element is added to the A stack that in the future will be filled with a deferred function result;
        3. The name of the deferred function is memorized in the B stack;
        4. The buffer is cleaned and bufferization is enabled again.

        Thus, the A stack contains all the page content divided into parts. In the same stack there are empty elements intended for their further fill up with deferred function results.

        There is also a B stack where the names and parameters of deferred functions are memorized in accordance with their sequential order in the code.
      3. At the foot of the page in the service part of the epilogue the following actions are performed:
        1. All deferred functions from the B stack start being executed one by one;
        2. Deferred function results are entered into a specially designated places in the A stack;
        3. All content from the A stack is “stuck together” (concatenated) and displayed on the screen.

      Thus, the processing technique permits fragmenting all the page content by dividing it into parts using special functions that ensure the temporary deferral of execution of other functions (deferred functions). At the foot of the page, all the deferred functions are executed one by one, and their results are introduced into specially designated places inside the fragmented content of the page. After that, all the content is stuck together and sent to the site visitor’s browser.

      Attention! When using this processing technique it must be taken into account that no actions can be performed with the results of the functions that ensure the deferral of other functions.

      An example of the code in which the deferred function will not execute code in the template as expected:

      if (!$APPLICATION->GetTitle())
        echo "Standard page";
      else
        echo $APPLICATION->GetTitle();

      But this code will work:

      $APPLICATION->AddBufferContent('ShowCondTitle');
      
      function ShowCondTitle()
      {
        global $APPLICATION;
       if (!$APPLICATION->GetTitle())
          return "Standard page";
       else
          return $APPLICATION->GetTitle();
      }

      Another example

      $page_title = $APPLICATION->GetPageProperty(title); // this function does not return anything
      if (strlen($page_title)<=0) $page_title = "Default page header";
      echo $page_title;

      this code will not work because all the deferred functions are executed at the very foot of the page, in the service part of the epilogue.

      Example:

      <?
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("Old header");
      ?>
      <?
      global $APPLICATION;
      $strTitle = $APPLICATION->GetTitle();
      echo $strTitle." - Page header

      "; $APPLICATION->SetTitle('New header'); ?> <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>

      The page will display "Old header" and the browser – "New header".

      Groups of functions involved in this processing technique:


      Name of the deferring function Deferred functionAdditional related functions
      CMain::ShowTitle CMain::GetTitle CMain::SetTitle
      CMain::ShowCSS CMain::GetCSS CMain::SetTemplateCSS
      CMain::SetAdditionalCSS
      CMain::ShowNavChain CMain::GetNavChain CMain::AddChainItem
      CMain::ShowProperty CMain::GetProperty CMain::SetPageProperty
      CMain::SetDirProperty
      CMain::ShowMeta CMain::GetMeta CMain::SetPageProperty
      CMain::SetDirProperty
      CMain::ShowPanel CMain::GetPanel CMain::AddPanelButton

      The processing technique permits creating deferred functions using the method CMain::AddBufferContent.

      Performance

      Site performance is complex phenomenon. Page opening speed is affected by the following:

      1. server resources;
      2. server software settings;
      3. software platform;
      4. platform application development.

      Performance failure can occur at any one or several stages. Application development plays a significant, albeit not the singular role in this list. At the end of the day, any advantages granted by the most sophisticated state-of-the-art platform can be messed up a lacking development effort.


      Sessions and cookies

      You can find a very detailed description for sessions handling in the Official PHP documentation.

      Attention! Methods for sessions handling described in this chapter are applicable from the main module version 20.5.0.

      Variable $_SESSION

      You can handle the variable $_SESSION directly, however such approach is not advised. All data changes in the global variable will be saved, but we strongly recommend to use the new Bitrix24 API instead of this variable.

      Instead of directly using the variable, it's better to use the object, returned by the method \Bitrix\Main\Application::getSession():

      $session = \Bitrix\Main\Application::getInstance()->getSession();
      if (!$session->has('foo'))
      {
          $session->set('foo', 'bar');            
      }
      
      echo $session['foo']; //bar

      This object is implemented by the interface \ArrayAccess, as well as by \Bitrix\Main\Session\SessionInterface.

      Session cache (Local Session)

      Sometimes, there is an objective to cache data, associated with the current user. However, one of available options is to use the session. Extra caution is required, however, since such approach is not always suitable:

      1. session wasn't specifically created for caching,
      2. large data volume affects the sessions speed,
      3. instances of blocked hits occur.

      One of alternative approaches is to create a cache, associated with session_id(). In essence, it's a simple session simulation. Starting from main version 20.5.400 there is a new option available.

      Example:

      $localStorage = \Bitrix\Main\Application::getInstance()->getLocalSession('someCategory');
      if (!isset($localStorage['productIds']))
      {
          $localStorage->set('productIds', [1, 2, 100]);
          $localStorage->set('price', 42);
      }
      
      var_dump($localStorage->get('productIds'));
      

      Basic principle

      Operational principle is fairly simple: calling \Bitrix\Main\Application::getLocalSession($name) always returns an instance of \Bitrix\Main\Data\LocalStorage\SessionLocalStorage. This is a cache element that automatically employs session_id().

      At this moment, if this is a first query and no data is available, the system creates an empty container. If cache contained data by $name, the container will be filled with data.

      All SessionLocalStorage are automatically saved at the end of hit by the kernel.

      Attention! SessionLocalStorage operates using cache, described in the .settings.php.

      Note: If this is a file-related cache, the SessionLocalStorage will use $_SESSION for storage Otherwise an issue of deleting and verifying legacy files occurs, affecting the file system operation.

      Related documentation:

      Hot&cold sessions

      Introduction

      By default, a session in PHP supports a sequential access. it means that the session's parallel hits are blocked and enqueued.

      This sequence is convenient for developers, but not always suitable for users, due to possible delays interface and application response. This results in necessity to reduce number of session interruptions.

      General principle

      One of solutions on how to avoid blocking is to write SessionHandlerInterface. This solution, however, is not suitable, due to being excessively used in the Bitrix Framework and Partner-related code.

      There is another, better path available:

      1. Data, used on each hit are implemented in KernelSession-session ("hot" data). This data is for authentication, authentication and other kernel-related data.
      2. All the rest that is stored in session - is the "cold" data.
      3. KernelSession-session is non-blocking.
      4. All the rest - a standard blocking session.
      5. Blocking session starts only upon first query to cold data.

      Division of cold&hot data storage

      Encrypted cookies serves as storage for KernelSession session.

      Storage for cold session: standard session with the same operational principle as before. That's why the data will be stored in Redis, Memcache, Database.

      Storage settings

      To enable a divided session mode, you need to change session[mode] to separated in bitrix/.settings.php. Add 'kernel' => 'encrypted_cookies' and 'lifetime' => 14400,.

      Examples can be found here.

      Data storage setting for session

      Kernel supports four variants (files, redis, database, memcache) for session data storage. Storage method is described in bitrix/.settings.php inside the 'session' section:

      Files

      // bitrix/.settings.php
      return [
      //...        
          'session' => [
              'value' => [
                  'mode' => 'default',
                  'handlers' => [
                      'general' => [
                          'type' => 'file',       
                      ]           
                  ],
      
              ]                   
          ] 
      ];

      Setting for divided session:

      // bitrix/.settings.php
      return [
      //...        
          'session' => [
              'value' => [
                  'lifetime' => 14400, // +
                  'mode' => 'separated',  // +
                  'handlers' => [
                      'kernel' => 'encrypted_cookies',  // +           
                      'general' => [
                          'type' => 'file',       
                      ],
                  ],
      
              ]                   
          ] 
      ];

      Redis

      // bitrix/.settings.php
      return [
      //...        
         'session' => [
             'value' => [
                 'mode' => 'default',
                 'handlers' => [
                     'general' => [
                         'type' => 'redis',  
                         'servers' => [
                             [
                                 'port' => 6379,
                                 'host' => '127.0.0.1',
                             ],
                             [
                                 'port' => 6379,
                                 'host' => '127.0.0.2',
                             ],
                             [
                                 'port' => 6379,
                                 'host' => '127.0.0.3',
                             ],
                         ],
                         'serializer' => \Redis::SERIALIZER_IGBINARY,
                         'persistent' => false,
                         'failover' => \RedisCluster::FAILOVER_DISTRIBUTE,
                         'timeout' => null,
                         'read_timeout' => null,    
                     ],          
                 ],
             ]                  
         ]
      ];

      Cluster storage of session data

      Difference from standard configuration: servers have additional options: serializer, persistent, failover, timeout, read_timeout. You can find detailed information about then in the official documentation.

      // bitrix/.settings.php
      return [
      //...        
          'session' => [
              'value' => [
                  'mode' => 'default',
                  'handlers' => [
                      'general' => [
                          'type' => 'redis',   
                          'servers' => [
                              [
                                  'port' => 6379,
                                  'host' => '127.0.0.1',
                              ],
                              [
                                  'port' => 6379,
                                  'host' => '127.0.0.2',
                              ],
                              [
                                  'port' => 6379,
                                  'host' => '127.0.0.3',
                              ],
                              'serializer' => \Redis::SERIALIZER_IGBINARY,
                              'persistent' => false,
                              'failover' => \RedisCluster::FAILOVER_DISTRIBUTE,
                              'timeout' => null,
                              'read_timeout' => null,
                          ],
                      ],           
                  ],
              ]                   
          ] 
      ];

      Setting for separate session:

      // bitrix/.settings.php
      return [
      //...        
          'session' => [
              'value' => [
                  'lifetime' => 14400, // +
                  'mode' => 'separated', // +
                  'handlers' => [
                      'kernel' => 'encrypted_cookies',  // +  
                      'general' => [
                          'type' => 'redis',   
          		    'port' => '6379',
          		    'host' => '127.0.0.1',
                      ],           
                  ],
      
              ]                   
          ] 
      ];

      Memcache

      // bitrix/.settings.php
      return [
      //...        
          'session' => [
              'value' => [
                  'mode' => 'default',
                  'handlers' => [
                      'general' => [
                          'type' => 'memcache',   
          		    'port' => '11211',
          		    'host' => '127.0.0.1',
                      ],           
                  ],
      
              ]                   
          ] 
      ];

      Cluster storage for session data

      When you need to create a cluster from server memcache, you just need to add the servers settings.

      // bitrix/.settings.php
      return [
      //...        
          'session' => [
              'value' => [
                  'mode' => 'default',
                  'handlers' => [
                      'general' => [
                          'type' => 'memcache',   
                          'servers' => [
                              [
                                  'port' => 11211,
                                  'host' => '127.0.0.1',
                                  'weight' => 1, //read more about the weight settings in the memcahe-related documentation
                              ],
                              [
                                  'port' => 11211,
                                  'host' => '127.0.0.2',
                              ],
                          ],
                      ],           
                  ],
              
              ]                   
          ] 
      ];

      Setting for separated session:

      // bitrix/.settings.php
      return [
      //...        
          'session' => [
              'value' => [
                  'lifetime' => 14400, // +
                  'mode' => 'separated', // +
                  'handlers' => [
                      'kernel' => 'encrypted_cookies',  // +  
                      'general' => [
                          'type' => 'memcache',   
          		    'port' => '11211',
          		    'host' => '127.0.0.1',
                      ],           
                  ],
      
              ]                   
          ] 
      ];

      Mysql

      Data is stored in the table b_user_session

      // bitrix/.settings.php
      return [
      //...        
          'session' => [
              'value' => [
                  'mode' => 'default',
                  'handlers' => [
                      'general' => [
                          'type' => 'database',       
                      ]           
                  ],         
              ]                   
          ] 
      ];

      Setting for separate session:

      // bitrix/.settings.php
      return [
      //...        
          'session' => [
              'value' => [
                  'lifetime' => 14400, // +
                  'mode' => 'separated', // +
                  'handlers' => [
                      'kernel' => 'encrypted_cookies',  // +  
                      'general' => [
                          'type' => 'database',       
                      ]           
                  ],         
              ]                   
          ] 
      ];

      Handling the sessions

      Bitrix24 allows handling sessions as follows:

      Session storage in memcached

      You need to set the following constants to enable session storage in memcached in the old core/kernel's file /bitrix/php_interface/dbconn.php:

      define('BX_SECURITY_SESSION_MEMCACHE_HOST', 'localhost');
      define('BX_SECURITY_SESSION_MEMCACHE_PORT', 11211);

      Or when using unix-socket:

      define('BX_SECURITY_SESSION_MEMCACHE_HOST', 'unix:///path/to/memcached.sock');
      define('BX_SECURITY_SESSION_MEMCACHE_PORT', 0);

      Then, enable session storage in the database in the proactive protection module. As a result, we get kernel-enabled session storage in memcached.

      This method of session storage provides the following advantages:

      • no need to track the number of old sessions on the highly-loaded project;
      • option to individuate sessions between servers in cluster;
      • option to use session that's not waiting for blocking;
      • option to use virtual sessions.

      Session storage in the database has the same advantages as the session storage in memcached. However, database storage is significantly slower. That's why we recommend using memcached for this purpose instead of database.

      Non-blocking sessions

      One of issues encountered in large-scale projects with multiple ajax queries is the frequently blocked hits of a single user for standby of blocked session. This is especially applicable for Bitrix24 On premise, where in many locations, files attached to entities are passed to user, after checking access permissions for PHP. That's why, a sort of "sequence" can be built due to standby for blocked session. You can enable a non-blocking session by specifying the constant,before connecting the kernel:

      define('BX_SECURITY_SESSION_READONLY', true);

      After this action, the session is read from memcached or database without waiting for blocking. For example, this feature is used when passing files.

      Important: the session won't be recorded when using this constant upon a completed hit. This can cause possible data loss saved within a hit in session.

      Virtual sessions

      Non-blocking sessions are sufficient for majority of cases. However, in some cases, when we do not need the session altogether, its overly excessive to use a non-blocking session - due to an extra session being created if unavailable. As the result, a large of "trash" sessions are created in case of large amount of unassociated hits. Example of such hits are REST hits.

      Virtual session was added to resolve this issue. It works as follows: a session is created in the memory, doesn't wait for blockings and is not saved. To enable it, set the constant, before connecting the kernel .

      define('BX_SECURITY_SESSION_VIRTUAL', true);

      Please remember that this session type is not saved anywhere. It is used when processing REST queries.


      Note: If you have a Bitrix24 On premise, a project with a large number of ajax queries or a high number of files - is passed with access permission check (for example, in blogs, social network, forum). It's better to use session storage in kernel-enabled memcached.


      Encrypted cookies

        Encrypted cookies

      Encrypted [ds]cookies[/ds][di] Cookie - is a text string of information, sent to a browser by a site visitor and which is saved in file on the site visitor device. Usually, a cookie is used to determine a user unique status, last visit time, personal settings, shopping cart unique ID and etc.
      [/di] (\Bitrix\Main\Web\CryptoCookie) allows sending data to user, without disclosing contents and without allowing to modify data inside. Available from main 20.5.400.

      Configuration

      For the kernel to be capable to encrypt the data, indicate crypto_key in the settings /bitrix/.settings.php . By default, it's generated automatically in new distribution packages.

      If it's unavailable, add it manually to kernel settings file:

      <?php
      return [
          //...
          'crypto' => [
              'value' => [
                  'crypto_key' => 'mysupersecretphrase',
                  //we recommend to set 32-character string from a-z0-9,
              ],
              'readonly' => true,
          ]
      
          //...
      ];
      

        Examples

      Setting a Cookie

      To set an encrypted cookie, just create an object as in the snippet below, into a desired Response:

      $cookie = new \Bitrix\Main\Web\CryptoCookie('someName', 'secret value');
      \Bitrix\Main\Context::getCurrent()->getResponse()->addCookie($cookie);

      Because the cookie is limited in length and data is encrypted and packaged in base64, to avoid data loss, the kernel can create several cookies with encrypted value.

      As the result, http response will contain cookie someName with the value -crpt-someName_0. And cookie someName_0 with already encrypted value such as DRMg6jrwXO1aUxTvdyBYyT-3_bCqomI9MMN_enurA5abplMm2OiSlNdu_1zgjbkKT_3D3uT8366.

      Reading a Cookie

      To get access to encrypted cookies value, it's sufficient to use standard kernel API for cookie handling

      $httpRequest = \Bitrix\Main\Context::getCurrent()->getRequest();
      
      echo $httpRequest->getCookie('someName');
      //secret value

      Kernel automatically determines that cookie is encrypted or not encrypted, unpackages the value and decrypts it. In case the value cannot be decrypted, gets an empty value.


      External Authorization

      Sometimes it may be necessary to use special checking algorithms and/or external user storage databases for the user’s authorization (verification of login and password). E.g., there is a user database and they must have the possibility to undergo authorization on a CMS managed site. In these cases, all users sometimes may be moved to the CMS database using API functions, but often it is impossible because of the following two reasons:

      • First. Users’ passwords in the external database are not stored in the open form, only their hash is stored; that is why after migration the users will not be able to undergo authorization due to password inconsistency.
      • Second. Sometimes a common user database is necessary, with users’ passwords stored on the same remote server and used for verification in several places, including CMS.

      In order to resolve such tasks, Bitrix Framework provides for an option to add own external authorization to the standard incorporated authorization system. It takes several steps that we will cover in detail taking an external authorization as an example and using users’ database of a popular forum PHP BB.

      For a start, let us create a file, for example, /bitrix/php_interface/scripts/phpbb.php. A class with an external handler will be located in this class, let us call it __PHPBB2Auth:

      class __PHPBB2Auth
      {
      }

      In order to have our function invoked during an authorization attempt it is necessary to set the OnUserLoginExternal event handler that will be invoked automatically each time a user enters their login and password, before the built-in check. For this, let us use the AddEventHandler function in the file /bitrix/php_interface/init.php:

      AddEventHandler(
           "main", 
           "OnUserLoginExternal", 
           Array("__PHPBB2Auth", "OnUserLoginExternal"), 
           100, 
           $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
      );

      We have specified our class method __PHPBB2Auth::OnUserLoginExternal as a handler. The OnUserLoginExternal event handler accepts as a parameter a link to an array with fields for the check:

      define("PHPBB2_TABLE_PREFIX", "phpbb_");
      function OnUserLoginExternal(&$arArgs)
      {
          $table_user = PHPBB2_TABLE_PREFIX."users";
          $table_user_group = PHPBB2_TABLE_PREFIX."user_group";
          extract($arArgs);
      
          global $DB, $USER, $APPLICATION;
      
          $strSql = "SELECT * FROM ".
                    $table_user.
                    " WHERE username='".
                    $DB->ForSQL($login).
                    "' AND user_password='".
                    $DB->ForSql(md5($password))."'";
          $dbRes = $DB->Query($strSql);
          if($arRes = $dbRes->Fetch())
          {
              if($arRes['user_active']!='0')
              {
                  // user’s name and password are correct
              }
          }
      }

      Having verified the login and password according to the PHPBB algorithm, an external user must be created in the internal database so that internal objects (news, votes, etc.) could be linked to it. To do so, let us use the method CUser::GetList() with a filter by login and external source code. If there is no such user we will create it, and if such a user exists we will update information about it.

      $arFields = Array(
          "LOGIN" => $login,
          "NAME" => $login,
          "PASSWORD" => $password,
          "EMAIL" => $arRes['user_email'],
          "ACTIVE" => "Y",
          "EXTERNAL_AUTH_ID"=>"PHPBB2",
          "LID" => SITE_ID
          );
      $oUser = new CUser;
      $res = CUser::GetList($O, $B, Array("LOGIN_EQUAL_EXACT"=>$login, "EXTERNAL_AUTH_ID"=>"PHPBB2"));
      if(!($ar_res = $res->Fetch()))
          $ID = $oUser->Add($arFields);
      else
      {
          $ID = $ar_res["ID"];
          $oUser->Update($ID, $arFields);
      }
      if($ID>0)
      {
         // authorization is permitted
         return $ID;
      }

      Now we have a user ID in our database and it can be recovered from the handler function to make this user authorized by the system, but a new user will be anonymous, because it is not bound to any group. Let us use the binding in the PHPBB database to transfer it to our database before authorization.

      $USER->SetParam("PHPBB2_USER_ID", $arRes['user_id']);
      $groups_map = Array(
          /*'PhpBB2 Group ID' => 'Local Group ID',*/
           '2' => '1'
          );
      
      $user_groups = Array();
      $dbUserGroup = $DB->Query('SELECT * FROM '.$table_user_group.' WHERE user_id='.$arRes['user_id']);
      while($arUserGroup = $dbUserGroup->Fetch())
          $user_groups[] = $arUserGroup['group_id'];
      
      if(count($user_groups)>0)
      {
          $arUserGroups = CUser::GetUserGroup($ID);
          foreach($groups_map as $ext_group_id => $group_id)
          {
              if(in_array($ext_group_id, $user_groups))
                  $arUserGroups[] = $group_id;
              else
              {
                  $arUserGroupsTmp = Array();
                  foreach($arUserGroups as $grid)
                      if($grid != $group_id)
                          $arUserGroupsTmp[] = $grid;
                  $arUserGroups = $arUserGroupsTmp;
              }
          }
          CUser::SetUserGroup($ID, $arUserGroups);
      }

      That is all. Now the local user account is consistent with the remote one, a user’s code may be recovered, and it will be authorized. Let us tentatively switch off the function "remember me on this computer” if the user has enabled the checkbox, because in this case we will not be able to correctly check the access rights:

      $arArgs["store_password"] = "N";
      return $ID;

      In order to register a new external system authorization check, the event OnExternalAuthList must be handled. Let us add a relevant invocation in the file /bitrix/php_interface/init.php:

      AddEventHandler(
           "main", 
           "OnExternalAuthList", 
           Array("__PHPBB2Auth", "OnExternalAuthList"), 
           100, 
           $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
           );

      Handler function must return the array from a set of handlers with the fields ID and NAME.

      function OnExternalAuthList()
      {
          return Array(
              Array("ID"=>"PHPBB2", "NAME"=>"PhpBB2")
              );
      }

      Now on the user editing page a drop-down list indicating external authorization sources appears. We provide the entire text of the file /bitrix/php_interface/scripts/phpbb.php below. A reverse mechanism is additionally implemented in it: once authorized in our system, a user is automatically authorized in the forum.

      <?
      define("PHPBB2_TABLE_PREFIX", "phpbb_");
      
      class __PHPBB2Auth
      {
          function OnUserLoginExternal(&$arArgs)
          {
              ////////// <settings> ////////////
              $table_user = PHPBB2_TABLE_PREFIX."users";
              $table_user_group = PHPBB2_TABLE_PREFIX."user_group";
              $groups_map = Array(
                  /*'PhpBB2 Group ID' => 'Local Group ID',*/
                  '2' => '1'
                  );
              ////////// </settings> ////////////
              extract($arArgs);
      
              global $DB, $USER, $APPLICATION;
      
              $strSql = "SELECT * FROM ".
                        $table_user.
                        " WHERE username='".
                        $DB->ForSQL($login).
                        "' AND user_password='".
                        $DB->ForSql(md5($password))."'";
              $dbRes = $DB->Query($strSql);
              if($arRes = $dbRes->Fetch())
              {
                  if($arRes['user_active']!='0')
                  {
                      $arFields = Array(
                          "LOGIN" => $login,
                          "NAME" => $login,
                          "PASSWORD" => $password,
                          "EMAIL" => $arRes['user_email'],
                          "ACTIVE" => "Y",
                          "EXTERNAL_AUTH_ID"=>"PHPBB2",
                          "LID" => SITE_ID
                          );
                      $oUser = new CUser;
                      $res = CUser::GetList($O, $B, 
                                            Array("LOGIN_EQUAL_EXACT"=>$login, 
                                                  "EXTERNAL_AUTH_ID"=>"PHPBB2"));
                      if(!($ar_res = $res->Fetch()))
                          $ID = $oUser->Add($arFields);
                      else
                      {
                          $ID = $ar_res["ID"];
                          $oUser->Update($ID, $arFields);
                      }
      
                      if($ID>0)
                      {
                          $USER->SetParam("PHPBB2_USER_ID", $arRes['user_id']);
      
                          $user_groups = Array();
                          $dbUserGroup = $DB->Query('SELECT * FROM '.
                                                    $table_user_group.
                                                    ' WHERE user_id='.$arRes['user_id']);
                          while($arUserGroup = $dbUserGroup->Fetch())
                              $user_groups[] = $arUserGroup['group_id'];
      
                          if(count($user_groups)>0)
                          {
                              $arUserGroups = CUser::GetUserGroup($ID);
                              foreach($groups_map as $ext_group_id => $group_id)
                              {
                                  if(in_array($ext_group_id, $user_groups))
                                      $arUserGroups[] = $group_id;
                                  else
                                  {
                                      $arUserGroupsTmp = Array();
                                      foreach($arUserGroups as $grid)
                                          if($grid != $group_id)
                                              $arUserGroupsTmp[] = $grid;
                                      $arUserGroups = $arUserGroupsTmp;
                                  }
                              }
                              CUser::SetUserGroup($ID, $arUserGroups);
                          }
                          $arArgs["store_password"] = "N";
      
                          return $ID;
                      }
                  }
              }
          }
      
          function OnExternalAuthList()
          {
              return Array(
                  Array("ID"=>"PHPBB2", "NAME"=>"PhpBB2 Board")
                  );
          }
      
          function OnAuthorize(&$arArgs)
          {
              extract($arArgs);
      
              global $DB, $APPLICATION, $USER;
              $user_id = $USER->GetParam("PHPBB2_USER_ID");
              if($user_id<=0)
                  return;
              $table_user = PHPBB2_TABLE_PREFIX."users";
              $table_sessions = PHPBB2_TABLE_PREFIX."sessions";
              $table_config = PHPBB2_TABLE_PREFIX."config";
      
              $dbConfig = $DB->Query("SELECT * FROM ".
                                     $table_config.
                                     " WHERE config_name
                                       IN ('cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure')");
              while($arConfig = $dbConfig->Fetch())
                  ${$arConfig['config_name']} = $arConfig['config_value'];
      
              if (isset($HTTP_COOKIE_VARS[$cookie_name . '_sid']) || 
                  isset($HTTP_COOKIE_VARS[$cookie_name . '_data']))
                  $session_id = isset($HTTP_COOKIE_VARS[$cookie_name . '_sid']) ? 
                                     $HTTP_COOKIE_VARS[$cookie_name . '_sid'] : '';
      
              $ip_sep = explode('.', $_SERVER['REMOTE_ADDR']);
              $user_ip = sprintf('%02x%02x%02x%02x', $ip_sep[0], $ip_sep[1], $ip_sep[2], $ip_sep[3]);
              $current_time = time();
              $sql =
                  "UPDATE ".$table_sessions." SET ".
                  "    session_user_id = ".$user_id.", ".
                  "    session_start = ".$current_time.", ".
                  "    session_time = ".$current_time.", ".
                  "    session_page = 0, ".
                  "    session_logged_in = 1 ".
                  "WHERE session_id = '".$DB->ForSQL($session_id)."' ".
                  "    AND session_ip = '".$user_ip."'";
      
              $r = $DB->Query($sql);
              if($r->AffectedRowsCount()<=0)
              {
                  $session_id = md5(uniqid($user_ip));
                  $sql =
                      "INSERT INTO ".
                      $table_sessions.
                      "(session_id, session_user_id, session_start, session_time, session_ip, session_page, session_logged_in)".
                      "VALUES ('".$session_id."', ".$user_id.", ".$current_time.", ".$current_time.", '".$user_ip."', 0, 1)";
                  $DB->Query($sql);
              }
      
              $sql =
                  "UPDATE ".$table_user." SET ".
                  "    user_session_time = ".$current_time.", ".
                  "    user_session_page = 0, ".
                  "    user_lastvisit = ".$current_time." ".
                  "WHERE user_id = ".$user_id;
      
              $DB->Query($sql);
      
              $sessiondata = Array('userid' => $user_id);
      
              setcookie($cookie_name.'_data', 
                        serialize($sessiondata), 
                        $current_time + 31536000, 
                        $cookie_path, 
                        $cookie_domain, $cookie_secure);
              setcookie($cookie_name.'_sid',
                        $session_id, 0, $cookie_path, 
                        $cookie_domain, $cookie_secure);
          }
      }
      ?>

      The following lines must be added to /bitrix/php_interface/init.php:

      <?
      AddEventHandler(
          "main", 
          "OnUserLoginExternal", 
          Array("__PHPBB2Auth", "OnUserLoginExternal"),
          100, 
          $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
          );
      
      AddEventHandler(
          "main", 
          "OnExternalAuthList", 
          Array("__PHPBB2Auth", "OnExternalAuthList"),
          100,
          $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
          );
      
      AddEventHandler(
          "main", 
          "OnAfterUserAuthorize", 
          Array("__PHPBB2Auth", "OnAuthorize"),
          100
          );
      ?>

      As an example, here is another script for external authorization – for the Invision Power Board forum:

      <?
      define("IPB_TABLE_PREFIX", "ibf_");
      define("IPB_VERSION", "2");
      
      AddEventHandler("main", "OnUserLoginExternal", Array("__IPBAuth", "OnUserLoginExternal"));
      AddEventHandler("main", "OnExternalAuthList", Array("__IPBAuth", "OnExternalAuthList"));
      
      class __IPBAuth
      {
          function OnUserLoginExternal(&$arArgs)
          {
              extract($arArgs);
      
              ////////// <settings> ////////////
              $table_user = IPB_TABLE_PREFIX."members";
              $table_converge = IPB_TABLE_PREFIX."members_converge";
              $groups_map = Array(
                  /*'IPB Group ID' => 'Local Group ID',*/
                  '4' => '1'
                  );
              ////////// </settings> ////////////
      
              global $DB, $USER, $APPLICATION;
      
              if(IPB_VERSION == '1')
              {
                  $strSql = "SELECT * FROM ".$table_user." WHERE name='".$DB->ForSql($login)."' AND password='".md5($password)."'";
              }
              else
              {
                  $strSql =
                      "SELECT t1.* ".
                      "FROM ".$table_user." t1, ".$table_converge." t2 ".
                      "WHERE t1.name='".$DB->ForSql($login)."' ".
                      "    AND t1.email = t2.converge_email ".
                      "    AND t2.converge_pass_hash = MD5(CONCAT(MD5(t2.converge_pass_salt), '".md5($password)."'))";
              }
      
              $dbAuthRes = $DB->Query($strSql);
              if($arAuthRes = $dbAuthRes->Fetch())
              {
                  $arFields = Array(
                      "LOGIN" => $login,
                      "NAME" => $arAuthRes['title'],
                      "PASSWORD" => $password,
                      "EMAIL" => $arAuthRes['email'],
                      "ACTIVE" => "Y",
                      "EXTERNAL_AUTH_ID"=>"IPB",
                      "LID" => SITE_ID
                      );
      
                  $oUser = new CUser;
                  $res = CUser::GetList($O, $B, Array("LOGIN_EQUAL_EXACT"=>$login, "EXTERNAL_AUTH_ID"=>"IPB"));
                  if(!($ar_res = $res->Fetch()))
                      $ID = $oUser->Add($arFields);
                  else
                  {
                      $ID = $ar_res["ID"];
                      $oUser->Update($ID, $arFields);
                  }
      
                  if($ID>0)
                  {
                      $USER->SetParam("IPB_USER_ID", $arAuthRes['id']);
      
                      $user_group = $arAuthRes['mgroup'];
                      $arUserGroups = CUser::GetUserGroup($ID);
                      foreach($groups_map as $ext_group_id => $group_id)
                      {
                          if($ext_group_id==$user_group)
                              $arUserGroups[] = $group_id;
                          else
                          {
                              $arUserGroupsTmp = Array();
                              foreach($arUserGroups as $grid)
                                  if($grid != $group_id)
                                      $arUserGroupsTmp[] = $grid;
                              $arUserGroups = $arUserGroupsTmp;
                          }
                      }
                      CUser::SetUserGroup($ID, $arUserGroups);
                      $arArgs["store_password"] = "N";
      
                      return $ID;
                  }
              }
          }
      
          function OnExternalAuthList()
          {
              return Array(
                  Array("ID"=>"IPB", "NAME"=>"Invision Power Board")
                  );
          }
      }
      ?>

      This script must be connected in /bitrix/php_interface/init.php to become operable.

      Mail subsystem

      Mail subsystem

      Mail subsystem is the technology used to handle e-mail messages within a site.

      The mail system operation includes the following stages:

      1. Creating a mail event type. A mail event type can be created using the function CEventType::Add only.
      2. Creating a mail template. Can be accomplished in the administrative menu Mail templates, as well as using the CEventMessage::Add function.
      3. Creating a mail event. A mail event can be created using the function CEvent::Send only.

        1. When a page finishes execution, the function CEvent::CheckEvents is called automatically. This function is used to accomplish the following main tasks:

          1. select unprocessed mail events from the table b_event;
          2. use the above selected events to generate e-mail messages;
          3. send these messages;
          4. write one of the following values as the send result to the table b_event in the field SUCCESS_EXEC:

            1. Y - all messages with all mail templates have been successfully sent;

              F - all messages with all mail templates could not be sent;

              P - some messages have been sent successfully, while some messages have failed to send;

              0 - mail templates could not found;

              N - the mail event has not yet been processed by the function.CEvent::CheckEvents


              Developers can affect sending messages by modifying the following settings of the Main (Kernel) module:

              • Convert Unix new line symbols to Windows format during email sending
              • Convert 8-bit characters in the message header
              • One or comma-separated list of E-mail addresses to receive copies of all the outcoming messages
              • E-mail address in caption
              • Send email event and template identifiers in message

              Besides the above parameters, the constant ONLY_EMAIL can be used. If initialized, it allows sending all messages only to the specified address or group of addresses.


              Example of creating the mail event type:

              Add(array(
                  "EVENT_NAME"    => "ADV_BANNER_STATUS_CHANGE",
                  "NAME"          => "Banner status changed",
                  "SITE_ID"       => "en",
                  "DESCRIPTION"   => "
                      #ID# - banner ID
                      #CONTRACT_ID# - contract ID
                      #TYPE_SID# - type ID
                      "
                  ));
              ?>
              

              Example of creating the mail template:

              Add($arr);
              ?>
              

              Example of creating the mail event:

               124,
                  "CONTRACT_ID" => 1,
                  "TYPE_SID"    => "LEFT"
                  );
              CEvent::Send("ADV_BANNER_STATUS_CHANGE", 
                           array("de", "en"), $arFields);
              ?>
              

              Related links:


              Balancing queries in cluser

              Starting from the main module version 24.0.0 the query balancing system in cluster has been [dw]improved significantly[/dw][di] The previous balancing system system version had any query update interrupting the conveyor process and all queries were submitted to the main (master) database. [/di], optimizing load between servers.

                Main updates

              1. The system functions in a standard mode before the first query for data update.
              2. In case of an update query, list of modified tables is registered.
              3. Upon subsequent retrieving queries, the updated tables are checked. If no tables were modified, queries will continue to be processed via slave servers.

              Tests demonstrate significant increase of queries, processed by slave servers and minimized errors when handling cluster configuration. This allows for better distribution of queries between servers and reduced load on master server.

                Load balancing

              1. Legacy (standard) variant: Master 30 la, Slave 3 la.

              2. New variant: Master 1 la, Slave 50 la.

              Now cluster settings allow to redirect 100% of load to slave servers, which significantly increases system performance and effectiveness.


              Core D7

              Development Purpose is the creation of a new software core at a new technology level while eliminating “layers” of outdated technology.

              The compatibility principle to which Bitrix, Inc. must adhere to made it necessary to perform a large scope of work not directly intended to develop Bitrix Framework. It immediately affected the speed and quality of development of the platform itself and implicitly affected the distribution of the company’s products in the market.

              Actually, the new core is a new development method. At the same time, all old API continues working in the product. And a new API is added for development in a new style. Gradually the old API must become something like an adaptor that ensures compatibility. And all logic with relevant refactoring must move to a new core.

              Technical Requirements

              The minimal technical requirements for the product version 18.5.400 (with core D7):

              • PHP version 7.1.
              • MySQL version 5.6.

              Main Differences from the Old Core

              • Database
                • The following databases are supported: MySQL and NoSQL.
                • Abandoning the inefficient driver MSSQL ODBC, only native driver is supported.
                • ORM (query builder) with noSQL is used.
              • Object-Oriented Programming (OOP)
                • Strong engagement. All code belonging to a certain area must be concentrated in a single place, in the same class, and in the same class set.
                • Components with OOP (class.php) have the possibility to write a more structured component code and a possibility of inheritance.
              • Development
                • Consistent code. All equal queries have the same name, the same sets of parameters, and return unified data. I.e. GetList of users does not differ from GetList of groups of users.
                • Support of name spaces.
                • New uniform rules of code formatting with strict control at the level of development.
                • Abandoning of global variables.
                • Support of exceptions.
                • Support of new types: date, time, and files. Classes with methods replace unformatted data. The values of these types are objects with formatting methods, etc.
                • Class library.
                • Unified events. A possibility of modification and integration using handlers.
                • Autoload. All system entities are located in previously determined places; accordingly, an autoload is supported without additional actions on the part of the developer.
                • Specialized handlers (classes, entities) for different situations – types of applications (http, cli).
                • Deferred load of language files. Files from /lang files are not connected simultaneously with a component connection; they are loaded after the first query of a language phrase.
                • Object providers for main operations (cache, log).o Object providers for main operations (cache, log).

              Core Parameter Setup


              Special file

              Bitrix Framework has several specific core settings that do not have an edit interface. Such design choice is due to occurrences when settings update or error in them can easily lead to rendering the whole system inoperable (due to database connection setting, caching settings and etc.).

              Settings in a Core D7 are entered in the file /bitrix/.settings.php. Please be advised, that in the old core/kernel similar settings were made in the file \bitrix\php_interface\dbconn.php. The file .settings.php differs significantly from the former dbconn.php in terms of structure.

              Note: due to the fact that the system uses 2 cores in parallel - old kernel and the new D7, both settings files are used simultaneously. That's is why you must configure both files.

              Even if you are using the old kernel's code, the file .settings.php is still must be created. A situation is possible when upon installing the updates, some of the in-built system mechanisms will be re-written to the new core. If this file is not configured correctly, it could lead to system inoperability.

              In some cases, the .settings.php file is missing. It can be created automatically, via executing it in [dw]command line[/dw][di]PHP Command line - system tool, allowing to launch arbitrary code on PHP with function calls. [/di]: Bitrix\Main\Config\Configuration::wnc();.

              Parameters can be edited via the Configuration class (Bitrix\Main\Config\Configuration).

              Note: Some sections of the setting file contain the readonly parameter. This parameter means that these settings will not be amended through API.

              In addition, these settings can be specified in the file .settings_extra.php. Basic settings file contains invariable settings, with available API. File .settings_extra.php can contain arbitrary code that changes settings dynamically. Accordingly, it doesn't have API functions.


              Parameter description

              Below are parameters that can be modified:

              Parameters that may be changed are described below:

            2. cache section
            3. exception_handling section
            4. connections section
            5. root section
            6. pull section
            7. http_client_options section
            8. services section


            9. Cache Section

              Responsible for caching settings and permits to set the caching method and its parameters.

              Before version 18.5.200, the old record format was as follows:

              In the version 18.5.200 the record format was updated simultaneously with the option to use Redis in caching. Presently, both formats are operational, however vendor strongly recommends to use new record format.

              Examples of new record format for various caching methods.

              Parameter Value
              type The following can be set as values:
              • memcache
              • eaccelerator
              • apc
              • xcache
              • files
              • [dw]redis[/dw][di]From version 18.5.200.[/di]
              • none
              or indicate arrays with values:
              • class_name - a class implementing ICacheEngine interface,
              • required_file - an add-on file with a path starting from bitrix or local (if required),
              • required_remote_file - an add-on file with an absolute path (if required).
              • extension - attempts to connect extension via extension_loaded. Only when successful, then connects already specified class.
              cache_flags Restriction on caching or updating the ttl. Set keys that include table name and suffixes:
              'cache_flags'=>   array(
                    'value'=> array(
                       "b_group_max_ttl" => 200,
                       "b_group_min_ttl" => 100,
                    )
                 ),

              By setting the b_group_max_ttl = 0, administrator restricts caching for this entity. By setting the b_group_min_ttl = 86400, admin expands our TTL for up to 24 hours (if the code contains 3600).

              Note: In addition to type there may be additional parameters if a specific caching class requires them.

              Note: The memcache settings may also be set in the file /bitrix/.settings_extra.php.

              Sample file /bitrix/.settings_extra.php

              Base file .settings.php contains invariable settings that have API functions. The file .settings_extra.php may contain arbitrary code that updates settings depending on various factors. Accordingly, this file doesn't contain API for setting updates. Naturally, array with the same base file structure will be returned upon executing this arbitrary code.


              Section exception_handling

              Responsible for exception handling.

                'exception_handling' => array (
                  'value' => array (
                    'debug' => false,
                    'handled_errors_types' => E_ALL & ~E_NOTICE & ~E_STRICT & ~E_USER_NOTICE,
                    'exception_errors_types' => E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT & ~E_USER_WARNING & ~E_USER_NOTICE & ~E_COMPILE_WARNING & ~E_DEPRECATED,
                    'ignore_silence' => false,
                    'assertion_throws_exception' => true,
                    'assertion_error_type' => 256,
                    'log' => array (
                      'settings' => array (
                        'file' => 'bitrix/modules/error.log',
                        'log_size' => 1000000,
                      ),
                    ),
                  ),
                  'readonly' => false,
                ),
              Parameter Value
              debug The key is responsible for displaying an error on a browser page. Errors should be displayed only at the development or debugging stage. Otherwise, there is a potential risk of information disclosure.
              handled_errors_types This key is intended for setting error types to be caught by the system (not ignored).
              exception_errors_types The key sets error types for which the system gives an exception.
              ignore_silence The key cancels action of the error management operator (@).
              log The key sets error logging parameters. If there is no key, there will be no logging. If set as in the example:
              'log' => array (
                 'settings' => array (
                    'file' => 'bitrix/modules/error.log',
                    'log_size' => 1000000,
                 ),
              ),

              the logging will be made into a file with limited size. If site root contains the file error.php and error printing on screen is enabled, this file will be connected in case of unprocessed exception.

              If set in a general case, logging can be made to anywhere:

              'log' => array(
                 'class_name' => 'MyLog', // custom log class, must extends ExceptionHandlerLog; 
                                          // can be omited, in this case default Diag\FileExceptionHandlerLog will be used
                 'extension' => 'MyLogExt', // php extension, is used only with 'class_name'
                 'required_file' => 'modules/mylog.module/mylog.php' // included file, is used only with 'class_name'
                 'settings' => array( // any settings for 'class_name'
                    ),
              ),

              The provided example:

              • class_name - custom class, descendant from \ExceptionHandlerLog. May not be specified. In this case, uses \Bitrix\Main\Diag\FileExceptionHandlerLog.
              • extension - PHP extension, use only jointly with class_name.
              • required_file - enabled file. Used only jointly with class_name.
              • settings - settings for class, specified in class_name
              assertion_throws_exception Enabling support for command assert.
              assertion_error_type The key specifies only error types for which the incorrect assert throws exception.

              Error type must be passed in handled_errors_types, exception_errors_types, assertion_error_type. Error type is a numerical code. For example, parameter exception_errors_types. And what does such record means: E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT & ~E_USER_WARNING & ~E_USER_NOTICE & ~E_COMPILE_WARNING?

              First, let's refer to levels of PHP error interpreter. There are specific values and determined constants matching them. In our case, this record means that E_ALL (constant value 2047), is bit-wise and not E_NOTICE, and not E_WARNING and not E_STRICT and not E_USER_WARNING and not E_USER_NOTICE and not E_COMPILE_WARNING. That is E_ALL with exception of previosuly specified constants that define one or the other PHP interpreter error level.


              Connections section

              Database and other data source connection parameters. The class name and connection parameters are specified.

              'connections' => array (
                      'value' => array (
                          'default' => array (
                              'className' => '\\Bitrix\\Main\\DB\\MysqlConnection',
                              'host' => 'localhost:31006',
                              'database' => 'admin_bus',
                              'login' => 'admin_bus',
                              'password' => 'admin_bus',
                              'options' => 2,
                              'handlersocket' => array (
                                  'read' => 'handlersocket',
                              ),
                          ),
                          'handlersocket' => array (
                              'className' => '\\Bitrix\\Main\\Data\\HsphpReadConnection',
                              'host' => 'localhost',
                              'port' => '9998',
                          ),
                      ),
                      'readonly' => true,
                  ),

              Attention: The mysqli extension can be used starting from core version 14.5.2 and higher
              'className' => '\\Bitrix\\Main\\DB\\MysqliConnection'. 
              
              Also, for this purpose, mysqli extension can be installed via PHP. Additional checks for extension availability are not performed! Mysqli can be enabled separately for the old (dbconn.php) and the new (.settings.php) core D7.


              Parameter Value
              options Checkboxes of persistent connection and deferred connection with the database. Example:
              Connection::PERSISTENT == 1
              Connection::DEFERRED == 2
              Their combinations can be recorded via bit operations. Example 3 - both PERSISTENT and DEFERRED.
              handlersocket The key specifies which connection must be used for read (read key). Connection must be created, where class, host and port are specified. The example of code above, 'read' parameter is specified with the handlersocket value. Below is the description for the handlersocket connection.
              className Class name, that receives the result of handling the specific database type.
              host host name, where the database is located; can be specified with the port
              database database name
              login database user login
              password database user password

              Note:
              Several databases can be handled within the ORM.
              To use the handlersocket connection, the HSPHP - PHP HandlerSocket client library must be installed



              Root section

              General settings are located in the root section.

              Parameter Value
              disable_iconv Restricts the use of iconv library. Equivalent of the BX_ICONV_DISABLE constant in the old core
              logger Loggers, implementing PSR-3 interface. Details.


              Pull section

              This section is specifically required only for hosting partners (for deployment automation). For the rest, its recommended to use the setup via administrative control panel interface.

              'pull' => Array(
                     'value' =>  array(
                         'path_to_listener' => "http://#DOMAIN#/bitrix/sub/";,
                         'path_to_listener_secure' => "https://#DOMAIN#/bitrix/sub/";,
                         'path_to_modern_listener' => "http://#DOMAIN#/bitrix/sub/";,
                         'path_to_modern_listener_secure' => "https://#DOMAIN#/bitrix/sub/";,
                         'path_to_mobile_listener' => "http://#DOMAIN#:8893/bitrix/sub/";,
                         'path_to_mobile_listener_secure' => "https://#DOMAIN#:8894/bitrix/sub/";,
                         'path_to_websocket' => "ws://#DOMAIN#/bitrix/subws/",
                         'path_to_websocket_secure' => "wss://#DOMAIN#/bitrix/subws/",
                         'path_to_publish' => 'http://127.0.0.1:8895/bitrix/pub/',
                         'nginx_version' => '3',
                         'nginx_command_per_hit' => '100',
                         'nginx' => 'Y',
                         'nginx_headers' => 'N',
                         'push' => 'Y',
                         'websocket' => 'Y',
                         'signature_key' => '1111111111',
                         'signature_algo' => 'sha1',
                         'guest' => 'N',
                     ),
                 ),

              Example of section code and parameters table
              Parameter Value
              path_to_listener path to connect to server (http) to receive commands
              path_to_listener_secure path to connect to server (https) to receive commands
              path_to_modern_listener path to connect to server for modern web browsers (http) to receive command
              path_to_modern_listener_secure path to connect to server for modern browsers (https) to receive commands
              path_to_websocket path to connect to server for modern browsers with socket support (http) to receive commands
              path_to_websocket_secure path to connect to server for modern browsers with web-socket support (https) to receive commands
              path_to_publish path to publish commands from the server
              path_to_publish_web path to publish commands from the web browser (http)
              path_to_publish_web_secure path to publish commands from the web browser (https)
              nginx_version queue server version
              nginx_command_per_hit number of channels to his which the same data will be sent per hit
              nginx enables the queue server
              nginx_headers allows to send special headers
              push enables the queue server
              websocket activates the web-sockets
              signature_key secret signature key for messages
              signature_algo signature encryption algorithm
              guest enables module active status for guests
              enable_protobuf enables new format for messages
              limit_max_payload maximum size of a message in bytes
              limit_max_messages_per_request maximum number of messages per hit
              limit_max_channels_per_request maximum number of channels per hit


              Http_client_options section

              This section specifies options by default for the Bitrix\Main\Web\HttpClient.

              [ICO_NEW data-adding-timestamp="1706700990"]

              Starting from main 23.0.0 HttpClient now supports PSR-18. In addition to PSR, client works in legacy mode, supports queues with asynchronous queries and CURL.

              [/ICO_NEW]

              Parameter Value
              redirect true by default, execute redirect
              redirectMax maximum number of such redirects (by default: 5)
              waitResponse if true - wait for response (by default), otherwise - return the response immediately
              socketTimeout response timeout period in seconds (by default: 30)
              streamTimeout stream timeout in seconds (by default: 60)
              version version http - 1.0 or 1.1 (by default: 1.0)
              proxyHost / proxyPort / proxyUser / proxyPassword parameter group to setup a proxy
              compress if true, sends Accept-Encoding: gzip
              charset encoding for the object body (is used in the header field for the Content-Type request for POST and PUT)
              disableSslVerification if true, ssl certificates verification will not be performed

              Config example:

                'http_client_options' =>
                 array (
                   'value' =>
                      array (
                       'redirect' => true,//execute redirects, if required
                       'redirectMax' => 10,//but not more than 10
                       'version' => '1.1'//work via the http 1.1 protocol 
                      ),
                   'readonly' => false,
                 ),

              You can check if your settings are correct as follows:

              use Bitrix\Main\Config\Configuration;
              print_r(Configuration::getValue("http_client_options"));

              Your array should be displayed.

              services section

              This section is intended for registering services. Find more details in the lesson [ds]Service Locator[/ds][di]Service locator is a design template for convenient handling of application services. Instead of creating specific services directly (via new) service locator is responsible for creating and finding services.

              Learn more ...[/di]


              Connection to Redis, Memcache

              Add a titled connection into connections section to create a connection inside bitrix/.settings.php settings file.

              Redis

              Ensure that you have installed Redis extension for working via PHP.

              Standard connection:

               'connections' => [
                  'value' => [
                    'default' => [
                      'className' => \Bitrix\Main\DB\MysqliConnection::class,
                      //... config for existing database connection
                    ],
                    'custom.redis' => [
                      'className' => \Bitrix\Main\Data\RedisConnection::class,
                      'port' => 6379,
                      'host' => '127.0.0.1',
                      'serializer' => \Redis::SERIALIZER_IGBINARY,
                    ],
                    'custom2.redis' => [
                      'className' => \Bitrix\Main\Data\RedisConnection::class,
                      'port' => 6379,
                      'host' => '127.0.0.4',
                      'serializer' => \Redis::SERIALIZER_IGBINARY,
                    ],
                  ],
                  'readonly' => true,
                ]

              You can find additional information about serializer config variants in the official documentation.

              Cluster

              When creating cluster from redis servers, just add servers config.

              'connections' => [
                  'value' => [
                    'default' => [
                      'className' => \Bitrix\Main\DB\MysqliConnection::class,
                      //... config for existing database connection
                    ],
                    'custom.redis' => [
                      'className' => \Bitrix\Main\Data\RedisConnection::class,
                      'servers' => [
                          [
                              'port' => 6379,
                              'host' => '127.0.0.1',
                          ],
                          [
                              'port' => 6379,
                              'host' => '127.0.0.2',
                          ],
                          [
                              'port' => 6379,
                              'host' => '127.0.0.3',
                          ],
                      ],
                      'serializer' => \Redis::SERIALIZER_IGBINARY,
                      'persistent' => false,
                      'failover' => \RedisCluster::FAILOVER_DISTRIBUTE,
                      'timeout' => null,
                      'read_timeout' => null,
                    ],
                  ],
                  'readonly' => true,
                ]

              Additional information about configurations for variants serializer, persistent, failover, timeout, read_timeout can be found in the official documentation.

              Use

              To get a connection instance, you may query the connection name using the method \Bitrix\Main\Application::getConnection.

              /** @var \Redis $redisConnection **/
              $redisConnection = \Bitrix\Main\Application::getConnection('custom.redis')->getResource();
              $redisConnection->setnx('foo', 'bar');

              Memcache

              Ensure that you have installed Memcache extension for working via PHP.

              Standard connection

              'connections' => [
                  'value' => [
                    'default' => [
                      'className' => \Bitrix\Main\DB\MysqliConnection::class,
                      //... config for existing database connection
                    ],
                    'custom.memcache' => [
                      'className' => \Bitrix\Main\Data\MemcacheConnection::class,
                      'port' => 11211,
                      'host' => '127.0.0.1',
                    ],
                    'custom42.memcache' => [
                      'className' => \Bitrix\Main\Data\MemcacheConnection::class,
                      'port' => 6379,
                      'host' => '127.0.0.4',
                    ],
                  ],
                  'readonly' => true,
                ]
              

              Cluster

              When creating cluster from memcache servers, just add servers config.

              'connections' => [
                  'value' => [
                    'default' => [
                      'className' => \Bitrix\Main\DB\MysqliConnection::class,
                      //... config for existing database connection
                    ],
                    'custom.memcache' => [
                      'className' => \Bitrix\Main\Data\MemcacheConnection::class,
                      'servers' => [
                          [
                              'port' => 11211,
                              'host' => '127.0.0.1',
                              'weight' => 1, //read more about weight config in the memcahe documenation
                          ],
                          [
                              'port' => 11211,
                              'host' => '127.0.0.2',
                              'weight' => 1, //read more about weight config in the memcahe documenation
                          ],
                      ],
                    ],
                  ],
                  'readonly' => true,
                ]

              Use

              To get a connection instance, query connection name using the method \Bitrix\Main\Application::getConnection.

              /** @var \Memcache $memcacheConnection **/
              $memcacheConnection = \Bitrix\Main\Application::getConnection('custom.memcache')->getResource();
              $memcacheConnection->set('foo', 'bar');

              SMTP server local settings

              Bitrix24 On-premise editions Main module version 21.900.0 has a new SMTP option allowing to organize dedicated email streams.

                 General information

              How emails are sent fr om site/instance

              For Bitrix24 On-premise, user calls a global function bxmail, triggering standard PHP mail function, wrapping an email with headers. After that, mail function queries your internal infrastructure (depending on your [ds]configuration[/ds][di] Bitrix Framework requires setup for sending and receiving emails. There are three options for sending emails:

              - via local sendmail or postfix (when site uses Linux);

              - via third-party SMTP server without authentication (for Windows);

              - via third-party server with authentication by replacing email sender function.[/di]: postfix, sendmail or any custom solution, operating based on queues, sending all emails via mail function).

              All emails (email campaigns and single emails, CRM emails, password restoration emails, workflows, etc.) are included into a single stream.

              Using new local SMTP server settings you can configure dedicated streams as per custom requirements.

              Advantages of sender's SMTP server

              • dedicated streams;
              • use of [dw]aliases[/dw][di] Aliases are additional mailbox names. They are used to assign additional names to a single mailbox located at your domain. For example, you have a mailbox mysite@example.com. You can add an alias marketing<mysite@example.com> for marketing department. After this action, users receiving an email from marketing department will see only marketing as a sender.

                When users respond to this email, their reply is sent to an actual mailbox mysite@example.com. [/di] (alias);
              • simple setup;
              • debugging option;
              • option to send on hits;
              • option to maintain open connection (useful for bulk mail).

                Enabling SMTP server

              To enable sender's SMTP server in Bitrix24 On-premise editions, you need to edit the file [ds]/bitrix/.settings.php[/ds][di] Bitrix Framework has a variety of kernel/core settings that do not have visual UI for editing. This is due to updated settings or an error can easily cause the complete system to malfunction (database connection settings, cache settings and etc.).

              Learn more...[/di], by adding the corresponding code:

              return [
              	//…
              	'smtp' => [
              		'value' => [
              			'enabled' => true,
              			'debug' => true, //optional
              			'log_file' => '/var/mailer.log' //optional
              		]	 
              	]
              ];
              

              Important! Extra caution is advised when editing the .settings.php file: a single error can easily cause the complete system to malfunction.

              Settings parameters:

              • enabled – enabling the SMTP server for the sender;
              • debug – optional parameter (disabled by default). Enable it if you want to see the complete interaction process with SMTP servers;
              • log_file – optional parameter. Can indicate a file for collection of logs, by entering full path to file (this file requires access). By default, project/site directory already contains the file mailer.log with all logs incoming logs.

                SMTP connection setup

              SMTP connection can be configured by two methods:

              • When creating new email message, [dw]add a sender[/dw][di] [/di], click on the link [dw]SMTP server[/dw][di] [/di] and complete the displayed config fields for connecting an SMTP server:

                • enter name and email;
                • indicate SMTP server sender's visibility: for you only or visible for all users;

                  Note: Each employee can connect a dedicated SMTP server.

                • indicate email server;
                • enter a port (mostly three values are used: 25, 465, 587);
                • indicate [dw]maximum daily message limits[/dw][di] Various email services (Google, Yahoo, etc.) have different limits on the number of sent emails (most frequently its the lim it on the number of emails sent per 24 hours). With this option enabled, Bitrix24 will maintain statistics on the number of dispatched emails and issue errors, when limits are reached. [/di];
                • enter login and password for connected SMTP server.
              • Similar form for connecting SMTP server can be completed at the site's Control panel at Settings > System settings > Email and SMS events > SMTP settings, by clicking on the button Add SMTP connection:

                Note: Currently, daily message limits config is not available for SMTP connections added inside the Control panel.

              Are the settings described above connected with SMTP settings at the Bitrix24 Virtual Appliance?

              SMTP server settings described in this lesson are not associated with [ds]BitrixVM SMTP settings[/ds][di] Complete the following actions to configure SMTP client:

              1. Go to the main menu 6. Configure pool sites > 4. Change e-mail settings on site and enter email host name

              Learn more...[/di].

              Example: BitrixVM site has a configured Gmail [ds]via BitrixVM menu[/ds][di] This lesson contains some examples of email services employed at BitrixVM.

              Learn more...[/di]. Add new SMTP connection for Yahoo.com via site administrative section. Now, the From field in a new email or email campaign has a selection of two email services: Gmail and Yahoo. Depending on which service is selected, system sends emails either using Bitrix Virtual Appliance or Bitrix24 core.

              Can there be conflicting email duplication in case SMTP has been configured both locally and on the virtual appliance?

              No, settings won't clash or emails won't duplicate. When sending an email the system checks, if there is an active smtp parameter in the file /bitrix/.settings.php:

              • if such such parameter exists, email will be sent via local SMTP;
              • if such parameter doesn't exist, an email will be sent using server functionality via [ds]msmtp[/ds][di]For SMTP client setup, execute the following:

                1. At the main menu, go to 6. Configure pool sites > 4. Change e-mail settings on site and enter the host name to configure email dispatch

                Learn more...[/di].


              Name Spaces

              Name spaces

              The notion of name spaces permits giving system elements clearer names, getting rid of numerous name prefixes, and also avoiding potential conflicts. All classes delivered in a standard installation package must be located in the Bitrix name space that does not overlap either with PHP or with partner implementations. Each standard module determines its subspace in the Bitrix name space which coincides with the module name. For example, for the forum module Bitrix\Forum will be the name space, and for the main module – Bitrix\Main.

              Note. For partner classes, the namespace can be as follows:
              namespace Asd\Metrika;  
               
              class CountersTable extends Entity\DataManager  
              {  
                 ....

              This means that this class (in /lib/) belongs to the module asd.metrika and it can be addressed as follows (after the indicated module is connected):

              \Asd\Metrika\CountersTable::update();

              The class itself is located in the file asd.metrika/lib/counters.php.

              If necessary, a module may organize subspaces inside its name space. For example, Bitrix\Main\IO, Bitrix\Forum\Somename\Somename2. But this option should be used only if it is justified for the organization of a correct architecture of this module.


              Naming Rules

              • Name spaces must be named in “UpperCamelCase.”
              • Names cannot contain any symbols but for Latin letters.
              • Class name must be a noun. Unnecessary acronyms and abbreviations should be avoided.

              Examples:

              namespace Bitrix\Main\Localization;
              namespace Bitrix\Main\Entity\Validator;

              Abbreviations that are not generally accepted (in Bitrix Framework) cannot be used.

              Attention! The examples that are provided does do not mention name spaces. It make the text more readable and easier to comprehend. Prior using the examples provided in the documentation, a name space must be added.

              It ca be done as follows:

              • By using PHPdoc;
              • By using IDE;
              • additionally, the documentation context usually clarifies which class is described.

              Full address line can be abridged. Instead \Bitrix\Main\Class::Function() you can specify Main\Class::Function().


              Synonyms also can be used instead of long name spaces. To do it, use is inserted. For example, the following long structure is available:

              \Bitrix\Main\Localization\Loc::getMessage("NAME");

              To abridge it, declare a synonym at the start of the file and use an abridged variant of the call afterwards:

              use \Bitrix\Main\Localization\Loc;
              ...
              Loc::getMessage("NAME");

              Related links:


              Modules in D7

              System modules (both standard and downloaded from Bitrix24 Marketplace) are located in the bitrix/modules system folder. User modules can be located in the folder /local/modules (Third-party developer modules may be located in different folders)]. Such difference allows developers to conveniently organize version control for their development products and maintain continuous updates using the standard update system.

              Module's API (classes, logic) are located in the module's subfolder [dw]/lib[/dw][di]This folder is optional if your class doesn't have its own methods.[/di]. For example, Main module path can be as follows: bitrix/modules/main/lib. The same folder is used for connecting API when specific rules are satisfied:

              • Class files must be titled in lower case, with class name equal to file name. For example: /lib/myclass.php.
              • Files must contain a correct namespace. Example: when module is connected as follows:
                Loader::includeModule('company.shotname');
                the class must have a namespace: namespace Company\Shotname.
              • Corresponding method must be connected where such module classes are used in the Control Panel.

              Exceptions

              The new core uses the mechanism of exceptions.

              Exception case (when an exception may be given) is an atypical situation when it makes no sense to continue the performance of the basic algorithm.


              Examples

              If a user has sent a form with an empty Name field, it is not an exception case. It is a regular expected case that must be handled appropriately.

              But if in case of API method invocation in order to change an infoblock element an empty element id was specified, it is an exception case. It is unexpected and it makes no sense to continue changing the element.

              If the method is waiting for the user id and you transmit a line, it is an exception, because the method does not know what to do with the line in this case.

              If the GetList method accepts the timestamp filter, and developer has written “tymestamp,” it will be an exception.


              Exception Hierarchy

              Available exceptions have a hierarchy. It handles exceptions, shows the activated exceptions, which allows to take appropriate actions. The general hierarchy logic is as follows:

              \Exception
                 Bitrix\Main\SystemException         - basic class of all system exceptions
                    Bitrix\Main\IO\IoException         - basic class of all exceptions of file input-output
                       Bitrix\Main\IO\FileDeleteException   - exception in case of file deletion
                       Bitrix\Main\IO\FileNotFoundException   - absence of a required file
                       Bitrix\Main\IO\FileOpenException   - exception in case of file opening
                       Bitrix\Main\IO\InvalidPathException   - invalid path
                       Bitrix\Main\IO\FileNotOpenedException - file not opened 
                    Bitrix\Main\Config\ConfigurationException   - configuration error
                    Bitrix\Main\Security\SecurityException      - security error
                    Bitrix\Main\Security\Sign\BadSignatureException  - signature error exception.
                    Bitrix\Main\ArgumentException      - basic class of exceptions associated with incoming parameters of methods
                       Bitrix\Main\ArgumentNullException   - parameter must not be left empty
                       Bitrix\Main\ArgumentOutOfRangeException   - parameter outside a permitted range
                       Bitrix\Main\ArgumentTypeException   - inadmissible type parameter
                    Bitrix\Main\DB\DbException      - basic class for database exceptions
                       Bitrix\Main\DB\ConnectionException   - exception during connection
                       Bitrix\Main\DB\SqlException      - exception during performance of a query
                    Bitrix\Main\NotSupportedException   - is invoked if the functionality is not supported
                    Bitrix\Main\NotImplementedException   - is invoked if the functionality must be supported but is not implemented so far
               Bitrix\Main\ObjectPropertyException - is invoked when the object properties are invalid
               Bitrix\Main\ObjectNotFoundException - is invoked when object does not exist 
               Bitrix\Main\ObjectException - is invoked if object cannot be created.  
               Bitrix\Main\LoaderException      - exception in loader
              

              Bitrix\Main\SystemException is the basic class for all system exceptions; all the rest of exceptions are inherited from it. This class re-defines the constructor for the \Exception system class. If the system class receives the message and error code on input:

              <?php
              public function __construct($message = null, $code = 0, Exception $previous = null);

              the constructor Main\SystemException additionally receives the file with a thrown exception and a number of string:

              <?php
              public function __construct($message = "", $code = 0, $file = "", $line = 0, \Exception $previous = null);

              Thrown exception must have the most suitable type possible.

              If your method creates an exception, it must be described in the method phpDoc.

              In Bitrix Framework, it is done as follows:

               /**
                   * Searches connection parameters (type, host, db, login and password) by connection name
                   *
                   * @param string $name Connection name
                   * @return array|null
                   * @throws \Bitrix\Main\ArgumentTypeException
                   * @throws \Bitrix\Main\ArgumentNullException
                   */
                  protected function getConnectionParameters($name) {}

              Ignoring exceptions

              Sometimes, an occurred error must not interrupt execution of a script. Such situation can be exemplified by the operation of CDN module administration page.

              If the CDN module is enabled, the traffic data is displayed at the top of the page. Its code looks as follows:

              $cdn_config = CBitrixCloudCDNConfig::getInstance()->loadFromOptions();
              $APPLICATION->SetTitle(GetMessage("BCL_TITLE"));
              require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_after.php");
              if (is_object($message))
              	echo $message->Show();
              
              if (CBitrixCloudCDN::IsActive())
              {
              	try
              	{
              		if ($cdn_config->getQuota()->isExpired())
              			$cdn_config->updateQuota();
              
              		$cdn_quota = $cdn_config->getQuota();
              		if ($cdn_quota->getAllowedSize() > 0.0 || $cdn_quota->getTrafficSize() > 0.0)
              		{
              			CAdminMessage::ShowMessage(array(
              				"TYPE" => "PROGRESS",
              				"DETAILS" => '

              '.GetMessage("BCL_CDN_USAGE", array( "#TRAFFIC#" => CFile::FormatSize($cdn_quota->getTrafficSize()), "#ALLOWED#" => CFile::FormatSize($cdn_quota->getAllowedSize()), )).'

              #PROGRESS_BAR#', "HTML" => true, "PROGRESS_TOTAL" => $cdn_quota->getAllowedSize(), "PROGRESS_VALUE" => $cdn_quota->getTrafficSize(), )); } } catch (Exception $e) { CAdminMessage::ShowMessage($e->getMessage()); } }

              The code above shows that if CDN is active, the progress bar with the displayed traffic data is created. However, if an error occurs during execution of this code, an exception will be thrown. This exception will be caught, because all the code is located in try and the branch will be executed after catch, where the error message is displayed via the standard function. The script execution will not be interrupted.



              Code Writing Rules

              The quality of a program begins with the quality of the source code. The fundamental factor for quality of source code is its readability and clarity. Formalized rules are necessary to write code which will be readable and understandable.

              The rules for formatting code must be uniform throughout the entire project. It is highly desirable for the rules to be similar from project to project.

              Note: The code formatting rules for core D7 are somewhat different from the code formatting rules for old core.

              1. Source Code Formatting


              1.1. Text Structure Rules


              1.1.1. Line Length

              Avoid typing lines whose length exceeds 120 characters. If a line spans beyond that limit, use the line wrapping rules described below.


              1.1.2. Line Wrapping Rules

              If a line length exceeds 120 characters, the following wrapping rules apply:

              • wrap lines after the comma or before the operator;
              • the wrapped line must be indented by one tab;
              • use UNIX line ends.

              Example 1: The code

              $arAuthResult = $USER->ChangePassword($USER_LOGIN, $USER_CHECKWORD, $USER_PASSWORD, $USER_CONFIRM_PASSWORD, $USER_LID);

              needs to be wrapped as follows:

              $arAuthResult = $USER->ChangePassword($USER_LOGIN, $USER_CHECKWORD,
                   $USER_PASSWORD, $USER_CONFIRM_PASSWORD, $USER_LID);

              Example 2: The code

              if(COption::GetOptionString("main", "new_user_registration", "N")=="Y" && $_SERVER['REQUEST_METHOD']=='POST' &&$TYPE=="REGISTRATION" && (!defined("ADMIN_SECTION") || ADMIN_SECTION!==true)) 

              needs to be wrapped as follows

              if (COption::GetOptionString("main", "new_user_registration", "N") == "Y")
                   && $_SERVER['REQUEST_METHOD'] == 'POST' && $TYPE == "REGISTRATION"
                   && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true)
              


              1.1.3. Spaces And Tabs

              Use tabs for indentation. Using spaces for indentation is forbidden for the following reasons:

              • with tabs, any developer can configure his or her text editor to show the desired tab length;
              • using tabs makes file size smaller;
              • if both tabs and spaces are used for indentation, the originally intended text formatting is likely to be damaged if a different tab size is used in a text editor.

              1.1.4. Scopes And Code Blocks

              The code block contents must be indented by one tab. The code block contents must not be on the same line as the controlling statement.

              Example:

              function func()
              {
                   if (condition)
                   {
                        while (condition2)
                        {
              
                        }
                    }
              }

              1.1.5.Rules for placing braces

              Opening braces must be place under a corresponding operator and on the same indent with it. Closing braces must be placed under the corresponding opening braces.

              Example:

              if ($condition)
              {
                 ...
              }
              


              1.1.6. Using the ternary operator "?:"

              Conditions must be enclosed in parentheses, and thus separated from the rest of the code. As much as possible, actions that occur under these conditions should be simple functions. If an entire branched block reads poorly, then it is worth replacing it with if/else.

              Example:

              (condition ? funct1() : func2());


              1.2. Expressions And Statements


              1.2.1. Expressions

              One line must contain only one expression.

              Example. This expression is formatted incorrectly:

              $a = $b; $b = $c; $c = $a;

              Rewrite it like this:

              $a = $b;
              $b = $c;
              $c = $a;
              

              1.2.2. The statements if, else, while etc.

              Use one of the following two formatting rules depending on the statement length.

              if a controlled code block contains only one statement, use the following form:

              if (expression)
                   statement 1;
              else
                   statement 2;
              

              if at least one controlled code block contains multiple statements, use braces:

              if (expression)
              {
                   statement 1;
              }
              else
              {
                    statement 2; 
                    statement 3;
              }
              

              The rule Scopes And Code Blocks must be obeyed when writing multiple statements: they must be indented by one tab off the controlling statement. The braces must exist on new lines on the same level as the controlling statement.


              Example. This code:

              if ($a == 0) $a = 10;
              else{
              $a = 5;
              $b = 10;}
              

              must be reformatted like this:

              if ($a == 0)
              {
                $a = 10;
              }
              else
              {
                $a = 5;
                $b = 10;
              }
              


              1.2.3. Compound Expressions

              Compound expressions must be split in multiple lines according to rules described in “The statements if, else, while etc.”.

              For example, consider the following code:

               if(COption::GetOptionString("main", "new_user_registration", "N")=="Y" && $_SERVER['REQUEST_METHOD']=='POST' &&
                   $TYPE=="REGISTRATION" && (!defined("ADMIN_SECTION") || ADMIN_SECTION!==true))
              

              Make it readable by sticking to the formatting rules:

              if (COption::GetOptionString("main", "new_user_registration", "N") == "Y"
                   && $_SERVER['REQUEST_METHOD'] == 'POST' && $TYPE == "REGISTRATION"
                   && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true))
              {
              }
              

              It is recommended that you split an extremely complex expression into several simple lines of code.

              For example, the code

              if((!(defined("STATISTIC_ONLY") && STATISTIC_ONLY && substr($APPLICATION->GetCurPage(), 0,
                   strlen(BX_ROOT."/admin/"))!=BX_ROOT."/admin/")) && COption::GetOptionString("main", "include_charset", "Y")=="Y"
                   && strlen(LANG_CHARSET)>0)
              

              is definitely more readable when written like this:

              $publicStatisticOnly = False;
              if (defined("STATISTIC_ONLY")
                && STATISTIC_ONLY
                && substr($APPLICATION->GetCurPage(), 0, strlen(BX_ROOT."/admin/")) != BX_ROOT."/admin/")
              {
                   $publicStatisticOnly = True;
              }
              if (!$publicStatisticOnly && strlen(LANG_CHARSET) > 0
                   && COption::GetOptionString("main", "include_charset", "Y") == "Y")
              {
              }

              or like this:

              if (!defined("STATISTIC_ONLY") || ! STATISTIC_ONLY
                || substr($APPLICATION->GetCurPage(), 0, strlen(BX_ROOT."/admin/")) == BX_ROOT."/admin/")
              {
                if (strlen(LANG_CHARSET) > 0 && COption::GetOptionString("main", "include_charset", "Y") == "Y")
                {
                }
              }
              


              1.2.4. Arrays

              The arrays consisting of multiple lines of code should be formatted like this:

              $arFilter = array(
                "key1" => "value1",
                "key2" => array(
                    "key21" => "value21",
                    "key22" => "value22",
                )
              ); 


              1.3. Empty Lines And Spaces


              1.3.1. Empty Lines

              Use empty lines to logically divide your source code. Use multiple empty lines to divide the source code into logical or functional sections in one file. Use a single empty line to separate methods, as well as expressions and statements within a method for better readability.

              It is recommended to add a comment before a logical or functional section (see Comments).


              1.3.2. Spaces

              The comma must be followed by the space. The semicolon must be followed by the space unless it is the last character on the line (for example, in a complex “for” statement). No spaces before the comma or semicolon is allowed. Tabs must not be used instead of spaces in such cases.

              Use Cases

              • The following example shows how a space is used after the commas, but not before the parenthesis:

                TestMethod($a, $b, $c);

                These two code fragments are formatted incorrectly:

                TestMethod($a,$b,$c);

                and

                TestMethod( $a, $b, $c );

              • The following example shows to use spaces to properly separate the operators:

                $a = $b * $c / $d;

                as opposed to the same code formatted incorrectly:

                $a=$b*$c/$d;

              • Use spaces to format the “for” statements:

                for ($i = 0; $i < 10; $i++)

              • Do not merge operators and expressions like this:

                for($i=0;$i<10;$i++)

              • Note that using tabs to format expressions inside statements is not allowed.

                The following formatting should not be made a common practice:

                $arArray = array(
                  "key1" => "value1",
                  "key2" => "value2",
                );

                There is no special rule regarding the use of the space, or lack thereof, after the “if” statement.

              1.4. Other regulations

              Use parentheses to group operators in complex expressions regardless of operator precedence for better readability.

              $r = $a + ($b * $c);


              2. Naming Conventions


              2.1. General Provisions

              Do not use underscores in the identifier names because it makes them longer and less readable. Use such names that can describe the identifier purpose and meaning in an unambiguous fashion.

              At the same time, try to make your identifier shorter (but they still must be well readable).

              If a name contains an abbreviation, it’s better to use a single capital letter for the first letter of the abbreviation than to use capitals for the entire abbreviation. So it would be better to assign a name like getHtmlStatistic than getHTMLStatistic.


              2.2. Variable Names

              Start with a lowercase character and use uppercase character as separators (camelCase). Variable names can have prefixes if there is a clear need to show the type of variable: ar – for arrays, db – for data sets from databases, etc.

              For example: $testCounter, $userPassword.


              2.3. Method Names

              • Clarity in action names, which will execute a function or method.
              • Use of prefixes: is (indicates a question), get (to get a variable), set (set value).

              Example: isFileWriteable()


              2.4. Class Names

              • The name should signify an entity described by the class.
              • The name should not consist of more than 3 words.
              • The underscore symbol (‘_’) cannot be used.
              • To separate words in the name, the first letter of each word can be capitalized.

              Example: class SaleAffiliateAccount


              3. Comments


              Commentary must be in English and contain relevant information


              3.1. PHPDoc

              All classes and their public methods must be described in PHPDoc style.

              Example:

              /**
               * Gets a value that indicates whether a directory exists in the file system
               *
               * @param string $path - Path to the directory
               * @return bool - True if the directory exists, false - otherwise
               */
              

              4. Other


              4.1. Magic numbers

              Code should not contain magic numbers. Here is an example of bad code:

              $task->Update($taskId, array('STATUS' => 3), 1);

              Proper code:

              $task->Update($taskId, array('STATUS' => CTaskStatus::New), TASK_PERMISSIONS_WRITE);


              4.2. Automatic formatting tools


              4.2.1. php_beautifier

              1. Install the php_beautifier (ubuntu) pack:

              • sudo aptitude install php_beautifier or sudo aptitude install php-pear и sudo pear install PHP_Beautifier-0.1.15
              • cd
              • hg clone http://hg.max/repos/Bitrix_php_beautifier
              • sudo ln -s Bitrix_php_beautifier/Bitrix.filter.php /usr/share/php/PHP/Beautifier/Filter/Bitrix.filter.php

              Configure the editor:

              • Select the menu point Settings - Configure Kate...
              • Select the setting Plugins and place a check next to Text Filter
              • Click OK
              • Now the Tools menu will have a point called Filter Text

              Use:

              • Select a text excerpt (or Ctrl-A for all the text)
              • Tools - Filter Text
              • Enter (or select from history): php_beautifier -t -f - -l 'Lowercase Bitrix'
              • Click OK.


              Applications and Context

              Application is an object responsible for core initialization.

              Application is the entry base point (router) for query to (kernel) core global entities: connection with data sources, managed cache, etc. Also, the application contains global data that belongs to the site itself and do not depend on a specific hit. I.e. the application is an unaltered part not dependent on a specific hit.

              Any specific class of application is a successor of the abstract class Bitrix\Main\Application.

              The specific class Bitrix\Main\HttpApplication is responsible for a regular http hit on the site.

              The application supports the Singleton template. I.e. as a part of the hit there is only one copy of a specific type of application. It can be obtained using the instruction:

              $application = Application::getInstance();

              Context is an object responsible for a specific hit. It contains a query of a current hit, response to it, and also the server parameters of the current hit. I.e. it is a variable part that depends on the current hit.

              Any specific class of a context is a successor of the abstract class Bitrix\Main\Context. Two specific classes of the context are supported – Bitrix\Main\HttpContext and Bitrix\Main\CliContext. The specific class Bitrix\Main\HttpContext is responsible for a regular http hit on the site.

              The following code may be used in order to obtain a context of the current hit execution:

              $context = Application::getInstance()->getContext();

              If the application of the Bitrix\Main\HttpApplication type was initiated, this call will bring an instance of the Bitrix\Main\HttpContext context type.

              The context contains a query of the current hit. In order to receive the query the following code may be used:

              $context = Application::getInstance()->getContext();
              $request = $context->getRequest();

              Note: In all the examples, a full form of record is used (sometimes without indicating name spaces) that permits you to obtain the result from any code point. However, short forms may exist for this specific code point in order to access the result.

              A request is a class instance that is a successor of the Bitrix\Main\Request class. In case of a normal http request the request will be a class instance of Bitrix\Main\HttpRequest that extends Bitrix\Main\Request. This class is a dictionary providing access to “key-value” pairs of incoming parameters.

              The following code may be used in order to access an incoming parameter transmitted by the GET or POST methods:

              $value = $request->get("some_name");
              $value = $request["some_name"];

              Note: Code $value = $request["some_name"]; returns string processed by the security module filters. However, this doesn't signify about its security, all depends on how it will be used further.

              Other useful query methods:

              $value = $request->getQuery("some_name");   // receive GET parameter
              $value = $request->getPost("some_name");   // receive POST parameter
              $value = $request->getFile("some_name");   // receive downloaded file
              $value = $request->getCookie("some_name");   // receive cookie value
              $uri = $request->getRequestUri();   // receive an address requested
              $method = $request->getRequestMethod();   // receive a request method
              $flag = $request->isPost();      // true - POST-request, otherwise false

              Errors

              In the old core, API tried to interpret for a user/developer and was overlooking errors and inaccuracies. As a result, errors that are difficult to catch may occur. In addition, such an approach causes a lot of implicit agreements that must be taken into account.

              For example, a user/developer selects entries for deletion using a filter. In this case, they incidentally make a writing error in the filter name. A typical API of the old core will ignore this filter and return all the entries. The next instruction will successfully delete all of these entries.

              The method changes in the new core D7. API does not have to guess anything for a user. API must respond appropriately if it meets an unexpected situation, such as an unknown filter, no id transmitted, lack of value, excessive value, must not be invoked in this mode, etc.

              When an error is displayed on the screen (if the debugging mode is available), connects the file /error.php (The file /error.php is located in site root. It can contain, for example, error message print).

              Example of error.php

              You can print error in the site design inside this file, as well as specify HTTP status code (for example, "500 Internal Server Error").

              API

              Module API (classes) is not divided by databases. ORM takes care of all the twists and turns of work with a specific database.

              No prefixes or suffixes shall be used in class names.

              Each module API class may be located in a separate file with a name that coincides with the class name written in lower case. The classes located in the root of module namespace must be based in the files of the /lib module folder root. The classes located in subspaces inside the module namespace must be based in the files of the relevant subfolders of the /lib module folder.

              For example, the Bitrix\Main\Application class must be located in the file /lib/application.php in respect of the main module root folder; the Bitrix\Main\IO\File class must be located in the file /lib/io/file.php in respect of the main module root folder; and the Bitrix\Forum\Message class must be located in the file /lib/message.php in respect of the root folder of the forum module.

              If these naming rules are complied with, once a module is connected, its classes are uploaded automatically upon first call. No additional actions are needed for the registration and connection of files with classes.

              Note: However, for performance reasons, additional registration and connection are recommended for classes that are used often.

              Classes of the ORM entities (successors of the class Bitrix\Main\Entity\DataManager) constitute an exception from the class and file naming rules. The names of such classes are generated with the Table suffix. E.g., CultureTable, LanguageTable. But file names do not contain the table suffix. Such classes are also connected automatically.

              Note: There is a possibility to register a class in the autoload system using the following method:
              void Loader::registerAutoLoadClasses(
              	$moduleName,
              	array $arClasses
              )

              It can be used in order to merge small classes in one file.

              Non-standard classes (partner’s classes) must be located in their own namespaces that coincide with the names of relevant partners. Each partner’s module determines its own subspace in the partner’s namespace that coincides with the module name without the partner’s name. E.g., for the module mycompany.catalog of the partner "Mycompany", the namespace will be MyCompany\Catalog. Other rules are the same as for standard modules.

              The following instruction is used in order to connect a module in a new core:

              mixed Loader::includeModule($moduleName);
              


              Naming Rules

              Classes

              • Classes must be named in UpperCamelCase.
              • Their name cannot contain any other symbols but for Latin letters.
              • Class name must be a noun. Unnecessary acronyms and abbreviations should be avoided.

              Examples:

              class User;
              class UserInformation;

              Methods

              • The methods, including class methods, must be named in lowerCamelCase.
              • Their name cannot contain any other symbols but for Latin letters.
              • Numbers may be used, provided that the name cannot be formed otherwise. E.g.: encodeBase64, getSha1Key.
              • The method name must start with a verb.
              • The name length must contain at least 3 characters.

              Examples:

              run();
              setImage();
              getName();

              Pure data

              • Pure data, including class pure data, must be written in UPPER_REGISTER_WITH_A_FLATWORM_SEPARATOR.
              • May contain Latin letters, flatworm character, and numbers (except for the first position).

              Examples:

              DATE_TIME_FORMAT
              LEVEL_7

              Class Members, Method Parameters, and Variables

              • Must be named in a lowerCamelCase.
              • May not contain prefixes meaning membership in class, appurtenance to parameters, type, and other insignificant things. Example of unnecessary prefixes: $this->mAge, function setName($pName), $arrArray.
              • May contain Latin letters and numbers (but for the first position).

              Examples:

              $firstName = '';
              $counter = 0;

              Generally Acceptable Abbreviations of the Names of Variables and Methods

              • Abbreviations in the first position must be written in lower case letters, abbreviations in other positions must start with a capital letter, with all other letters being written in lowercase.
              • Class names should start with a upper case letter, with all other letters being written in lower case.

              Example:

              $xmlDocument
              $mainXmlDocument
              HttpParser

              Abbreviations that are not generally acceptable (in Bitrix) cannot be used.

              Working with the Database

              General architecture of API classes for working with databases:

              • The connection pool Bitrix\Main\DB\ConnectionPool contains information about connections of applications. There are default connections (main connections) and there may be a set of named connections to connect elsewhere, including to another database of a different type.
              • Specific connection classes inheriting the abstract class Bitrix\Main\DB\Connection ensure low-level operations with the database. They are divided into classes by databases.
              • Specific classes of forming SQL queries inheriting Bitrix\Main\DB\SqlHelper that help to form a query without going into the syntax of a specific database.
              • Specific classes for work with query execution result inheriting Bitrix\Main\DB\Result. They also vary according to databases.

              These classes permit working with databases at a low level, but it is rarely required. Instead, working through ORM is recommended since ORM permits programming only at a business logic level.

              Obtaining Database Connection, Named Connections

              A connection may be obtained through applications and is, among other things, an entry point. From this entry point, instances of “star” objects for this application may be obtained that are needed for all (or almost all) pages or components of this application.

              $connection = Main\Application::getConnection();
              $sqlHelper = $connection->getSqlHelper();
              
              $sql = "SELECT ID FROM b_user WHERE LOGIN = '".$sqlHelper->forSql($login, 50)."' ";
              
              $recordset = $connection->query($sql);
              while ($record = $recordset->fetch())
              {
                 ***

              Various Forms of Query Execution Call

              Accordingly, a call must be executed through the application in various forms: simple query, query with a limit for entries, scalar query, or a query “in general.”

              $result1 = $connection->query($sql);
              $result2 = $connection->query($sql, $limit);
              $result3 = $connection->query($sql, $offset, $limit);
              
              $cnt = $connection->queryScalar("SELECT COINT(ID) FROM table");
              
              $connection->queryExecute("INSERT INTO table (NAME, SORT) VALES ('Name', 100)")

              Obtaining Query Results:

              $connection = Main\Application::getConnection();
              $sqlHelper = $connection->getSqlHelper();
              
              $sql = "SELECT ID FROM b_user WHERE LOGIN = '".$sqlHelper->forSql($login, 50)."' ";
              
              $recordset = $connection->query($sql);
              while ($record = $recordset->fetch())
              {
                 ***

              Typed data are immediately returned as a type and not as lines or numbers.


              Result Modification:

              $connection = \Bitrix\Main\Application::getConnection();
              $recordset = $connection->query("select * from b_iblock_element", 10);
              $recordset->setFetchDataModifier(
                 function ($data)
                 {
                     $data["NAME"] .= "!";
                     return $data;
                 }
              );
              
              while ($record = $recordset->fetch(\Bitrix\Main\Text\Converter::getHtmlConverter()))
                 $data[] = $record

              Result may be modified and conversed immediately, e.g. for preparation to XML displaying, etc.


              Classes. Examples

              Configuration

              Located in the namespace \Bitrix\Main\Config. Consists of 2 classes: \Bitrix\Main\Config\Configuration и \Bitrix\Main\Config\Option.

              Configuration

              $realm = \Bitrix\Main\Config\Configuration::getValue("http_auth_realm");
              if (is_null($realm))
                  $realm = "Bitrix Site Manager"

              Class is responsible for the global settings of the entire application. (This feature is determined by pure data in the old core.) Class operates a single base of settings stored in the file /bitrix/.settings.php. The data stored are random. For example, the entire data pool can be stored for named connections.

              Option

              $cookiePrefix = \Bitrix\Main\Config\Option::get('main', 'cookie_name', 'BITRIX_SM');
              $cookieLogin = $request->getCookie($cookiePrefix.'_LOGIN');
              $cookieMd5Pass = $request->getCookie($cookiePrefix.'_UIDN');

              The option class is virtually the same as the COption class of the old core and works with the parameters of modules and sites stored in the database. It is controlled from the administrative section: settings of specific forms, setup, etc.

              Files

              Work with files is object-oriented. It is located in the Bitrix\Main\IO namespace and has 3 basic classes:

              • Path – handling paths, static.
              • Directory – handling folders.
              • File – handling files.

              Some other classes, including abstract, for managing hierarchy are also available in addition to these classes.

              Other classes

              The folder bitrix/modules/main/lib contains class library for implementing different frequent actions, included in the Main module and not spread among different modules. В том числе в соответствующих пространствах лежат файлы и API для работы:

              Equivalent CUtil::jSPostUnescape() in Core D7

              If you need to use HttpRequest in AJAX queries:

              Application::getInstance()->getContext()->getRequest()->getPost('name')

              you'll have to consider that CUtil::JSPostUnescape won't help in case of encoding win-1251.

              You can use instead:

              use Bitrix\Main\Web\PostDecodeFilter;
              ...
              Application::getInstance()->getContext()->getRequest()->addFilter(new PostDecodeFilter)
              

              After this, you can get decoded data via getPost.


              Handling D7. Location example

              Do not forget to connect sale module.

              Types of locations

              Adding a location type:

              $res = \Bitrix\Sale\Location\TypeTable::add(array(
              	'CODE' => 'CITY',
              	'SORT' => '100', // nesting level
              	'DISPLAY_SORT' => '200', // display priority when searching
              	'NAME' => array( // language names
              		),
              		'en' => array(
              			'NAME' => 'City'
              		),
              	)
              ));
              if($res->isSuccess())
              {
              	print('Type added with ID = '.$res->getId());
              }

              Location type update

              $res = \Bitrix\Sale\Location\TypeTable::update(21, array(
              	'SORT' => '300',
              	'NAME' => array(
              		'en' => array(
              			'NAME' => 'New City'
              		),
              	)
              ));
              if($res->isSuccess())
              {
              	print('Updated!');
              }

              Deleting location type

              $res = \Bitrix\Sale\Location\TypeTable::delete(21);
              if($res->isSuccess())
              {
              	print('Deleted!');
              }

              Getting type of location by ID

              $item = \Bitrix\Sale\Location\TypeTable::getById(14)->fetch();
              print_r($item);

              Getting list of types with names on the current language

              $res = \Bitrix\Sale\Location\TypeTable::getList(array(
              	'select' => array('*', 'NAME_EN' => 'NAME.NAME'),
              	'filter' => array('=NAME.LANGUAGE_ID' => LANGUAGE_ID)
              ));
              while($item = $res->fetch())
              {
              	print_r($item);
              }

              Getting groups with account of hierarchy having this location

              <?
              \Bitrix\Main\Loader::includeModule('sale');
              
              function getGroupsByLocation($locationId)
              {
                  $res = \Bitrix\Sale\Location\LocationTable::getList([
                      'filter' => ['=ID' => $locationId],
                      'select' => [
                          'ID', 'LEFT_MARGIN', 'RIGHT_MARGIN'
                      ]
                  ]);
              
                  if(!$loc = $res->fetch())
                  {
                      return [];
                  }
              
                  $locations = [$locationId];
              
                  $res = \Bitrix\Sale\Location\LocationTable::getList([
                      'filter' => [
                          '<LEFT_MARGIN' => $loc['LEFT_MARGIN'],
                          '>RIGHT_MARGIN' => $loc['RIGHT_MARGIN'],
                          'NAME.LANGUAGE_ID' => LANGUAGE_ID,
                      ],
                      'select' => [
                          'ID',
                          'LOCATION_NAME' => 'NAME.NAME'
                      ]
                  ]);
              
                  while($locParent = $res->fetch())
                  {
                      $locations[] = $locParent['ID'];
                  }
              
                  $res = \Bitrix\Sale\Location\GroupLocationTable::getList([
                      'filter' => ['=LOCATION_ID' => $locations]
                  ]);
              
                  $groups = [];
              
                  while($groupLocation = $res->fetch())
                  {
                      $groups[] = $groupLocation['LOCATION_GROUP_ID'];
                  }
              
                  return $groups;
              }

              Locations

              Adding

              $res = \Bitrix\Sale\Location\LocationTable::add(array(
              	'CODE' => 'newly-created-location-code',
              	'SORT' => '100', // priority for showing in list
              	'PARENT_ID' => 1, // parent location ID
              	'TYPE_ID' => 14, // type ID
              	'NAME' => array( // language names
              		'en' => array(
              			'NAME' => 'New York'
              		),
              	),
              	'EXTERNAL' => array( // external service values
              		array(
              			'SERVICE_ID' => 1, // service ID
              			'XML_ID' => '163000' // value
              		),
              		array(
              			'SERVICE_ID' => 1,
              			'XML_ID' => '163061'
              		),
              	)
              ));
              if($res->isSuccess())
              {
              	print('Location added with ID = '.$res->getId());
              }
              else
              {
              	print_r($res->getErrorMessages());
              }

              Update

              $res = \Bitrix\Sale\Location\LocationTable::update(3156, array(
              	'PARENT_ID' => 33,
              	'NAME' => array(
              		'de' => array(
              			'NAME' => 'New York'
              		),
              	)
              ));
              if($res->isSuccess())
              {
              	print('Updated!');
              }

              Deleting

              $res = \Bitrix\Sale\Location\LocationTable::delete(3156);
              if($res->isSuccess())
              {
              	print('Deleted!');
              }

              Getting location by ID

              $item = \Bitrix\Sale\Location\LocationTable::getById(3159)->fetch();
              print_r($item);

              Getting location by CODE, with optional filtering\field retrieval. In fact, this is a wrapper \Bitrix\Sale\Location\LocationTable::getList().

              $item = \Bitrix\Sale\Location\LocationTable::getByCode('newly-created-location-code', array(
              	'filter' => array('=NAME.LANGUAGE_ID' => LANGUAGE_ID),
              	'select' => array('*', 'NAME_EN' => 'NAME.NAME')
              ))->fetch();
              print_r($item);

              Getting list of locations with names on the current language and type codes

              $res = \Bitrix\Sale\Location\LocationTable::getList(array(
              	'filter' => array('=NAME.LANGUAGE_ID' => LANGUAGE_ID),
              	'select' => array('*', 'NAME_EN' => 'NAME.NAME', 'TYPE_CODE' => 'TYPE.CODE')
              ));
              while($item = $res->fetch())
              {
              	print_r($item);
              }

              Getting node direct descendants with ID=1 with names on the current language, codes and names for location types

              $res = \Bitrix\Sale\Location\LocationTable::getList(array(
              	'filter' => array(
              		'=ID' => 1, 
              		'=CHILDREN.NAME.LANGUAGE_ID' => LANGUAGE_ID,
              		'=CHILDREN.TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
              	),
              	'select' => array(
              		'CHILDREN.*',
              		'NAME_EN' => 'CHILDREN.NAME.NAME',
              		'TYPE_CODE' => 'CHILDREN.TYPE.CODE',
              		'TYPE_NAME_EN' => 'CHILDREN.TYPE.NAME.NAME'
              	)
              ));
              while($item = $res->fetch())
              {
              	print_r($item);
              }

              Getting parent nodes for three nodes

              $res = \Bitrix\Sale\Location\LocationTable::getList(array(
              	'filter' => array(
              		'=ID' => array(3159, 85, 17), 
              		'=PARENT.NAME.LANGUAGE_ID' => LANGUAGE_ID,
              		'=PARENT.TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
              	),
              	'select' => array(
              		'PARENT.*',
              		'NAME_EN' => 'PARENT.NAME.NAME',
              		'TYPE_CODE' => 'PARENT.TYPE.CODE',
              		'TYPE_NAME_EN' => 'PARENT.TYPE.NAME.NAME'
              	)
              ));
              while($item = $res->fetch())
              {
              	print_r($item);
              }

              Getting path from tree root to the current item

              $res = \Bitrix\Sale\Location\LocationTable::getList(array(
              	'filter' => array(
              		'=ID' => 224, 
              		'=PARENTS.NAME.LANGUAGE_ID' => LANGUAGE_ID,
              		'=PARENTS.TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
              	),
              	'select' => array(
              		'I_ID' => 'PARENTS.ID',
              		'I_NAME_EN' => 'PARENTS.NAME.NAME',
              		'I_TYPE_CODE' => 'PARENTS.TYPE.CODE',
              		'I_TYPE_NAME_EN' => 'PARENTS.TYPE.NAME.NAME'
              	),
              	'order' => array(
              		'PARENTS.DEPTH_LEVEL' => 'asc'
              	)
              ));
              while($item = $res->fetch())
              {
              	print_r($item);
              }

              Getting list of root nodes with indicated number of descendants

              $res = \Bitrix\Sale\Location\LocationTable::getList(array(
              	'filter' => array(
              		'=PARENT_ID' => 0,
              		'=NAME.LANGUAGE_ID' => LANGUAGE_ID,
              		'=TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
              	),
              	'select' => array(
              		'ID',
              		'NAME_EN' => 'NAME.NAME',
              		'TYPE_CODE' => 'TYPE.CODE',
              		'TYPE_NAME_EN' => 'TYPE.NAME.NAME',
              		'CHILD_CNT'
              	)
              ));
              while($item = $res->fetch())
              {
              	print_r($item);
              }

              Getting external data for locations with indicated service code

              $res = \Bitrix\Sale\Location\LocationTable::getList(array(
              	'filter' => array(
              		'CODE' => array('newly-created-location-code', '0000028090'),
              	),
              	'select' => array(
              		'EXTERNAL.*',
              		'EXTERNAL.SERVICE.CODE'
              	)
              ));
              while($item = $res->fetch())
              {
              	print_r($item);
              }

              Getting subtree of nodes on the current language

              $res = \Bitrix\Sale\Location\LocationTable::getList(array(
              	'runtime' => array(
              		'SUB' => array(
              			'data_type' => '\Bitrix\Sale\Location\Location',
              			'reference' => array(
              				'>=ref.LEFT_MARGIN' => 'this.LEFT_MARGIN',
              				'<=ref.RIGHT_MARGIN' => 'this.RIGHT_MARGIN'
              			),
              			'join_type' => "inner"
              		)
              	),
              	'filter' => array(
              		'=CODE' => '0000028042',
              		'=SUB.NAME.LANGUAGE_ID' => LANGUAGE_ID
              	),
              	'select' => array(
              		'S_CODE' => 'SUB.CODE',
              		'S_NAME_EN' => 'SUB.NAME.NAME',
              		'S_TYPE_CODE' => 'SUB.TYPE.CODE'
              	)
              ));
              while($item = $res->fetch())
              {
              	print_r($item);
              }

              Getting locations included into the group without hierarchy.

              \Bitrix\Main\Loader::includeModule('sale');
              
              
              /* Group identifier */
              $groupId = 1
              
              /* Get locations included into group */
              $res = \Bitrix\Sale\Location\GroupLocationTable::getConnectedLocations(1);
              
              while($item = $res->fetch())
              {
                  var_dump($item);
              }

              Service Locator

              Description

              Service locator - is a design pattern used for convenient handling of application services. You can find more details in this article.

              Idea of service patten is designed to use special object (service locator) that will be responsible for creating and locating services instead of creating specific services directly (via "new"). It's a kind of a register.

              Class \Bitrix\Main\DI\ServiceLocator implements the interface PSR-11. Available from main version 20.5.400.

              Simple example of use:

              $serviceLocator = \Bitrix\Main\DI\ServiceLocator::getInstance();
              
              if ($serviceLocator->has('someService'))
              {
                  $someService = $serviceLocator->get('someService');
                  //...$someService service use
              }

              Service registration

              Service registration via bitrix/.settings.php files

              Before querying the service, register it, using .settings.php files. All the necessary services are listed in the services section.

               [
                      'value' => [
                          'someServiceName' => [
                              'className' => \VendorName\Services\SomeService::class,
                          ],                      
                          'someGoodServiceName' => [
                              'className' => \VendorName\Services\SecondService::class,
                              'constructorParams' => ['foo', 'bar'],
                          ],                      
                      ],
                      'readonly' => true,
                    ],
                    //...
                 ];            

              As a result, these services will be available immediately after kernel is initialized. Below you can read about the available service describing methods.

              $serviceLocator = \Bitrix\Main\DI\ServiceLocator::getInstance();
              $someGoodServiceName = $serviceLocator->get('someGoodServiceName');
              $someServiceName = $serviceLocator->get('someServiceName');

              Service registration has been performed via the {moduleName}/.settings.php module setting files

              Module root directory can also contain the .settings.php file. It can contain service descriptions belonging to this module and are used in it. Semantics is similar to description in the global bitrix/.settings.php and config description rules.

               [
                      'value' => [
                          'someModule.someServiceName' => [
                              'className' => \VendorName\SomeModule\Services\SomeService::class,
                          ],                      
                          'someModule.someAnotherServiceName' => [
                              'constructor' => static function () {
                                  return new \VendorName\SomeModule\Services\SecondService('foo', 'bar');
                              },
                          ],                      
                          'someModule.someGoodServiceName' => [
                              'className' => \VendorName\SomeModule\Services\SecondService::class,
                              'constructorParams' => static function (){
                                  return ['foo', 'bar'];
                              },
                          ],                      
                      ],
                      'readonly' => true,
                    ],
                    //...
                 ];
              Attention! These services will be registered only after module is connected. Also, it is recommended to name module services by using module name prefix, to avoid duplicate service codes, for example:
               iblock.imageUploader
                  disk.urlManager
                  crm.entityManager
                  crm.urlManager
                  someModule.urlManager.

              Registering service via API

              Services can be registered via API as well. Use the class methods \Bitrix\Main\DI\ServiceLocator

              Service configuration

              Configuration is described as an array and prompts service locator a method for creating an object. Presently, there are three description methods:

              1. Indication of service class. Service locator creates the service by calling new $className.
                 'someModule.someServiceName' => [
                            'className' => \VendorName\SomeModule\Services\SomeService::class,
                        ]
              2. Indicating service class and parameters to be passed into constructor. Service locator creates a service by calling new $className('foo', 'bar').
                'someModule.someServiceName' => [
                            'className' => \VendorName\SomeModule\Services\SomeService::class,
                            'constructorParams' => ['foo', 'bar'],
                        ]                      
                
                        'someModule.someServiceName' => [
                            'className' => \VendorName\SomeModule\Services\SomeService::class,
                            'constructorParams' => static function (){
                               return ['foo', 'bar'];
                            },
                        ]
              3. Indicating closure-constructor that create and return service object.
                 'someModule.someAnotherServiceName' => [
                            'constructor' => static function () {
                                return new \VendorName\SomeModule\Services\SecondService('foo', 'bar');
                            },
                        ]

              Locator registration

              Service registration

              Service registration via the bitrix/.settings.php files

              Before querying the service, it must be registered. It can be done via .settings.php files. All the necessary services are listed in the services section.

               [
                      'value' => [
                          'someServiceName' => [
                              'className' => \VendorName\Services\SomeService::class,
                          ],                      
                          'someGoodServiceName' => [
                              'className' => \VendorName\Services\SecondService::class,
                              'constructorParams' => ['foo', 'bar'],
                          ],                      
                      ],
                      'readonly' => true,
                    ],
                    //...
                 ];            

              As the result, these services will be available immediately after core D7 initialization. Read below about which service description methods are presently available.

              $serviceLocator = \Bitrix\Main\DI\ServiceLocator::getInstance();
              $someGoodServiceName = $serviceLocator->get('someGoodServiceName');
              $someServiceName = $serviceLocator->get('someServiceName');

              Service registration using the module settings {moduleName}/.settings.php files

              The module root can also have the .settings.php file. You can describe services that belong and are used in this module. Semantics is similar to the description in the global bitrix/.settings.php files and config description rules.

               [
                      'value' => [
                          'someModule.someServiceName' => [
                              'className' => \VendorName\SomeModule\Services\SomeService::class,
                          ],                      
                          'someModule.someAnotherServiceName' => [
                              'constructor' => static function () {
                                  return new \VendorName\SomeModule\Services\SecondService('foo', 'bar');
                              },
                          ],                      
                          'someModule.someGoodServiceName' => [
                              'className' => \VendorName\SomeModule\Services\SecondService::class,
                              'constructorParams' => static function (){
                                  return ['foo', 'bar'];
                              },
                          ],                      
                      ],
                      'readonly' => true,
                    ],
                    //...
                 ];
              Attention! These services will be registered only after module is connected. Also, it is recommended to name the module services by using the module name prefix to avoid issues with service code duplicates, for example:
               iblock.imageUploader
                  disk.urlManager
                  crm.entityManager
                  crm.urlManager
                  someModule.urlManager.

              Service registration via API

              Services can be registered via API as well. Use the class methods \Bitrix\Main\DI\ServiceLocator

              Service configuration

              Configuration is described as an array and prompts service locator a method for creating an object. Presently, there are three methods for description:

              1. Indicating a service class. Service locator creates a service by calling a new $className.
                 'someModule.someServiceName' => [
                            'className' => \VendorName\SomeModule\Services\SomeService::class,
                        ]
              2. Indicating a service class and parameters to be passed into constructor. Service locator will create a service by calling a new $className('foo', 'bar').
                'someModule.someServiceName' => [
                            'className' => \VendorName\SomeModule\Services\SomeService::class,
                            'constructorParams' => ['foo', 'bar'],
                        ]                      
                
                        'someModule.someServiceName' => [
                            'className' => \VendorName\SomeModule\Services\SomeService::class,
                            'constructorParams' => static function (){
                               return ['foo', 'bar'];
                            },
                        ]
              3. Indicating a closure-constructor that must create and return a service object.
                 'someModule.someAnotherServiceName' => [
                            'constructor' => static function () {
                                return new \VendorName\SomeModule\Services\SecondService('foo', 'bar');
                            },
                        ]

              Controllers

              Terminology

              • Action, or AJAX action is a responder method that implements final logic, executes operations and returns data.
              • Controller - is a combination of AJAx-actions.
              • configureActions() method regulates configuration of actions inside the controller, defining rules of access to actions.
              • Pre-, post-filters are configuration items, that are action start and end event handers. Prefilter can block the start, postfilter can affect the result.

              Agreements

              Note: Main (main) module version 20.600.87 supports PSR-4 in ajax-controllers.
              • Upon calling all parameter names are caseSEnsitive
              • Upon calling all parameter names are caseSEnsitive
              • Upon calling all action names are case-INsensitive
              • Full action name is generated by the template vendor:module.partOfNamespace0.partOfNamespace1.Controller.action.
                    \Bitrix\Disk\Controller\Folder::getAction() 
                    bitrix:disk.Controller.Folder.get
                
                    \Bitrix\Disk\Controller\Intergation\Dropbox::connectAction() 
                    bitrix:disk.Controller.Intergation.Dropbox.connect
                
                    \Qsoft\Somedisk\Controller\SuperFolder::getAction() 
                    qsoft:somedisk.Controller.SuperFolder.get
              • When not indicating the vendor:, it means it is bitrix:
                    \Bitrix\Disk\Controller\Folder::getAction() 
                    disk.Controller.Folder.get
              • When indicating defaultNamespace in module settings, it can be omitted and not indicated in the action.
                	defaultNamespace = \Bitrix\Disk\Controller
                	
                    \Bitrix\Disk\Controller\Folder::getAction() 
                    disk.Folder.get
              • When indicating alias in module settings, use it instead of abbreviated namespace.
                	\Bitrix\Disk\CloudIntegration\Controller => cloud
                	
                    \Bitrix\Disk\CloudIntegration\Controller\File::getAction() 
                    disk.cloud.File.get
              • Upon calling the action from component, indicate full component name and action name (without Action suffix).
                    bitrix:list.example
                    showFormAction
                
                    BX.ajax.runComponentAction('bitrix:list.example', 'showForm', {
                        ...
                    }).then(function (response) {});
              • Time, date, links must be returned not in string format, but as objects.
                        \Bitrix\Main\Type\DateTime
                        \Bitrix\Main\Type\Date
                        \Bitrix\Main\Web\Uri

              Controller

              Controllers is the part of MVC architecture, responsible for query processing and response generation.

              Actions

              Controllers consist of main operational actions, employed by user to get a result. Single controller can have one or several actions. For example, let's create controller with two actions item.add and item.view in the module example. Specifically for this example, controller must be located at /modules/vendor.example/lib/controller/item.php.

              First step - create file .settings.php in the module root.

              <?php
              //modules/vendor.example/.settings.php
              return [
              	'controllers' => [
              		'value' => [
              			'defaultNamespace' => '\\Vendor\\Example\\Controller',
              		],
              		'readonly' => true,
              	]
              ];

              Next, create controller file (see details for available methods here):

              namespace Vendor\Example\Controller;
              
              use \Bitrix\Main\Error;
              
              class Item extends \Bitrix\Main\Engine\Controller
              {
              	public function addAction(array $fields):? array
              	{
              		$item = Item::add($fields);
              
              		if (!$item)
              		{
              			$this->addError(new Error('Could not create item.', {error_code}));
              
              			return null;
              		}
              
              		return $item->toArray();
              	}
              
              	public function viewAction($id):? array
              	{
              		$item = Item::getById($id);
              		if (!$item)
              		{
              			$this->addError(new Error('Could not find item.', {error_code}));
              					
              			return null;
              		} 
              
              		return $item->toArray();
              	}
              }

              First, the add action (using specific method Item::addAction) attempts to create a specific Item passed by $fields.

              Note. Array $fields retrieved by automatic binding of method parameters and $_REQUEST.

              When an activity was failed due to specific reasons, return null and complete the controller by errors. In this case, kernel generates a response:

              {
              	"status": "error", //please note that status has been automatically updated
              	"data": null,
              	"errors": [
              		{
              			"message": "Could not create item.",
              			"code": {code}
              		}
              	]
              }

              Otherwise, add an item and return it as an array $item->toArray() from activity. This array, kernel generates the response:

              {
              	"status": "success",
              	"data": {
              		"ID": 1,
              		"NAME": "Nobody",
              		//...element fields
              	},
              	"errors": null
              }

              Generally, activity can return not only scalars, but objects as well.

              The view activity (using specific method Item::viewAction) first attempts loading a specific Item object by specific passed parameter $id. It's important to note, the $id will be automatically retrieved from $_POST['id'] or $_GET['id'].

              When this parameter is not found, kernel generates response with error:

              {
              	"status": "error",
              	"data": null,
              	"errors": [
              		{
              			"message": "Could not find value for parameter {id}",
              			"code": 0
              		}
              	]
              }
              

              How execute controller action?

              Specific AJAX-action requires action naming convention awareness. In our case: Item::addAction -> vendor:example.item.add Item::viewAction -> vendor:example.item.view.

              vendor:example.item.add, vendor:example.item.view can be used for calling actions using BX.ajax.runAction:

              BX.ajax.runAction('vendor:example.item.add', {
              	data: {
              		fields: {
                          ID: 1,
                          NAME: "test"
                      } 
              	}
              }).then(function (response) {
              	console.log(response);
              	/**
              	{
              		"status": "success", 
              		"data": {
              			"ID": 1,
              			"NAME": "test"
              		}, 
              		"errors": []
              	}
              	**/			
              }, function (response) {
              	//receives all responses with status status !== 'success'
              	console.log(response);
              	/**
              	{
              		"status": "error", 
              		"errors": [...]
              	}
              	**/				
              });

              Or retrieve the link to action and send individual http-query.

              /** @var \Bitrix\Main\Web\Uri $uri **/
              $uri = \Bitrix\Main\Engine\UrlManager::getInstance()->create('vendor:example.item.view', ['id' => 1]);
              echo $uri;
              // /bitrix/services/main/ajax.php?action=vendor:example.item.view&id=1
              // execute GET-query

      Creating controllers and actions

      Creating controllers

      Controllers must be inherited from \Bitrix\Main\Engine\Controller or its descendants. Controllers can be located inside the module, or inside the component in the file ajax.php and be a controller for component.

      Creating actions

      Creating actions means creating methods in a specific controller. Method requires to be public and have an Action suffix.

      namespace Vendor\Example\Controller;
      
      class Item extends \Bitrix\Main\Engine\Controller
      {
      	public function addAction(array $fields)
      	{
              //...
      	}
      }

      Returns action value is response data that can be sent to client.

      If action returns \Bitrix\Main\HttpResponse or its descendants, this object is sent to client. When action returns data, it must be converted to scalar or object format, to be converted to JSON for generating \Bitrix\Main\Engine\Response\AjaxJson.

      Cumulatively, the action can return both scalars but objects as well that implement the following interfaces:

      • \JsonSerializable
      • \Bitrix\Main\Type\Contract\Arrayable
      • \Bitrix\Main\Type\Contract\Jsonable

      Or specific descendant \Bitrix\Main\HttpResponse:

      Creating action-classes

      There is an option to create action classes, inherited from \Bitrix\Main\Engine\Action. This option may be required, when logic must be repeatedly used in several controllers. For example, for implementing the same exchange protocol in different modules (e. g. standard search, executing step-by-step actions with progress and etc.). You need to describe the method configureActions in the controller config map:

      class Test extends \Bitrix\Main\Engine\Controller
      {
      	public function configureActions()
      	{
      		return [
      			'testoPresto' => [
      				'class' => \TestAction::class,
      				'configure' => [
      					'who' => 'Me',
      				],
      			],
      		];
      	}
      }

      Here's the TestAction itself:

      <?php
      
      use \Bitrix\Main\Engine\Action;
      
      class TestAction extends Action
      {
      	protected $who;
      
      	//method for additional config from controller. When requires to set
      	//some values into internal status
      	public function configure($params)
      	{
      		parent::configure($params);
      
      		$this->who = $params['who']?: 'nobody';
      	}
      
      	//main operational method. Parameters are automatically associated the same way as in the method
      	//ajax-action
      	public function run($objectId = null)
      	{
      		return "Test action is here! Do you know object with id {$objectId}? Mr. {$this->who}";
      	}
      }

      To call this action, query testoPresto, as described in the config map. Class action supports pre- and post- filters and are no different from the standard action method. run() method reason is similar to other methods/ajax-actions.

      Using controllers inside components

      Preliminarily create and use controller classes, located in modules, as listed in this article, because this allows to organize better a repeated use of auxiliary code and business logic.

      In simple cases, when component is self-sufficient and is not actively used with module's API, you can use controllers inside the component.

      Controller life cycle

      Application query create controller based on naming convention. Next, controller executes the following work:

      • Controller::init() is called after controller was created and configured.
      • Controller creates an action object
        • When action creating is unsuccessful, throws an exception.
      • Controller requests the parameter preparation method Controller::prepareParams.
      • Controller call the method Controller::processBeforeAction(Action $action); when returns true, the execution continues.
      • Controller throws main module event {full_name_controller_class}::onBeforeAction, when returns EventResult not equal to EventResult::SUCCESS, execution is blocked. Prefilters are executed in this event.
      • Controller triggers the action
        • Action parameters are matched with query data
      • Controller throws the main module event {full_name_controller_class}::onAfterAction. Postfilters are executed in this event.
      • Controller calls the method Controller::processAfterAction(Action $action, $result).
      • Application receives an action result and if result is data, creates \Bitrix\Main\Engine\Response\AjaxJson with this data, or sends response object from action.
      • Application calls the method Controller::finalizeResponse($response), by passing final response variant, which will be sent to user after all events and preparations.
      • Prints $response to user.

      Several namespaces

      Indicating several namespaces in the module.

      You can indicate several namespaces in .settings.php, in addition to defaultNamespace. This can may be necessary, when controllers are located next to their workflow entities. For example, in a "Drive" module we have integration with clouds.

      <?php
      return [
      	'controllers' => [
      		'value' => [
      			'namespaces' => [
      				'\\Bitrix\\Disk\\CloudIntegration\\Controller' => 'cloud', //cloud - is an alias
      			],
      			'defaultNamespace' => '\\Bitrix\\Disk\\Controller',
      		],
      		'readonly' => true,
      	]
      ];

      Now you have call-available controllers, located in both namespaces. Both of them support calling via action full name and via abbreviated record, due to available cloud alias.

      Similar to:

      disk.CloudIntegration.Controller.GoogleFile.get
      disk.cloud.GoogleFile.get
      
      disk.Controller.File.get
      disk.File.get

      Calling modular controller

      Calling modular controller with signed component parameters

      When you need to query the controller, implemented in the module, use the following method from component context by getting signed parameters:

      BX.ajax.runAction('socialnetwork.api.user.stresslevel.get', {
          signedParameters: this.signedParameters, // result $this->getComponent()->getSignedParameters() 
          data: {
              from: myComponentName, // for exmple, 'bitrix:intranet.user.profile', which parameters we require
              fields: {
                  //..
              }
          }
      });

      After this, inside this action code, use:

      <?php
      
          //...
          public function getAction(array $fields)
          {
              //inside is unpacked, checked array with parameters
              $parameters = $this->getUnsignedParameters();
              
              return $parameters['level'] * 100;
          }

      Controllers and component

      You can use the following two approaches for processing AJAX queries in component:

      class.php

      Query handler in component class (class.php file) allows to:

      • Incapsulate complete code in a single class
      • Repeatedly use component methods, data and parameters
      • Use component phrases and templates
      • Re-define standard behaviour in descendant-components

      For the component class to process queries, it's necessary to:

      • Implement the interface \Bitrix\Main\Engine\Contract\Controllerable
      • Define action method with suffix Action
      • Implement the method configureActions (usually returns an empty array === default configuration)
      • When you need to add, process errors, you can implement \Bitrix\Main\Errorable

      Note: Executing component in Ajax mode sequentially executes CBitrixComponent::onIncludeComponentLang, CBitrixComponent::onPrepareComponentParams and launching an action with filters.

      Attention! Executing component in ajax-mode doesn't launch method CBitrixComponent::executeComponent().

      Example

      <?php
      #components/bitrix/example/class.php
      
      use Bitrix\Main\Error;
      use Bitrix\Main\ErrorCollection;
      
      if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
      
      class ExampleComponent extends \CBitrixComponent implements \Bitrix\Main\Engine\Contract\Controllerable, \Bitrix\Main\Errorable
      {
      	/** @var ErrorCollection */
      	protected $errorCollection;
      
      	public function configureActions()
      	{
      		//when actions does not require configuration, write them as is. They will use default config 
      		return [];
      	}
      
      	public function onPrepareComponentParams($arParams)
      	{
      		$this->errorCollection = new ErrorCollection();
      		
      		//parameter preparation
      		//This code **will** be executed upon launching ajax-actions
      	}
      	
      	public function executeComponent()
      	{
      		//Attention! This code **won't be** executed upon launching ajax-actions
      	}
      		
      	//data from REQUEST will be inserted to parameter $person 
      	public function greetAction($person = 'guest')
      	{
      		return "Hi {$person}!";
      	}
      
      	//example of error processing
      	public function showMeYourErrorAction():? string
      	{
      		if (rand(3, 43) === 42)
      		{
      			$this->errorCollection[] = new Error('You are so beautiful or so handsome');
      			//now response will contain errors and automatic 'error' response status. 
      			
      			return  null;					
      		}
      
      		return "Ok";
      	}
      	
      	/**
      	 * Getting array of errors.
      	 * @return Error[]
      	 */
      	public function getErrors()
      	{
      		return $this->errorCollection->toArray();
      	}
      
      	/**
      	 * Getting once error with the necessary code.
      	 * @param string $code Code of error.
      	 * @return Error
      	 */
      	public function getErrorByCode($code)
      	{
      		return $this->errorCollection->getErrorByCode($code);
      	}
      }

      ajax.php

      Query controller handler in the ajax.php file allows creating lightweight ajax-query handler, by directly dedicating logic from component.

      To implement this:

      • Create ajax.php file in the component root
      • Define action method with Action suffix

      The controller logic is fully the same as module controller description.

      <?php
      #components/bitrix/example/ajax.php
      
      if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
      
      class ExampleAjaxController extends \Bitrix\Main\Engine\Controller
      {
      	#data from REQUEST is automatically inserted into the parameter $person 
      	public function sayByeAction($person = 'guest')
      	{
      		return "Goodbye {$person}";
      	}
      
      	public function listUsersAction(array $filter)
      	{
      		$users = [];
      		//user list as per filter
      		//array data composition for response
      		
      		return $users;
      	}
      }

      Practical hints for developers

      For convenient error debugging in AJAX life cycle, enable debug => true in .settings.php, then you will be able to view error and exception traces.

      When required:

      • to pass the file, use the classes \Bitrix\Main\Engine\Response\File and \Bitrix\Main\Engine\Response\BFile:
        class Controller extends Engine\Controller
           {
            public function downloadAction($orderId)
             {
              //... find attached fileId by $orderId
              	return \Bitrix\Main\Engine\Response\BFile::createByFileId($fileId);
             }
            public function downloadGeneratedTemplateAction()
             {
               //... generate file ... $generatedPath
              	return new \Bitrix\Main\Engine\Response\File(
              	   $generatedPath, 
              	   'Test.pdf',
              	   \Bitrix\Main\Web\MimeType::getByFileExtension('pdf')
              	);
             }
        
            public function showImageAction($orderId)
             {
              	//... find attached imageId by $orderId
              	return \Bitrix\Main\Engine\Response\BFile::createByFileId($imageId)
              		->showInline(true)
              	;
              }
           }
        
      • to pass resized image, use \Bitrix\Main\Engine\Response\ResizedImage.

        Be advised, you cannot allow user to request arbitrary dimensions for resizing. Always sign the parameters or directly set dimensions inside code.
        class Controller extends Engine\Controller
           {
            public function showAvatarAction($userId)
              {
                 //... find attached imageId by $userId
              	return \Bitrix\Main\Engine\Response\ResizedImage::createByImageId($imageId, 100, 100);
              }
           }    
        
      • to generate link in controller for the action from the same controller, use \Bitrix\Main\Engine\Controller::getActionUri
        public function getAction(File $file)
          {
            return [
               'file' => [
                  'id' => $file->getId(),
           	  'name' => $file->getName(),
                  'links' => [
                      'rename' => $this->getActionUri('rename', array('fileId' => $file->getId())),
                      ]				
                 ]
             ];
          }    
        
        public function renameAction(File $file)
           {
              ...
           }
        
      • to generate link in controller for an action that will pass content, for example, to download file, then use \Bitrix\Main\Engine\Response\DataType\ContentUri. This is required for integration with REST module.
        public function getAction(File $file)
           {
              return [
                 'file' => [
                    'id' => $file->getId(),
                    'name' => $file->getName(),
                    'links' => [
                         'download' => new ContentUri($this->getActionUri('download', array('fileId' => $file->getId()))),
                                 ]				
                            ]
                       ];
           }    
        
        public function downloadAction(File $file)
           {
             ...
           }
        
      • to convert data SNAKE_CASE as per standard to camelCase, then you can use auxiliary controller methods \Bitrix\Main\Engine\Controller::convertKeysToCamelCase, or direct config \Bitrix\Main\Engine\Response\Converter:
        public function getAction(File $file)
           {
              return [
                  'file' => $this->convertKeysToCamelCase($fileData)
                       ];
           }    
        
        public function showInformationAction(File $file)
           {
              $converter = new \Bitrix\Main\Engine\Response\Converter(Converter::OUTPUT_JSON_FORMAT & ~Converter::RECURSIVE);
            		
              return $converter->process($data);
           }


      Practical advice: interaction with controllers from Javascript

      How to use component parameters in AJAX-action?

      Frequently, AJAX-query must retrieve the same parameters from component that were used for its page display.

      • You need to describe parameters to be used in the method listKeysSignedParameters
              class ExampleComponent extends \CBitrixComponent implements \Bitrix\Main\Engine\Contract\Controllerable
              {
              	protected function listKeysSignedParameters()
              	{
              		//list parameter names to be used in AJAX-actions				
              		return [
              			'STORAGE_ID',
              			'PATH_TO_SOME_ENTITY',
              		];
              	}
      • Get signed template parameters and, for example, pass into your into your component JS class
              <!--template.php-->
              <script type="text/javascript">
              	new BX.ExampleComponent({
              		signedParameters: '<?= $this->getComponent()->getSignedParameters() ?>',
              		componentName: '<?= $this->getComponent()->getName() ?>'
              	});
              </script>
      • Call BX.ajax.runComponentAction (as in examples) with parameter signedParameters.
              BX.ajax.runComponentAction(this.componentName, action, {
              	mode: 'class',
              	signedParameters: this.signedParameters, //here is the way to pass parameters to component.
              	data: data
              }).then(function (response) {
              	//some work
              });

      As the result, your can use the parameters STORAGE_ID, PATH_TO_SOME_ENTITY in your AJAX-action. These parameters are signed and integrity is controlled by kernel.

      When you need to work with signed parameters inside ajax.php, use the method Controller::getUnsignedParameters() inside the controller action; it will contain array with unpacked data.

      Additional information

      Practical advice: page navigation

      You can organize page navigation in AJAX-action by implementing in the method parameters \Bitrix\Main\UI\PageNavigation and return \Bitrix\Main\Engine\Response\DataType\Page.

      Example:

      use \Bitrix\Main\Engine\Response;
      use \Bitrix\Main\UI\PageNavigation;
      
      public function listChildrenAction(Folder $folder, PageNavigation $pageNavigation)
      {
      	$children = $folder->getChildren([
              'limit' => $pageNavigation->getLimit(),
              'offset' => $pageNavigation->getOffset(),
          ]);
      
      	return new Response\DataType\Page('files', $children, function() use ($folder) {
      		//lazy evaluation of total records as per filter		
      		return $folder->countChildren(); 	
      	});
      }

      To pass page number in JS API, take note of navigation.

      BX.ajax.runAction('vendor:someController.listChildren', {
      	data: {
      		folderId: 12 
      	},
      	navigation: {
      		page: 3
      	}
      });
      Attention! Response\DataType\Page($id, $items, $totalCount) $totalCount can be both an integer and \Closure, which can be a lazy evaluation. It's done for improved performance.

      For example, in case of REST, calculation of total records is always required, but for standard AJAX it's optional. More performance and convenient is to use a separate AJAX-action for getting total records as per specific filter.


      Practical advice: integration with REST module

      You can make controllers, programmed inside a module, available for REST module. This is very convenient, because we re-use already written code.

      You need to correct .settings.php module config.

      Important! This is a new method that requires dependency from REST 18.5.1).

      <?php
      return [
      	'controllers' => [
      		'value' => [
      			'defaultNamespace' => '\\Bitrix\\Disk\\Controller',
      			'restIntegration' => [
      				'enabled' => true,
      			],
      		],
      		'readonly' => true,
      	]
      ];

      How to use \CRestServer in AJAX-action

      In case AJAX-action must use \CRestServer for a specific task, it can be done by declaring \CRestServer as one of parameters.

      Example:

      public function getStorageForAppAction($clientName, \CRestServer $restServer)
      {
      	$clientId = $restServer->getClientId();
      	...
      }
      

      Please, be advised, the example above cannot work via standard AJAX, because \CRestServer $restServer is not available in it and cannot be implemented. It can be available only for the REST module. If you declare it as optional, everything will work.

      public function getStorageForAppAction($clientName, \CRestServer $restServer = null)
      {
      	if ($restServer)
      	{
      		$clientId = $restServer->getClientId();
      	}
      	...
      }
      

      How to understand, if actions are called in REST or AJAX environment?

      It can happen that you need to distinguish in which context the action is presently executed: in REST or AJAX? You need to ask the controller:

      \Bitrix\Main\Engine\Controller::getScope()
      
      //possible variants
      \Bitrix\Main\Engine\Controller::SCOPE_REST
      \Bitrix\Main\Engine\Controller::SCOPE_AJAX
      \Bitrix\Main\Engine\Controller::SCOPE_CLI
      


      Practical advice: dependency integration

      Scalar and nonscalar parameters

      Let's overview example of AJAX-action with parameters:

      public function renameUserAction($userId, $newName = 'guest', array $groups = array(2))
      {
      	$user = User::getById($userId);
      	...
      	$user->rename($newName);
      	
      	return $user;
      }
      

      How to retrieve method parameters?

      Scalar parameters $userId, $newName, $groups will be retrieved automatically from REQUEST.

      • Parameter association is case-sensitive.
      • When search is unsuccessful, uses default value, if available.
      • First searches in $_POST, then in $_GET.

      Failed search without matching results means that action won't be launched; server will send response with error message informing about missing parameter.

      How to integrate objects (nonscalar parameters)?

      By default, you can integrate:

      The parameter name can be arbitrary. Uses class for association:

      public function listChildrenAction(Folder $folder, PageNavigation $pageNavigation);
      public function listChildrenAction(Folder $folder, PageNavigation $navigation);
      public function listChildrenAction(Folder $folder, PageNavigation $nav, \CRestServer $restServer);
      

      Integrating custom types

      Let's start with example:

      class Folder extends Controller
      {
      	public function renameAction($folderId)
      	{
      		$folder = Folder::getById($folderId);
      		if (!$folder)
      		{
      			return null;
      		}
      		...
      	}
      	
      	public function downloadAction($folderId)
      	{
      		$folder = Folder::getById($folderId);
      		...
      	}
      	
      	public function deleteAction($folderId)
      	{
      		$folder = Folder::getById($folderId);
      		...
      	}
      }
      

      We have a standard AJAX-controller for a specific Folder. However, all actions are eventually performed for an object, with an attempt to load folder and etc. It's preferable to receive Folder $folder on input.

      class Folder extends Controller
      {
      	public function renameAction(Folder $folder);
      	public function downloadAction(Folder $folder);
      	public function deleteAction(Folder $folder);
      }
      

      And now it is possible:

      class Folder extends Controller
      {
      	public function getPrimaryAutoWiredParameter()
      	{
      		return new ExactParameter(
      			Folder::class, //full name for subclass to be created 
      			'folder', //specific parameter name to be integrated
      			function($className, $id){ //function that creates object for integration. Retrieves specific class and $id
      				return Folder::loadById($id);
      			}
      		);
      	}
      }
      

      In JS call:

      BX.ajax.runAction('folder.rename', {
      	data: {
      		id: 1 
      	}
      });
      

      It's important that closure after $className can indicate unlimited number of parameters needed for created object. Such parameters will be associated with data from $_REQUEST in the same way as scalars in standard action methods.

      class Folder extends Controller
      {
      	public function getPrimaryAutoWiredParameter()
      	{
      		return new ExactParameter(
      			Folder::class, 
      			'folder',
      			function($className, $entityId, $entityType){
      				return $className::buildByEntity($entityId, $entityType);
      			}
      		);
      	}
      	
      	public function workAction(Folder $folder);
      }
      

      In JS call:

      BX.ajax.runAction('folder.work', {
      	data: {
      		entityId: 1,
      		entityType: 'folder-type'
      	}
      });
      

      When you need to describe several parameters to be created:

      class Folder extends Controller
      {
      	/**
      	 * @return Parameter[]
      	 */
      	public function getAutoWiredParameters()
      	{
      		return [
      			new ExactParameter(
      				Folder::class, 
      				'folder',
      				function($className, $id){
      					return $className::loadById($id);
      				}
      			),
      			new ExactParameter(
      				File::class, 
      				'file',
      				function($className, $fileId){
      					return $className::loadById($fileId);
      				}
      			),
      		];
      	}
      	
      	public function workAction(Folder $folder, File $file);
      }
      

      There is one more general method for describing the integration:

      new \Bitrix\Main\Engine\AutoWire\Parameter(
      	Folder::class, 
      	function($className, $mappedId){
      		return $className::buildById($mappedId);
      	}
      );
      

      In detail: first, we have declared class name which subclasses we will attempt to create when encounter them in AJAX-actions. Anonymous function will created an action instance.

      • $className - specific class name, specified type-hinting.
      • $mappedId - value, retrieved from $_REQUEST. Accordingly, searches folderId in this $_REQUEST. Parameter name that we will search in $_REQUEST by default will be created as {variable name} + Id.
      •       Folder $folder   => folderId
              Folder $nene     => neneId
              File $targetFile => targetFileId 
        

        As the result, you can described the type, if the module has class, for example, Model, from which all entities are inherited:

        new \Bitrix\Main\Engine\AutoWire\Parameter(
        	Model::class, 
        	function($className, $mappedId){
        		/** @var Model $className */
        		return $className::buildById($mappedId);
        	}
        );
        

        Subsequently, you can easily use type-hinting in your AJAX-actions, directly handling the entities.


        Routing

        Available starting from the Main version 21.400.0. Presently, Bitrix24 doesn't support custom modules employing their own routes in module folder.

          Launch

        To launch a new system of routing, you need to forward processing of 404 errors to the file routing_index.php inside the file .htaccess:

        #RewriteCond %{REQUEST_FILENAME} !/bitrix/urlrewrite.php$
        #RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L]
        
        RewriteCond %{REQUEST_FILENAME} !/bitrix/routing_index.php$
        RewriteRule ^(.*)$ /bitrix/routing_index.php [L]

        Note: Starting from version 23.500.0, the router is connected via the method \Bitrix\Main\Application::getRouter().

          Configuration

        Files with route configuration are located in the folders /bitrix/routes/ and /local/routes/. Connected file must be described in its [ds].settings.php[/ds][di]Bitrix Framework has several specific kernel settings without visual edit UI. This is done to prevent errors and setting update easily causing system crashes (database connection settings, caching settings and etc.).

        Learn more ...[/di] in routing section:

        'routing' => ['value' => [
          'config' => ['web.php', 'api.php']
        ]], 
        
        // connects the following files:
        // /bitrix/routes/web.php, /local/routes/web.php,  
        // /bitrix/routes/api.php, /local/routes/api.php
        

        File format provides for returned closure, passed to the routing config object:

        <?php
        
        use Bitrix\Main\Routing\RoutingConfigurator;
        
        return function (RoutingConfigurator $routes) {
            // routes
        };

        Searches for matches in the same sequence, as described routes in the config.


        Routes

          Queries

        Route description starts from defining a query method. There are 3 combinations of methods available:

        $routes->get('/countries', function () {
            // triggered only by GET query
        });
        
        $routes->post('/countries', function () {
            // triggered only by POST query
        });
        
        $routes->any('/countries', function () {
            // triggered only by any query type
        });

        Use the method methods for specifying arbitrary set of methods:

        $routes->any('/countries', function () {
            // triggers any type of query type
        })->methods(['GET', 'POST', 'OPTIONS']);

          Parameters

        Use curly brackets to define parameter in the address:

        $routes->get('/countries/{country}', function ($country) {
            return "country {$country} response";
        });

        By default, [^/]+ pattern is used for parameters. The where route method is used for indicating your own criteria:

        $routes->get('/countries/{country}', function ($country) {
            return "country {$country} response";
        })->where('country', '[a-zA-Z]+');

        If parameter value can contain /, use the pattern .*:

        $routes->get('/search/{search}', function ($search) {
            return "search {$search} response";
        })->where('search', '.*'); 

        Parameters can have default values; in this case their presence is optional in the address:

        $routes->get('/countries/{country}', function ($country) {
            return "country {$country} response";
        })->default('country', 'Australia');
        
        // route will be selected when querying /countries/
        // the country parameter will have a specified value

        For convenience, you can set parameters that don't participate in generating the address at all:

        $this->routes->get('/countries/hidden', function ($viewMode) {
            return 'countries response {$viewMode}';
        })->default('viewMode', 'custom');

        Access to route parameter values is gained via controller parameters or current route object:

        $routes->get('/countries/{country}', function ($country) {
            return "country {$country} response";
        });
        
        ...
        
        $app = \Bitrix\Main\Application::getInstance(); 
        $country = $app->getCurrentRoute()->getParameterValue('country');    

          Names

        Assign this route a unique identifier - a name - for convenience and route lists systematization:

        $routes->get('/path/with/name', function () {
            return 'path with name';
        })->name('some_name');

        Subsequently, this will allow to query the route when generating links.

          Controllers

        Routing supports several types of controllers:

        1. Controllers Bitrix\Main\Engine\Controller:
          $routes->get('/countries', [SomeController::class, 'view']);
          
               // launches action SomeController::viewAction()
        2. Separate controller actions Bitrix\Main\Engine\Contract\RoutableAction:
          $routes->get('/countries', SomeAction::class);
        3. Closures:
          $routes->get('/countries/', function () {
                   return "countries response";
               });

          Arguments can be a query object Bitrix\Main\HttpRequest, current route object Bitrix\Main\Routing\Route, as well as route named parameters in any combinations:

          use Bitrix\Main\HttpRequest;
          use Bitrix\Main\Routing\Route;
          
          $routes->get('/countries/{country}', function ($country, HttpRequest $request) {
              return "country {$country} response";
          });
          
          $routes->get('/countries/{country}', function (Route $route) {
              return "country {$route->getParameterValue('country')} response";
          });
        4. The class Bitrix\Main\Routing\Controllers\PublicPageController is available for purposes of backward compatibility with public pages:
           $routes->get('/countries/', new PublicPageController('/countries.php'));

        Groups

          Merging in groups

        In case of similar attributes for several routes, it's recommended to merge them into groups:

        $routes->group(function (RoutingConfigurator $routes) {
            $routes->get('/path1, function () {});
            $routes->get('/path2, function () {});
            $routes->get('/path3, function () {});
        });

        Merging itself doesn't affect the system behaviour and is useful in case of shared attributes: parameters, prefix or name, that will be overviewed below.

          Group parameters

        When several routes have a shared parameter, it makes sense to move it up to a group level. This will allow avoid describing this parameter separately for each route:

        $routes
            ->where('serviceCode', '[a-z0-9]+')
            ->group(function (RoutingConfigurator $routes) {
                $routes->get('/{serviceCode}/info', [ServicesController::class, 'info']);
                $routes->get('/{serviceCode}/stats', [ServicesController::class, 'stats']);
        });

          Group prefix

        If the address start matches for several routes, move it as general for the group:

        $routes->prefix('about')->group(function (RoutingConfigurator $routes) {
            $routes->get('company', function () {});
            $routes->get('personal', function () {});
            $routes->get('contact', function () {});
        }); 

        The example above shows route addresses as /about/company, /about/personal and /about/contact, to avoid duplicating the general portion.

          Group name

        General portion of route name is generated in the similar manner as the prefix:

        $routes
            ->prefix('about')
            ->name('about_')
            ->group(function (RoutingConfigurator $routes) {
                $routes->name('company')->get('company', function () {});
                $routes->name('personal')->get('personal', function () {});
                $routes->name('contact')->get('contact', function () {});
            })
        ;

        The example above shows route names as about_company, about_personal and about_contact.


        Generating links

          Routes with name

        When describing a route, set a unique name for it:

        $routes->get('/countries/{country}', function () {
            return 'some output';
        })->name('country_detail');

        And use this name for generating a link:

        $router = \Bitrix\Main\Application::getInstance()->getRouter();
        $url = $router->route('country_detail', ['country' => 'Australia']);
        
        // $url: /countries/Australia

        Names act as unique identifiers. When link format must be changed, i. e. its static portion:

        - $routes->get('/countries/{country}', function () {
        + $routes->get('/countries/{country}', function () {
            return 'some output';
        })->name('country_detail');

        In this case, you don't have to change all links to this route, because they use specifically name for forwarding.

          Routes without name

        When unique name is not specified for the route, its permissible to indicate its address in the link. Helper \Bitrix\Main\Routing\Router::url() may be useful for an available GET parameters:

        $country = 'Australia';
        $router = \Bitrix\Main\Application::getInstance()->getRouter();
        $url = $router->url("/contries/{$country}", [
            'showMoreDetails' => 1
        ]);
        
        // $url: /contries/Australia?showMoreDetails=1

        Loggers

          Introducton

        Core now have available loggers, implementing the PSR-3 interface:

        • base abstract class \Bitrix\Main\Diag\Logger, implementing the PSR-3 interface;
        • file logger: \Bitrix\Main\Diag\FileLogger;
        • syslog logger: \Bitrix\Main\Diag\SysLogger.

        Loggers are used by the log formatter \Bitrix\Main\Diag\LogFormatter that replaces placeholders as per PSR-3.

        Note: Library is available starting from main version 21.900.0.

          Logger Interface

        \Psr\Log\LoggerInterface interface is quite simple, representing a set of logging features that support levels of logging. Levels are set by constants \Psr\Log\LogLevel::*.

        interface LoggerInterface
        {
            public function emergency($message, array $context = array());
            public function alert($message, array $context = array());
            public function critical($message, array $context = array());
            public function error($message, array $context = array());
            public function warning($message, array $context = array());
            public function notice($message, array $context = array());
            public function info($message, array $context = array());
            public function debug($message, array $context = array());
            public function log($level, $message, array $context = array());
        }

        Message can contain {placeholders}, replaced by data from the $context associative array.

        Also, a useful interface \Psr\Log\LoggerAwareInterface is available if you want to notify that your object is ready to accept the PSR-3 logger:

        interface LoggerAwareInterface
        {
            public function setLogger(LoggerInterface $logger);
        }

          Loggers in Bitrix24

        While implementing PSR-3, loggers in Bitrix24 have expanded functionality. Now you can:

        • set a minimum logging level, below which logger doesn't show anything,
        • set formatter.

        File logger \Bitrix\Main\Diag\FileLogger can write messages into file, specified in constructor. When log size exceeds the set maximum, system performs a single-time log file rotation. Null - no rotation is needed. Default size: 1 Mb.

        $logger = new Diag\FileLogger($logFile, $maxLogSize);
        $logget->setLevel(\Psr\Log\LogLevel::ERROR);
        // prints into log
        $logger->error($message, $context);
        // Doesn't print into log
        $logger->debug($message, $context);

        Syslog logger \Bitrix\Main\Diag\SysLogger is an addin to the function php syslog function. Constructor receives parameters, used by the function openlog.

        $logger = new Diag\SysLogger('Bitrix WAF', LOG_ODELAY, $facility);
        $logger->warning($message);

        File logger uses the function AddMessage2Log and class \Bitrix\Main\Diag\FileExceptionHandlerLog, as well as logging in the Proactive protection module (security).

        Starting from version 23.500.0 you can define the following in the settings .settings.php.

        • specify logger for AddMessage2Log from version 23.500.0;
        • [ICO_NEW data-adding-timestamp="1703579666"]
        • redefine default logger for the File converter (transformer) module. Logger ID – transformer.Default. Starting from transformer module version 23.0.0.
        • [/ICO_NEW]

          Message formatting

        Message formatter can be set into the logger. By default, uses the formatter \Bitrix\Main\Diag\LogFormatter, implementing the interface \Bitrix\Main\Diag\LogFormatterInterface:

        interface LogFormatterInterface
        {
        	public function format($message, array $context = []): string;
        }

        Formatter constructor receives parameters $showArguments = false, $argMaxChars = 30 (show argument values in trace, maximum argument length).

        $logger = new Main\Diag\FileLogger(LOG_FILENAME, 0);
        $formatter = new Main\Diag\LogFormatter($showArgs);
        $logger->setFormatter($formatter);

        Formatter main task is to insert values into message placeholders from context array. Formatter can process specific placeholders:

        • {date} - current time * ;
        • {host} - HTTP host * ;
        • {exception} - exception object (\Throwable);
        • {trace} - backtrace array;
        • {delimiter} - message delimiter * .

        * - optional to pass in context array, substituted automatically.

        $logger->debug(
            "{date} - {host}\n{trace}{delimiter}\n", 
            [
                'trace' => Diag\Helper::getBackTrace(6, DEBUG_BACKTRACE_IGNORE_ARGS, 3)
            ]
        );

        Formatter formats values from context array into convenient format depending on the value type. Accepts strings, arrays, objects.

        Use

        Standard object format can get a logger that supports the interface \Psr\Log\LoggerAwareInterface. Can use a corresponding trait:

        use Bitrix\Main\Diag;
        use Psr\Log;
        
        class MyClass implements Log\LoggerAwareInterface
        {
        	use Log\LoggerAwareTrait;
        	
        	public function doSomething()
        	{
        	    if ($this->logger)
        	    {
        	        $this->logger->error('Error!');
        	    }
        	}
        }
        
        $object = new MyClass();
        $logger = new Diag\FileLogger("/var/log/php/error.log");
        $object->setLogger($logger);
        $object->doSomething();

        However, it's not convenient to change the code at the operational project to pass logger a desired object. Logger class provides an individual factory for this. The factory receives a logger string ID:

        use Bitrix\Main\Diag;
        use Psr\Log;
        
        class MyClass implements Log\LoggerAwareInterface
        {
        	use Log\LoggerAwareTrait;
        	
        	public function doSomething()
        	{
        	    if ($logger = $this->getLogger())
        	    {
        	        $logger->error('Error!');
        	    }
        	}
        
        	protected function getLogger()
        	{
        		if ($this->logger === null)
        		{
        			$logger = Diag\Logger::create('myClassLogger', [$this]);
        			$this->setLogger($logger);
        		}
        
        		return $this->logger;
        	}
        }

        Configuration

        Root section for the .settings.php file indicates loggers in the loggers key. Description syntax matches with ServiceLocator settings. The difference is that service locator is a register and this file contains the factory config.

        Additional parameters can be passed to constructor closure via the Diag\Logger::create('logger.id', [$this]) factory's second parameter. Parameters allows to flexibly enable logging depending on passed parameters, including calling the methods of the object itself.

        Additionally, you can indicate minimal level of logging (level) and formatter. Formatter is searched in service locator by its ID.

        // /bitrix/.settings.php
        return [
        	//...
        	'services' => [
        		'value' => [
        			//...
        			'formatter.Arguments' => [
        				'className' => '\Bitrix\Main\Diag\LogFormatter',
        				'constructorParams' => [true],
        			],
        		],
        		'readonly' => true,
        	]
        	'loggers' => [
        		'value' => [
        			//...
        			'main.HttpClient' => [
        //				'className' => '\\Bitrix\\Main\\Diag\\FileLogger',
        //				'constructorParams' => ['/home/bitrix/www/log.txt'],
        //				'constructorParams' => function () { return ['/home/bitrix/www/log.txt']; },
        				'constructor' => function (\Bitrix\Main\Web\HttpClient $http, $method, $url) { 
        					$http->setDebugLevel(\Bitrix\Main\Web\HttpDebug::ALL);
        					return new \Bitrix\Main\Diag\FileLogger('/home/bitrix/www/log.txt');
        				},
        				'level' => \Psr\Log\LogLevel::DEBUG,
        				'formatter' => 'formatter.Arguments',
        			],
        		],
        		'readonly' => true,
        	],
        	//...
        ];

        Upon indicating closing constructor, its preferable to use the .settings_extra.php file, to avoid loosing code when saving settings from API.

        There is \Psr\Log\NullLogger that can be installed to avoid writing if($logger) each time before calling logger. However, you need to consider, if extra message and context formatting work is worth it.

          Classes

        List of classes supporting logger factory:

        ClassLogger IDPassed parameters
        \Bitrix\Main\Web\HttpClient main.HttpClient [$this, $this->queryMethod, $this->effectiveUrl]

        Design Integration

        Note. Portal templates are system templates and cannot be customized! There is a technical possibility to copy, customize, and apply the template, but in this case the backward compatibility will be lost.

        The following aspects are reviewed in this section:

        • Site design template management;
        • Work with include areas and advertising areas;
        • Site navigation tools management: menu and navigation chain;
        • Main product localization principles;
        • Work with visual components;
        • Site optimization.

        The minimum requirements for study: knowledge of the basic techniques for website development, such as HTML, CSS, and PHP.

        Web design – it is first of all the development of the interface, as the user’s interaction environment with information, and not just a “beautiful picture”. Specifics of the web environment must be taken into account along with such things as usability and fit for purpose of creating a site. In addition, due regard must be given to the main scenarios of a user’s behavior and the particulars of the target audience.

        While working with the design of your own site you must remember that any work with site design has its own preferences. If you want to make any changes to the design, do the following:

        • First try to do it by editing the template of the site itself and CSS files;
        • If the previous step is impossible, then try to do it using site page editing means;
        • And only in the last resort start editing the component templates and files of the CSS component. In this case, templates must be copied and not edited in a system folder.

        This sequence of actions is required because during product update component templates are also updated. If you have changed (customized) a component template, it will not be updated. In this case, the functionality loss of the updated version and decrease in the security level are possible.

        Attention! If you have activated component cache, you might not notice the changes after introducing them to component templates or its CSS file. It is about caching itself, which shows you not a real view but a cached one from previous sessions. In this case, you only have to update the component cache by using the Update button on the administrative panel.

        Using Access Rights

        Quite often, when creating a site template, access to certain elements must be limited. An access right verification mechanism included in the system can be used while creating a site template for the following purposes:

        • To Control Menu Option View

          When editing the menu in an extended mode, a viewing condition may be set for each menu option. For example:

        • To control the menu template

          The level of users’ access rights may affect the menu template structure, elements, and images used, etc. An example of the verification of a user’s access right level for a menu template is provided below:

          <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
          
          <?if (!empty($arResult)):?>
          <div class="blue-tabs-menu">
                 <ul>
          <?foreach($arResult as $arItem):?>
          
                 <?if ($arItem["PERMISSION"] > "D"):?>
                        <li><a href="<?=$arItem["LINK"]?>"><nobr><?=$arItem["TEXT"]?></nobr></a></li>
                 <?endif?>
          
          <?endforeach?>
          
                 </ul>
          </div>
          <div class="menu-clear-left"></div>
          <?endif?>
          Important! The conditions that include the verification of a value of the $PERMISSION variable are used only for the site menu.

        • To control site template

          A condition of use of a design template can be set up for each design template. The setup shall be made on the site parameter control page (Control Panel > Settings > System settings > Websites > Websites). E.g.:

          The most flexible tool to setup display conditions is the PHP Condition. Examples of php conditions to display the site template:

          $USER->IsAuthorized()Checks if a current user is authorized in the system.
          $USER->IsAdmin()Checks if a current user is the administrator.
          in_array('5',$USER-> GetUserGroupArray())Checks if a current user belongs to the specified group (in this case to a group with the ID equal to 5).

        • To control design template elements

          Control of display of site template elements, their form, color, and other parameters may also be effected based on the level of access rights of site users. For more details, please refer to the lesson Design Template Development.

        • Control of specific elements of the site

          The use of the access rights check feature permits to organize control of specific site elements (pages, sections, advertising, fora, etc.) by different users. Please see the relevant section of the course System administration.

        Site Design Template

        The information of this section gives an idea about the structure, creation technique, and use of the site design templates. In addition, this section describes the possibility to use templates for working and editing areas of a site page.

        Design template is an exterior appearance of a site determining layout of different elements on the site along with page art style and display mode. It includes programming html and php codes, graphic elements, style sheets, and additional files for contents display. It can also include component templates, ready page templates, and snippets.

        The use of templates makes it possible to perform the flexible design setup of sites, sections, and webpages. E.g., it is possible to apply a special festival design during a certain time, automatic control of the site exterior depending on visitor group, etc.

        The site may have many design templates and, vice versa, one template may be used in several sites.

        Design Template

        The site template is a group of PHP and CSS files (<file_name>.css). Templates are used for public section layout. Site template defines:

        • site design (layout, using sets of CSS and etc.);
        • menu types;
        • advertising areas;
        • include and editable areas;
        • occurrence of authorization or subscription forms in site design, etc.

        All templates are stored in directory /bitrix/templates/. Files that comprise the template are stored in subdirectory named by the template identifier (for example, /bitrix/templates/demo/ or /bitrix/templates/template1/).

        A common site design usually includes three main parts:

        • (1) Topmost section (header);
        • (2) Main section that is used to display the site presentation and information content, components or the code;
        • (3) Bottom section (footer).

        Header - is the top part of the design. As a rule, it includes the top and left part of the design with static information (logo, slogan, etc.), top horizontal menu, and left menu (if they are stipulated by design). Dynamic informational materials may be included. It is stored in a separate file .../<template_identifier>/header.php.

        Work area - is the page working area where the proper informational materials of the site are located. Work area comprises all the documents created by users that are stored in the files <document_name>.php in the relevant site folders.

        Footer - is the bottom part of the design containing static information (as a rule: contact details, information about author and owner of the site, etc.), low horizontal menu, and right menu (if they are stipulated by design). Informational materials may be included. It is stored in a separate file .../<template_identifier>/footer.php.

        Connection of Design Parts

        Assembling of typical page is implemented by uniting header and footer parts of the site design with work (main) area. In the general case a site page has the following structure:

        <?
        require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
        $APPLICATION->SetTitle("Bitrix Site Manager");
        ?>
        
        Main part
        
        <?
        require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
        ?>

        Note: In addition to static information, the following items may be located in the template and work area:
        • Visual components;
        • Include areas;
        • Arbitrary PHP code.

        These site elements are intended for the display of dynamic information.

        Site Template File Structure

        Example of the general file and folder structure of a site template:

        • The components catalogue is intended for component templates;
        • The images catalogue is intended for template images (that do not depend on the browsable page) and copied from site layout;
        • The include_areas catalogue contains the areas of a template;
        • The lang catalogue contains language communication files;
        • The page_templates catalogue is intended for templates of pages and editable areas;
        • The snippets catalogue contains snippets – small fragments of html code intended to speed up the operation of the content manager associated with creation of frequent code blocks;
        • The themes catalogue is a template theme;
        • The header.php file is a part of the template BEFORE the contents;
        • The footer.php file is a part of the template AFTER the contents;
        • The description.php file is a name and description of the template;
        • The .styles.php files contains a description of styles for the visual page editor;
        • The template_styles.css file contains template styles (styles applied in the site design template itself);
        • The styles.css file contains styles for the contents and include areas. These styles may be used in visual editor.

        Site template developing

        The site template developing process consists of two basis stages:

        • the template prototype developing;
        • the full-functional template creation.

        The template prototype developing

        The prototype is a site template in HTML format. During the page makeup there are defined all functional areas in future site template. For example:

        • page title;
        • menu;
        • navigation chain;
        • authorization form;
        • subscription form;
        • advertising areas;
        • etc.

        The full-functional template creation

        At this stage, the HTML design elements are replaced with appropriate functional elements: source code and component calls. As a result, a PHP template of the site design is obtained.

        Note: When creating a site template, various program conditions may be used that affect the display of template elements for different site sections. For this, a certain property must be determined for a site section the value of which will be checked in the site template:
        <?if ($APPLICATION->GetProperty(“SECT_PROP”)=="Y"):?>
        

        Similar conditions may be entered for any template elements. E.g., deactivation of the display of include areas and the control of navigation chain display, etc.

        Recommendations for Template Creation

        The main aspects to be taken into account while creating a template:

        • When preparing graphic design, a design dividing line should be marked in advance for the prologue (header.php) and epilogue (footer.php).
        • The main design elements should be singled out for subsequent modification of the style sheets: fonts, fill colors, etc.
        • When developing the design of the menu of different levels, it is recommended to mark repeated elements in order to simplify menu template creation and for the further control of these menus.
        • The use of text elements instead of graphics is recommended to facilitate the support of different language versions of the site.
        • During the development of graphic design and the HTML template it is necessary to provide for the location of the main components of the site control system and to allocate menu areas, advertising areas, and areas for additional forms.
        • The template should be prepared taking into account subsequent table assembly. Layers may be used simultaneously.
        • Solid areas must be marked during preparation of graphic design. When assembling a template, these areas may be represented by table cells with solid color fill.
        • Graphic images associated with template should be located in the folder /bitrix/templates//images.
        • Site design template will be edited correctly in a visual mode, if HTML tag attributes do not contain php code and also, for example, if table lines and cells are not interrupted with php code while a table is formed. If the site design template code has such particulars, it should be edited only in the code mode.
        • Cascading style sheets used in the template should be divided into two style tables stored in two different files. Two files shall be located in the directory /bitrix/templates//. The file styles.css contains styles to represent the information contents of the page on the site. The file template_styles.css contains styles to display texts in the design template itself.

          If necessary, any number of style files may be connected to <head> in addition to styles.css and template_styles.css connected through showhead(). It should be done using regular links before closing the tag </head> additional style files can be placed in any folder. The result will be the same as if you had collected all your additional styles and written them in two files of the site template with standard names.

        The site template should be created on a local demo version of the product. A ready-made template must be exported using system means as a set of files in tar.gz format into a remote server and then deployed.

        Note: The use of complex components in the design template is strongly discouraged because in this case the rules of address rewriting become applicable to the entire site. It may affect the operation of computer numerical control of other components and page /404.php. Complex components must be located in #WORKAREA.

        Attention! Be very careful using visual editor to edit site template since unforeseen code distortions may occur.

        As a rule, a site requires several templates to be developed: for a home page, a section, a detailed page, etc. If there are very insignificant differences among the templates, the use of one template is preferable:

        • Either with an incorporated check verifying which page is currently open,
        • Or together with include areas.

        Experience has shown that this solution is by far cheaper in terms of site support.


        Site template management

        Templates for the Bitrix Site Manager can be loaded in the system in <template_name>.tar.gz. format. Also the site template can be added by copying the template folder to the system.

        The system templates management is implemented in the administrative section: Settings > System settings > Sites > Site templates

        There you can view, edit and upload the existing templates and also add new templates.

        The possibility to view templates layout directly in the template list is realized by means of template capture using. The template capture should be located in the corresponding template folder with the screen.gif name (for example, /bitrix/templates/demo/screen.gif).

        Template editing

        To view or modify a template code use the Edit item in the template context menu.

        Note: Template editing may also be accessed from the public part of the site. For this, use the option Edit template of the menu of the Site template button in the administrative panel:

        The field Site template design contains the template code.

        Click to enlarge

        Design template may be edited using a visual editor or working with a source code. The latter option is preferable.

        The possibility of the visual editing of a template is determined by Kernel module settings (option Use WYSIWYG editor for Site Templates).

        If this option is selected in the template editing form the option Use visual editor will become available.

        Attention! Site design template will be edited correctly in a visual mode, if HTML tag attributes do not contain php code and also, for example, if table lines and cells are not interrupted with php code while a table is formed. If site design template code has such particulars, it should be edited only in the code mode. In addition, template editing is not recommended in case of a complex layout.

        When editing a template in a visual editor, consolidated top and bottom parts of the site design are displayed. Components and functions written in the PHP programming language are inserted into HTML code and ensure the display of various types of information: metadata, page header, cascading style sheets, administrative panel, and subsequent control of this information using visual tools.

        Attention! The use of composite components in site design template is strongly discouraged. Please note that #WORK_AREA# separator is available in the template code which is used to indicate the boundary between the top and bottom part of the design. Here, the work area of the site page will be connected. In the visual mode of the template editing work area is indicated with . The template cannot be saved without this separator.


        Template export

        The special system interface features allow to export templates used in the system in the <file_name>.tar.gz format. The Downoadcontext menu item is used.

         


        Template import

        A ready-made template must be exported as a set of files using file manager or special interface of the system. A special Load template button of the context panel is located on the page containing the list of templates.

        If we click the button, the following form opens:

        • Specify the file containing template for upload using the Browse… button.
        • In case of upload by default the template will be unpacked and placed into a folder with a name that corresponds to the name of the uploaded file (/bitrix/templates/<template_identifier>/). For example, if the name of the uploaded file is template1.tar.gz, the template will be automatically placed into the folder .../template1/, and the identifier (ID) template1 will be assigned to the template itself.

        • In order to assign another identifier to a template and have the template itself placed in a folder with the appropriate name, the necessary template code should be indicated in the Template code field.
        • Also, an uploaded template may be linked as a default template for the selected site using the relevant option.

        Template creation

        A new site template can be created directly in the system with use of the New template form. To go to this form use the Add template button located on the page context panel.

        Creating the template via the system interface you can:

        • assign template ID (must be unique);
        • assign template name;
        • input template description for showing in template list;
        • input template source code;
        • assign used in template CSS:

          • The bookmark Site styles serves for description of cascading style sheets (CSS) used on the site pages. Style description is stored in the file styles.css in the site template folder.
          • The bookmark Template styles serves for description of cascading style sheets (CSS) used in the template. Style description is stored in the file template_styles.css in the site template folder.
        • define set of used in template pictures and include areas.

        The new template is stored in the /bitrix/templates/<template_ID> directory (this directory is created automatically).

        It is recommended to store all graphic files used in the template in the /bitrix/templates/<template_ID>/images/ directory.

        Note: It is recommended to deactivate caching during template creation.

        Page templates developing

        The Bitrix Framework allows creating and utilizing templates for work (main) and include page areas. Templates utilizing makes work with page having a complex structure (layout) easier and faster.

        All page and include area templates are stored in the /page_templates/ directory located in the corresponding template folder or in the folder .default (if these page templates are used for all site templates).

        Creating a new page in HTML editor mode you can just choose necessary page template in the list and than add page content.

        List of available page templates is created with use of .content.php file. This file is also stored in the /page_templates/ directory in the corresponding site template folder. This file contains associative array of page templates and their names intended for displaying in the list.

        <?
        if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
        $TEMPLATE["standard.php"] = Array("name"=>"Standard page (Default template)", "sort"=>1);
        $TEMPLATE["page_inc.php"] = Array("name"=>"Include area for page", "sort"=>2);
        $TEMPLATE["sect_inc.php"] = Array("name"=>"Include area for section", "sort"=>3);
        ?>
        

        Using message files

        The Bitrix Framework allows to use one site template for several sites on different languages. This possibility is realized by means of message files usage:

        • in the template HTML prototype are defined areas intended for text displaying (for example, titles, ALT attributes, controls text etc.);
        • then in these areas should be placed special source code that calls language files containing necessary text messages on corresponding language.

        More detailed information on language files usage is given in the Message files section.

        Examples of work and problem solutions

        Simple examples of template application depending on various conditions

        If the phone section property is equal to Y

        $APPLICATION->GetDirProperty("phone")=="Y"

        If the current section is equal to /en/catalog/phone/

        $APPLICATION->GetCurDir()=="/en/catalog/phone/"

        If a current user is the administrator

        $USER->IsAdmin()

        If the template must be linked to a dynamic page (in the example, to a detailed page (card) of the goods)

        preg_match("#/catalog/\?SECTION_ID=\d+&ELEMENT_ID=\d+#i",$_SERVER['REQUEST_URI']);

        Connection of Favicon.ico for Various Templates

        In order to have different symbols in different site templates a call of a symbol from the template must be added to header.php of the template:

        <link rel="icon" type="image/x-icon"
        href="<?=SITE_TEMPLATE_PATH;?>/images/favicon.ico" />
        

        Separate Template for the Home Page of the Site

        In addition to the main template for all site pages, a required template must be connected through the condition For folder or file indicating /index.php and taking into account the application sorting index of the template.


        Change of the Site Template Depending on the Time Parameter

        Task: How to implement a site template change depending on the time parameter (two templates in total, but they must be changed every hour)?

        For this, the current template must be changed according to the condition PHP expression.

        First Option

        On odd hours: ((date("H")+6)%2) == 1
        On even hours: ((date("H")+6)%2) == 0

        where +6 indicates the time zone.

        Second Option

        date("H")%2
        or
        !date("H")%2



        Application of the Template Using Both Conditions Simultaneously

        Task: How to set a template according to 2 conditions simultaneously (for a group of users and for both folder and file)?

        For this, the current template must be changed according to the condition PHP expression:

        in_array($groupID, $USER->GetUserGroupArray()) || strpos($APPLICATION->GetCurDir(), "/dir/")!==false || $APPLICATION->GetCurPage()=="/dir/file.php"
        

        Template Application Only to the Files with a Specific Extension

        Task: Which PHP expression should be used so that the template applies to all pages ended in .php but not .html?

        Solution: change of the template according to the condition PHP expression:

        substr($APPLICATION->GetCurPage(true), -4) == ".php" 
        

        Similarly for the files with the html extension:

        substr($APPLICATION->GetCurPage(true), -5) == ".html"

        Change of Site Header Design for Different Sections

        Task: The site is divided into several sections. The idea is that each section must have its own header in the design. There are no other changes in the design.

        The component Insert include area is connected to the template:

        <div id="header">
        <?$APPLICATION->IncludeComponent("bitrix:main.include", ".default", array(
           "AREA_FILE_SHOW" => "sect",
           "AREA_FILE_SUFFIX" => "headerinc",
           "AREA_FILE_RECURSIVE" => "Y",
           "EDIT_TEMPLATE" => "sect_headerinc.php"
           ),
           false
        );?>
        </div> 
        

        The header code of each section will be stored in the file sect_headerinc.php. The parameter "AREA_FILE_RECURSIVE" => "Y" means that the same header will appear in all subsections of this section, unless the parental sect_headerinc.php is specifically shut off in any underlying sections.

        Specific of working with AJAX

        Using the ajax mode has its specifics. For the navigation string to have its name at the page opened via ajax, the template must have the element with id="navigation". The div is optional: it can be span, h1, p and so on.

        Similarly, the header must have the id="pagetitle" element available.



        Using message files for localization

        Language communication is a group of phrases in different languages having the same meaning.


        The Bitrix Framework allows using the same template for several sites on different languages. This opportunity is realized by means of the message files mechanism:

        • areas intended for text displaying are defined in the template HTML prototype (for example, titles, ALT attributes, controls text etc.);
        • then, special source code should be placed in these areas that calls language files containing necessary text messages on corresponding language.

        For example, table headers, button inscriptions, component messages, etc. may be created using language communications.


        Note: language communication feature is also used to support multi-language interface of the administrative section of the system.


        Related links:

        Implementation

        • The /lang/ folder is created in the a site template directory:

          /bitrix/templates/<>/lang/
        • The /lang/ folder has the folders with the utilized languages identifiers: /en/, /de/, /fr/, and etc. For example:

          /bitrix/templates/<>/lang/fr/
        • The languages identifiers corresponding to message files are stored within the folders. These files are characterized by the following properties:
          • the message file name is equal to the file name where this message file is called. For example, if a message file call is implemented in the template header (file header.php) then this message file must have name header.php.
          • message list in the file is stored the following way:

            <?
            $MESS ['COMPANY_NAME'] = "Company Name";
            $MESS ['MAIN_PAGE'] = "Home page";
            $MESS ['PRINT'] = "Print version";
            $MESS ['AUTH_LOGIN'] = "Authorization";
            $MESS ['RATES_HEADER'] = "Currency rates";
            $MESS ['SEARCH'] = "Site search";
            $MESS ['SUBSCR'] = "Subscription";
            ?>
        • At the beginning of file, where the message file call is implemented, the following function is added:

          <?
          IncludeTemplateLangFile(__FILE__);
          ?>
          The IncludeTemplateLangFile(__FILE__) connects a message file for the current language.
        • All text in template is replaced with the function calling the corresponding messages:

          <font class="search"><?echo GetMessage("SEARCH");?></font>
          The code (ID) of calling message is used as the GetMessage() parameter. The function verifies if the connected message file contains necessary message. If the necessary message exists then it is shown to page visitors.

        Localization

        The message files localization can be implemented the following ways:

        • manually (when the site administrator searches and translates necessary message files);
        • with use of the Localization module tools.

        The Localization module provides users with a handy interface for text messages search and translation. The Localization module allows:

        • to look through the message files distribution among the system files;
        • define the number of untranslated text messages for every file;
        • proceed to the necessary text messages translation.

        In order to view distribution of language communications by files, go to the page Interface localization (Control Panel > Settings > Localization > Browse Files) and click the button Show differences on the context panel:

        The number of untranslated phrases used in a file or directory for each language is marked in red.

        Note: For big projects, it is not recommended to search for differences in all the product files; it should be done in stages, for each folder. Otherwise, the error 504 Gateway Timeout may occur.

        In order to translate language phrases, a file for which language communications are used should be selected, and a translation of the communication to a relevant language should be entered:

        Note: This form shows translations into the languages of the same encoding. I.e. if the ISO-8859-1 encoding is set for the English and Spanish language settings and Windows-1251 for the Bulgarian language, the form will contain fields to introduce the translation of the message into English and Spanish. To translate the phrases into Bulgarian, the interface must be switched to Bulgarian.

        You can quickly start translating the missing messages directly from a page on which these messages are used (both in the public section and Control Panel). To activate the quick translation mode, use the Show Language File button (it is visible if the corresponding option is checked in the Localization module settings)

        or add the parameter show_lang_files=Y to the page address:

        As the result, list of localization files used on the page will be displayed at the bottom of the page:

        File names are shown as links, which can be clicked to open the language file for editing. In the end of the list you will find a search field. You can use it to find a required message (exact match).

        Information: All localized phrases can be collected with use of the special tool. Collected files can be placed in the Update system for downloading by other users. Please refer to the Localization Guide for more details.

        Change of Phrases in Components and Modules

        Sometimes during developing, it’s necessary to change some words or phrases in the system components or modules.

        The main aim of this technology is to allow the developer to modify language phrases after connecting with the language file. A list of changes is kept in the file: /bitrix/php_interface/user_lang/<language_code>/lang.php

        In this file, the elements of the array $MESS must be determined as $MESS['language file']['code of the phrase'] = 'new phrase'', for example:

        <?
        $MESS["/bitrix/components/bitrix/system.auth.form/templates/.default/lang/en/template.php"]["AUTH_PROFILE"] = "My Profile";
        $MESS["/bitrix/modules/main/lang/en/public/top_panel.php"]['top_panel_tab_view'] = "View";
        $MESS["/bitrix/modules/main/lang/en/interface/index.php"]['admin_index_sec'] = "Pro+Pro";
        ?>

        The first line changes the text of the link in the authorization form component; the second line changes the name of the tab of the public panel; and the third line changes the line for index page of the control panel.

        Theoretically, there can be problems with maintenance of this file, if the code of the phrase or arrangement of the files are changed, but in practice we try not to do that, because it will be additional work for localizers.

        Localization Archive Import/Export

        Importing and exporting into CSV file

        CSV file Import/Export feature is used to handle localization files manually.

        • Go to the required folder (for example, activity) to the Export files tab (Settings > Localization > Browse files) select entirely replace language files to export all localization strings of this folder (module), or export only new messages to export only translated strings into CSV file.
        • Click Export and the created localization CSV file window ill appear.

          By default, the name of the CSV file will consist from the folder (or module) names, included into the localization. Otherwise, the file will have the name bitrix_activities.csv

        • Edit the required strings and save all changes in the same CSV file.
        • In the similar manner, you can select the way of adding localization into the system via Browse files: import only new messages or entirely replace language files, select CSV file and click Import:

        The module has been translated.


        Collect localization strings

        After the system localization archive was created, a full localization package for each system language can be created.

        • Go to the page Collect strings (Settings > Localization > Export and Import).

        • Select the required options in the Collect strings tab:
          • Select language - indicate which language phrases are to be exported;
          • Localization version date - date format YYYYMMDD for the localization archive;
          • Convert To UTF-8 - select the option to convert into a national encoding (UTF-8) for all localization files (by default, the files will be collected in the current site encoding otherwise);
          • Pack Files (tar.gz) - option to compress files into the archive of tar.gz format;
        • To launch the collection of localization package, click the Build Localization button.

        After the localization archive is collected, a link with the archive location is provided (if the option to build archive with tar.gz format is selected) or to files of the complete archive.


        To import the localization archive into the system:

        • Go to the page Browse files (Settings > Localization > Export and Import):

        • Select the required options in the Collect Strings:
            indicate the language phrases of which language are to be imported
          • Localization archive (with tar.gz format);
          • Select Language (the required language can be already installed in the system, or can be created via the Add link);
          • Convert from UTF-8 - select this option if your project is in national encoding and the localization archive to be imported is in UTF-8;
        • To launch the localization package import into the system, click the Import Localization button.

        Related Links:

        Bitrix Framework Localization Guide.

        Editable areas

        Editable area is a specially designated area on the site page that may be edited separately from the main contents of the page.
        Include areas serve to place reference information, forms (subscription, voting, surveys, etc.), news, and any other static and dynamic information. Also the areas indicating copyrights, graphic links, contact information, company logo, etc. may be performed as an include area.


        The Bitrix Framework allows creating different types of editable areas:

        • editable area of the certain file – displayed only when viewing the current document;

        • editable area of the certain section – displayed for all documents of the given section;

        • editable area of site template – displayed in predefined areas of site design template.

        The number of editable areas can be extended. In this case you need to implement the corresponding modifications in site template. For example you may need to include php code calling the additional editable areas.

        Besides that, editable areas can be displayed according to any program conditions. For example, editable areas can be shown only for exact user groups or only for authorized users, and etc.

        You can turn on the special mode that enables to view the include areas by clicking the Edit Mode button in the administrative toolbar. As the result all editable areas will be spotlighted as separate blocks. Links allowing to proceed to area editing will be displayed in top left conner of each block.

        Managing editable areas

        The contents of include areas are stored in separate PHP or HTML files. Areas for pages or sections are saved with a suffix. E.g., in the product files supplied the suffix _inc is used as a designation of the include area for a page, and the include area for a site section is stored in the sect file with a suffix added (e.g., sect_inc.php).

        Important! The file with include area must be saved in the same directory with the page for which it was created. Include area for a section must be saved in the folder of this section.

        Areas in the site design template should be connected using the component Insert include area or using the function of IncludeFile().

        The suffix used for the designation of include areas is determined by the option having the same name in the settings of the component Insert include area. The component may be located not only in the design template but also on the site pages, provided that file suffix must be set as different from that used in the template.

        Note: The type of the include area is determined by the parameter Show include area of the component Insert include area.
        If the component is located in the site design template, the information from file will be displayed on the entire site. Parameter setup is available only for users with the operation Edit PHP code (edit_php) at the kernel module access level.


        Include Area Layout

        In order to place the include area proceed as follows:

        • Open a site template or a page in a visual editor for editing.
        • Add the component Insert include area (bitrix:main.include) to the site template (or page body) and set up its parameters.

        Using the editable areas when integrating into the site design has limitations. They are related with the size, assigned for a slot in which the component is allocated. If a text, an image or any other object has the size larger than the slot, assigned for the the component, the site design will become misaligned.

        Using the editable areas allows to manage not only the text. An image instead of a text can be placed in such an area (or a Random photo component) and receive an custom appearance for each section. With this, the design variation will be "dynamic" and can be modified.


        Creating and Editing the Include Area

        Include areas may be created:

        • From the administrative section in Site Explorer (Control Panel > Content > Site Explorer > Files and Folders) by creating a file with the relevant name;
        • From the public section of the site in the edit mode. In the places where include areas are supposed to be displayed, the icons will be shown for quick access to creation of these areas.

          Note: A file of the include area will be created and named in accordance with the suffix indicated in the component settings – for the option for section, or a file name – for the option from file.

          After the command of Add Area is selected, visual editor will be launched to create the contents of the include area. If the command of Edit Area as PHP is selected, it will become possible to add an area in the PHP code mode.


        Likewise, it is possible to switch to editing of include areas as follows:

        • Directly from the public section of the site in the edit mode

        • Or from the administrative section by opening relevant file in Site Explorer for editing.

        Attention! If the option from file is used as include area, you have to check that the file is connected from the system and not accessed directly. It should be done using the following line: <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>.
        Example of contents of included file:
        <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
          <div class="bx-main-title">World Book</div>
          <span class="bx-main-subtitle">Books</span>
        


        Templates of Include Areas

        Editable areas are created on base of templates stored in folders with the names /page_templates/:

        • /bitrix/templates/.default/page_templates/ - in case if this editable area template is utilized for all site templates;
        • /bitrix/templates/<template_ID>/page_templates/ - in case if for this site template are used individual editable areas templates.

        If you want the editable areas to be added to the list of available templates of WYSIWYG editor, add the editable area templates manes in the file .content.php.

        The file .content.php is stored in the folder /page_templates/ located in the corresponding template directory.

        Example of file contents:

        <?
        if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
        
        IncludeTemplateLangFile(__FILE__);
        
        $TEMPLATE["standard.php"] = Array("name"=>GetMessage("standart"), "sort"=>1);
        $TEMPLATE["page_inc.php"] = Array("name"=>GetMessage("page_inc"), "sort"=>2);
        $TEMPLATE["sect_inc.php"] = Array("name"=>GetMessage("sect_inc"), "sort"=>3);
        ?>
        

        Also the name of utilized template can be defined with use of special parameter while assigning editable area to the site. (see the code spotlighted with blue color in the example below).


        Assigning editable areas to site templates is implemented with use of the function IncludeFile(), places in the necessary areas of site template:

        <?
        $APPLICATION->IncludeFile(substr($APPLICATION->GetCurPage(),
        0, strlen($APPLICATION->GetCurPage())-4)."_inc.php", Array(),
        Array("MODE"=>"html", "NAME"=>GetMessage("PAGE_INC"), "TEMPLATE"=>"page_inc.php"));
        ?>
        <?
        $APPLICATION->
        IncludeFile($APPLICATION->GetCurDir()."sect_inc.php", Array(), Array("MODE"=>"html",
        "NAME"=>GetMessage("SECT_INC"), "TEMPLATE"=>"sect_inc.php"));
        ?>

        Note: If no variable is indicated, the system will use a default template, which is the page template.


        Deleting Include Areas from the Demo Template

        The line responsible for connecting an include area must be commented out or deleted in the template code. Normally these are files ending with _inc.php.

        The line may look like the following:

        <?
        $APPLICATION->IncludeFile(
        substr($APPLICATION->GetCurPage(), 0, strlen($APPLICATION->GetCurPage())-4)."_inc.php",
        Array(),
        Array("MODE"=>"html", "NAME"=>GetMessage("PAGE_INC"), "TEMPLATE"=>"page_inc.php"));
        ?>
        

        Example of Using Include Areas

        Task: The site is divided into several sections. The idea is that each section must have its own header in the design. The design has no other changes. What is the best way to implement the change of section headers?

        Solution: The component of the "Include Area (for section)" is connected to the template:

        <div id="header">
        <?$APPLICATION->IncludeComponent("bitrix:main.include", ".default", array(
           "AREA_FILE_SHOW" => "sect",
           "AREA_FILE_SUFFIX" => "headerinc",
           "AREA_FILE_RECURSIVE" => "Y",
           "EDIT_TEMPLATE" => "sect_headerinc.php"
           ),
           false
        );?>
        </div>

        Header code for each section will be stored in the file sect_headerinc.php. The parameter of "AREA_FILE_RECURSIVE" => "Y" means that the same "header" will appear on all subsections of this section, if the parental sect_headerinc.php is not specifically shut off at a lower section level.

        Navigation tools

        Navigation tools shall be included in the site template. The use of special components is recommended for their implementation.

        Site Map

        Site map is one of the navigation elements on the site that is a standard element of the modern site. As a rule, the page headers contained in the list constitute links to these pages.


        Creation of a Site Map

        The component of Site map (bitrix:main.map) is used to create the site map. The component is located in the folder Content > Site map of the panel Components 2.0 of the visual editor.

        The map is built up based on the menus used in the system. The menu types are specified in the settings of the Kernel module in the Site Map section (Control Panel > Settings > System settings > Module settings).

        Note: Several types separated by a comma may be used simultaneously as a basis for building up the reference level and branches of the site map.

        • Create a separate page and place the component of the Site map.
        • Setup the component parameters:
          • Maximum number of nested levels - is the depth of the section nesting that must be reflected in the menu. If the number in this field is more than 4, the site structure should be thought over again.
          • Number of columns – indicate how many columns will be used to create the site map image. This number largely depends on the site design, nesting level used, and site structure. Allocation of map points in the columns is based on the first level menu options. I.e., if one of the menu options has a slightly deeper structure compared to other options, then in case a deep nesting level is chosen on the site map you can obtain a long column for this point and short columns for other points.
          • Show descriptions – descriptions of sections (physical folders) will be displayed. A description of information block sections is not displayed.

        Note: If a site folder is specified incorrectly (e.g., there is no closing slash) in the site settings (Control Panel > Settings > System settings > Websites > Websites > _ site _), the component will not be able to create the site map.


        Examples of Work

        How to include site sections that are not specified in the menu into the site map?

        Normally, the customization of a component template is required to do so, but there is also another solution. The simplest one is to prepare their own menu type for these sections and add it, separated by a comma, in the settings of the main module in the menu types option of the site map menu.

        Menu

        Menu is a design element that constitutes the main site navigation tool.

        This section contains description of the main principles of creating and managing site menu, including description of file with menu data and menu template structure. Also the possibility of dynamic menu usage is mentioned.


        Menu Types

        Menu types are the menu organization principle. By default, two menu types are used in the installation package – Top and Left.

        Several menu types are possible, depending on the site function: top, left, and bottom, etc. In each menu component, two menu types may apply: one as the main menu, and another as an additional menu, provided that multi-level templates are used.

        In the most general case, the site has one “main” menu corresponding with the top most hierarchy level and is reflected in all the site sections. In addition, the system often uses a “secondary” menu (or a second-level menu) that includes links to the subsections and documents of the current section.

        The menu in the system is hereditary. This means that if a certain menu type is selected for a menu component in the template, this menu will be translated downwards to all site sections and pages with this template, unless a proper menu was created for these sections and pages. This mechanism is convenient for the site main menu; normally it is assigned the Top type.

        Note: If a higher menu must not be reflected in the lower level section, just create a menu in the relevant section without menu options.

        As a rule, a section menu shall be created for each section and translated to all the pages of the section. If necessary, a proper menu can be created for each subsection and a proper type applied thereto.


        Types of used on a site menu are defined in the administrative section on the Site Explorer module page.

        For example, let us assume that the system uses two menu types:

        • Left menu – “left” type;
        • Top (main) menu – “top” type.

        A menu type (defined on this page) will be utilized as prefix both for file with the menu template (e.g., top.menu_template.php) and for file with section menu items (e.g., .top.menu.php). Besides the menu type is used calling necessary menu in site template.

        Note: Menu types can be defined for every site individually.

        The menu types are assigned arbitrarily. But to make work with menu more convenient it is recommended to use meaningful designations for menu types. For example, top, left, bottom.

        Menu Building and Display

        Generally, the task of menu creation consists of the following:

        • marking HTML elements to build up the menu;
        • creating menu template (creation of the Menu component template);
        • including a menu display function (Menu component call) in the general template (“prologue” and “epilogue”);
        • completing menu in accordance with the site structure.

        Menu Structure

        Any menu on the site consists of two parts:

        • $aMenuLinks data array determining menu composition which gives names and links for all menu options. Data array is managed through an administrative interface;
        • template of an external display of the menu. The menu template is a PHP code determining the external aspects of the menu (Menu component template). The menu template processes the data array giving an HTML code in the output.

        Menu Data Array

        Data for each menu type are stored in a separate file. Its name has the following format: .<menu type>.menu.php. For example, to store the data of the left type menu, the file .left.menu.php will be used, and to store the menu data of the top type – the .top.menu.php file.

        The menu is inherited hierarchically. The menu files are located in the folders of the site sections where the display of the relevant menu types is required. If no relevant menu file is created for this section, the system searches for the file in the catalog of the upper level.

        For example, since the main menu (it is a top type menu in the product demo version) must be displayed in all the sections, the file of this menu shall be located only in the root catalog of the site.

        Accordingly, the second level menu (it is a left menu in the product demo version) is displayed separately for each section of the site. That is why a proper file for this menu type must be created in the folder of each section.

        Another example: a visitor is in the section /en/company/about/. In order to display a left type menu, the menu file will be searched in the following order:

        1. /en/company/about/.left.menu.php
        2. /en/company/.left.menu.php
        3. /en/left.menu.php
        4. /.left.menu.php

        If the menu is found in one of the catalogs, the search will be stopped and the next catalogs will not be searched.

        The Bitrix Framework system also permits you to create a dynamic type menu. I.e. the data array of such a menu is generated automatically based on certain data obtained using the source code. This code must be stored in the folder of a relevant site section in a file named .<menu type>.menu_ext.php..

        The principal task of these files is to manipulate a $aMenuLinks array. These files cannot be edited visually in the Site Explorer module, which is why they will not be edited accidentally during the visual editing of the menu. Use the Menu items (bitrix:menu.sections) component to create this file.

        Note: The paragraph above describes only the adding names of information block sections to the created menu. This variant is not suitable, for example, for adding forum names to the menu.
        Attention! If catalog sections without computer numerical control are used as menu options, it is necessary to indicate variables in significant variables of a query.

        A drop-down menu of the Products section offered in the demo version of the product may be a good example of such a menu. Here, upper menu options are created normally, and the remaining options (Sofas & Loveseats, Office Desks and Chairs etc.) are formed dynamically.

        In this case, the names of the groups of the Products catalog created based on the information blocks are used as menu options.

        The source code used to generate the menu is stored in the file .left.menu_ext.php in the folder /products/.


        The following standard variables may be used in the files .<menu type>.menu.php:

        • $sMenuTemplate - is an absolute path to the menu template (this variable is used very rarely);
        • $aMenuLinks is an array; each element of this array describes the next menu option.

          This array structure:

          Array
          (
              [0] => menu option 1
                  Array
                      (
                          [0] => menu option header
                          [1] => menu option link
                          [2] => array of additional links for a menu option selection:
                              Array
                                  (
                                      [0] => link 1
                                      [1] => link 2
                                      ...
                                   )
                          [3] => array of additional variables transmitted to the menu template:
                              Array
                                  (
                                      [name of variable 1] => value of variable 1
                                      [name of variable 2] => value of variable 2
                                      ...
                                   )
                          [4] => condition for the menu option to appear
                                 is a PHP expression which must return “true”
                      )
              [1] => menu option 2
              [2] => menu option 3
              ...
          )
          

        Examples of menu files

        <?
        // file example .left.menu.php
        
        $aMenuLinks = Array(
        	Array(
        		"Furniture Company", 
        		"company/", 
        		Array(), 
        		Array(), 
        		"" 
        	),
        	Array(
        		"News", 
        		"news/", 
        		Array(), 
        		Array(), 
        		"" 
        	),
        	Array(
        		"Products", 
        		"products/", 
        		Array(), 
        		Array(), 
        		"" 
        	)
        );
        
        ?>
        <?
        // file example .left.menu_ext.php
        
        if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
        
        global $APPLICATION;
        
        $aMenuLinksExt = $APPLICATION->IncludeComponent(
        	"bitrix:menu.sections",
        	"",
        	Array(
        		"ID" => $_REQUEST["ELEMENT_ID"], 
        		"IBLOCK_TYPE" => "books", 
        		"IBLOCK_ID" => "30", 
        		"SECTION_URL" => "/e-store/books/index.php?SECTION_ID=#ID#", 
        		"CACHE_TIME" => "3600" 
        	)
        );
        
        $aMenuLinks = array_merge($aMenuLinks, $aMenuLinksExt);
        
        ?>

        Organizing the Menu Display

        In order to display the menu on the site pages, the component Menu (bitrix:menu) is used. For example, the top menu on the demo site is invoked as follows:

        <?$APPLICATION->IncludeComponent(
        "bitrix:menu",
        "horizontal_multilevel",
        Array(
        "ROOT_MENU_TYPE" => "top",
        "MAX_LEVEL" => "3",
        "CHILD_MENU_TYPE" => "left",
        "USE_EXT" => "Y"
        )
        );?>

        This code is located in the areas of the site template designated for the menu display.


        Building the Site Menu

        Menu for display is built as follows:

        • menu display call is included in the general template;
        • when loaded, the component checks for the availability of a file containing array of values for the menu in the current site section;
        • after that, the component invokes a building template for this type of the menu and displays an HTML menu on the screen.

        Menu Templates

        Data of the Menu component are displayed using templates. The templates may be created by users independently.

        System Templates

        Six templates are included in the Bitrix Site Manager installation package by default:

        • Default (vertical menu by default) is a template for the vertical menu. It is the simplest template. If a nesting depth higher than 1 is selected in the component parameters, you will see the list of site pages in the general hierarchy. I.e. the index page of the site (section) will be located on the same level with a nested page (section). It makes the understanding of the site structure by a visitor more difficult. That is why the template is recommended for simple menu types and the main menu of the top level. The template is simple enough for customization under a specific design.

        • Tree (Tree menu) is a template for vertical menu. It implements the menu as a tree-like structure similarly to Windows Explorer. Nested pages are shown as pages in the folder (section), which makes it significantly easier for a user to understand the site structure. This menu is not always convenient for a branched structure of the Internet project because it significantly extends the column where it is located in a vertical direction.

        • Vertical_multilevel (Vertical multilevel dropdown menu) is a template for the vertical menu. It implements the menu with dropdown options of a lower level menu that maintains the visitor-friendly site structure properly for the tree-like menu, but at the same time does not extend the design the way the branched structure does.

        • Grey_tabs (Gray menu in the form of bookmarks) and Blue_tabs (Blue menu in the form of bookmarks) are templates for the horizontal menu. They differ only in appearance. These are the simplest templates similar to the default template for the vertical menu.

        • Horizontal_multilevel (Horizontal multilevel dropdown menu) is a template for the horizontal menu. It is similar to Vertical_multilevel (vertical multilevel dropdown menu) and implements the menu with dropdown options of the lower level menu.


        Creating Menu Templates

        Selecting HTML Elements to Create Menu

        The creation of menu templates is started from selecting the necessary HTML areas in the site template:

        • Unchanged top and bottom part of the template;
        • repeated elements. For example, table cells for horizontal menu and lines for the vertical menu.

        Creating a Menu Template

        All menu templates have a similar structure:

        • menu template prologue area;
        • area describing the replacements for the different conditions of template processing;
        • menu template body area;
        • menu template epilogue area.

        The array of $arItem, which is a copy of menu option array, is used in the php template to display the menu. In this array, each option represents, in its turn, an array using the following parameters:

        • TEXT - menu option header;
        • LINK - menu option link;
        • SELECTED - whether a menu option is selected at the time; the following values are possible:
          • true - menu option selected;
          • false - menu option is not selected.
        • PERMISSION - the right of access to the page indicated in LINK for the current user. The following values are possible:
          • D - access denied;
          • R - read (the right to view file contents);
          • U - document flow (the right to edit file in a document flow mode);
          • W - write (the right to direct editing);
          • X - full access (the right to direct editing of the file and the right to change access rights to this file).
        • ADDITIONAL_LINKS is an array of additional links for menu selection;
        • ITEM_TYPE is a flag indicating the link type specified in LINK, the following values are possible:
          • D - directory (LINK is ended with “/”);
          • P - page;
          • U - page with parameters.
        • ITEM_INDEX - sequential number of a menu option;
        • PARAMS - associated array of menu option parameters. The parameters are set in the extended menu editing mode.

        Let us consider the creation of a menu template using the example of the Left menu provided in the demo version of the product (template .default of the Menu component bitrix:menu):

        <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
           <?if (!empty($arResult)):?>
              <ul class="left-menu"> 
              <?foreach($arResult as $arItem):?>
                  <?if($arItem["SELECTED"]):?>
                       <li><a href="<?=$arItem["LINK"]?>" class="selected">
                       <?=$arItem["TEXT"]?></a></li>
                  <?else:?>
                       <li><a href="<?=$arItem["LINK"]?>">
                       <?=$arItem["TEXT"]?></a></li>
                  <?endif?>
              <?endforeach?>
              </ul>
           <?endif?>

        The repeated part of the menu marked in the previous step is allocated in the body of the menu template.

        When creating the menu template, additional styles will be required in the style sheet (CSS). E.g., for text menu: the color of a menu option and the color of a current (active) menu option.

        Section headers may require a separate description in the template (e.g., the name of the current section during subsection view). In addition, the use of graphics or text denominations may be added; e.g. that this option refers to subsections or a document of the current section, etc.

        Note: All menu templates are stored in the component folder: /bitrix/components/bitrix/menu/templates/.


        Quick access to template editing of each menu type may be effectuated in the Edit Mode using the option Edit component template of the command menu of the component control button.

        Note: The menu template, if it is a system template, must be copied into the current site template before making any changes.
        When editing such a template from the public part of the site, the system will automatically offer the possibility of copying.

        Menu Control

        Menu is controlled using both tools of the administrative and public sections.

        Creating Menu

        The command Add menu of the Add button located on the context panel of the File Manager makes it possible to start creating the section menu from the administrative section:

        The menu will be created for a section in which the folder is currently opened in the File Manager.

        Note: As a result of these actions, a file with menu data is created with a name .<menu_type>.menu.php. However, the name of the data file in the file manager is automatically represented as a link of the “<menu_type>Menu type.


        Menu Editing

        Note: When editing the file .<menu_type>.menu.php is changed (e.g., .top.menu.php). However, work with this file is carried out through a special interface of the system. It makes the work directly with source code unnecessary and gives the possibility to edit the menu options in visual mode.

        In order to edit the menu from the administrative section go to File Manager and open the file of a relevant menu for editing.

        The system provides for two modes of menu editing (use the button located in the context panel of the menu editing page to switch between them):

        • Simple mode of editing;

          The mode permits to determine the menu type and indicate a name of menu option, navigation link, and a sorting index value.
        • Advanced mode of editing.

          The following data are available for control in this mode:
          • type of the menu being edited;
          • template which serves as a basis to generate the menu (the field is used if the new menu must be generated based on a template which is different from the default template);
          • name of the menu option;
          • navigation link;
          • sorting index;
          • a set of additional links that correspond to the same menu option. A set of links to pages that, when accessed, will also highlight this menu option that is established in this field. For example, in order to have the Book catalog menu option highlighted when any Book catalog section page is viewed, this field must contain a link to the folder with all the pages of the section (or necessary pages): /e-store/books/;
          • viewing conditions. For example, it permits you to introduce viewing restrictions that are applicable to this menu option for users with certain access rights;
          • Additional parameters is a set of arbitrary parameters that may be processed in the menu display template and are represented accordingly. For example, if a menu option is a section header, it may be specified in the option parameters as follows: parameter name – SEPARATOR, value – Y. When creating a template, this parameter may be verified and this option may be marked with a separator.

            Parameters are stored in an associated array $PARAMS as name => value pairs. When creating a menu according to a template, the template itself may include parameter verification, for example:

            if ($PARAMS["MY_PARAM"]=="Y")
            

            Note: The number of additional parameters in the form may be increased using the option Number of additional menu parameters in the settings of the Site Explorer module, Website Parameters section.

        Problems with Menu Caching

        Oversized Folder Containing Menu Cache

        Situation. A big size of the folder bitrix/managed_cache/MYSQL/menu: 1.9 Gb is revealed. The site has 4 types of cut-through menus and many pages. What is the problem and what can be done about it?

        Cause. One cache file is created per page for each type of the menu used. In addition to that, if caching is set for various groups, multiply this number by the number of such groups. I.e., for each page you will have 4 files of the menu cache (if by groups, multiply by the number of groups). That explains the size of the folder.

        Such a number of files by itself is not a problem, provided that there is enough space on the disk. The problem is that the accelerator (in our case, APC) saves these files into cache overfilling it.

        Solution: Make sure that template.php and result_modifier.php contains no requests and heavy computing and exclude a file cache from the accelerator. Requests must be cached in menu_ext files.

        apc.filters="-/bitrix/cache,-/bitrix/managed_cache/,-/upload/"

        Note: If a certain menu type is not redefined in subfolders of the site, the following parameter may be specified upon connecting to the menu:
        "CACHE_SELECTED_ITEMS" => "N",
        As a result, the URL will not appear in the key during creation of the menu cache file. The selected level will be calculated after data from the cache are retrieved.

        Examples of Resolving Specific Tasks in the Menu

      • Highlighting of Links
      • How to Open a Menu Option in a New Window?
      • Displaying a Menu Option for Certain User Groups
      • Displaying a Menu Option to Unauthorized Users
      • Displaying a Menu Option when Visiting a Certain Site Section
      • Displaying Certain Options Only on the Home Page and in an Internal Section
      • Displaying a Menu Option Referring to a Specific Product Connected with the Product Opened on This Page
      • Pop Up Tips for Menu Options
      • How to Put Images Close to Menu Options?
      • Different Menu Option Images for Different Languages
      • Displaying of Customized Menu Different from Template Menu for Specific Site Sections
      • How to Keep Tree Menu Open at All Times?
      • Having Opened a Page through a Tree Menu, How to Prevent the Menu from Rolling Up and Make It Show on Which Page You Are?
      • Dropdown Menu with Inactive Options of the First Level
      • Hiding of a Side Menu According to the Page Properties
      • Menu and Cache

      • Kernel module version 9.0.5 in the bitrix:menu component has the parameter Delay building the menu template that permits adding menu options to the components.

         $GLOBALS['BX_MENU_CUSTOM']->AddItem('left', array('TEXT' => 'Mob.version', 'LINK' => $APPLICATION->GetCurPage(false) . '?mobile'))
        First parameter is the menu type.
        Second parameter is an array describing the menu option.


        Highlighting of Links

        The field Additional links to highlight in an extended editing form that permits you to include the highlighting of certain menu options when going to the page. It may be necessary when a user must not forget where they came from or in order to draw attention to a certain page.

        When visiting a page indicated in the field Additional links to highlight, the appropriate menu option will be highlighted. The path to pages is set starting from the site root.


        How to Open a Menu Option in a New Window?

        The following additional parameters must be indicated for the appropriate options in the extended editing mode:

        Name: target 
        Value: target="_blank"
        

        The code must be replaced in the menu template after displaying the menu element:

        <a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a>
        

        with:

        <a href="<?=$arItem["LINK"]?>" <?=$arItem["PARAMS"]["target"]?>><?=$arItem["TEXT"]?></a>
        

        Displaying a Menu Option for Certain User Groups

        Choose the Condition type equal to For user group for the targeted option in the extended edit mode and select groups that will see this option in the Condition itself.

        Condition type: For user groups
        Condition: required_user_groups
        

        Displaying a Menu Option to Unauthorized Users

        Set the following condition in the extended edit mode:

        Condition type: PHP expression
        Condition: !$USER->IsAuthorized()
        

        Displaying a Menu Option when Visiting a Certain Site Section

        The system permits you to display a menu option only when visiting a specified site section or its pages. For this, select the option For file or folder in the field Condition type in the extended edit mode and specify the path in the Condition field.

        Note: The condition For file or folder should apply for static pages. It will not work on dynamic pages because it verifies the address and the dynamic pages always include selected values. For a dynamic URL, the use of the URL parameters condition is recommended.


        Displaying Certain Options Only on the Home Page and in an Internal Section

        Introduce the following php expression in the Condition field for the required options in extended mode:

        CSite::InDir('/about/') or CSite::InDir('/index.php')

        Displaying a Menu Option Referring to a Specific Product Connected with the Product Opened on This Page

        The URL parameters condition type may be used for this. The option will be displayed on pages with a defined parameter in a URL. The parameter works with a URL containing the symbol "?". I.e., with dynamic pages.

        The pages created on the basis of infoblocks have a URL of the following type: http://site/section/index.php?SECTION_ID=***. Let us assume that on the page with SECTION_ID=123 a menu option leading to the page SECTION_ID=456 must be displayed.

        We create a menu option leading to the page http://site/section/index.php?SECTION_ID=456. Let us select the URL parameter in the field Condition type and specify SECTION_ID in the first field and 123 in the second field.


        Pop Up Tips for Menu Options

        In the extended edit mode, add the additional parameter of A_TITLE and write the contents of the pop up tip there.

        Name: A_TITLE
        Value: pop_up_tip
        

        In the menu template:

        <?if($arItem["SELECTED"]):?>
        		<li><a href="<?=$arItem["LINK"]?>" class="selected"><?=$arItem["TEXT"]?></a></li>
        	<?else:?>
        		<li><a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a></li>
        <?endif?>
        
        the first link in the code must be replaced with the line:
        <a href="<?=$arItem["LINK"]?>" class="selected" title="<?=$arItem["PARAMS"]["A_TITLE"]?>"><?=$arItem["TEXT"]?></a>
        
        and the second with:
        <a href="<?=$arItem["LINK"]?>" title="<?=$arItem["PARAMS"]["A_TITLE"]?>"><?=$arItem["TEXT"]?></a>
        

        How to Put Images Close to Menu Options?

        Add an additional parameter into the menu (menu editing in the extended mode), e.g., IMG, and write the address of the image that you want to display close to this option.

        Name: IMG
        Value: path_to_image
        

        In the following display of a menu element after the line (depending on the template):

        <a href="<?=$arItem["LINK"]?>">
        

        the following must be added in the menu template:

        <img src="<?=$arItem["PARAMS"]["IMG"]?>" border="0" />
        

        Different Menu Option Images for Different Languages

        The menu consists of image options set by CSS classes. The site is bilingual. The menu structure itself is already separated and connected; the external appearance must be separated.

        Solution:

        Specify the following in the menu template:
        <body class="lang-<?=LANG?>"> 
        
        in CSS:
        .menu li.item1 { 
        background-image:url(title-en.gif); 
        } 
        .lang-ru .menu li.item1 { 
        background-image:url(title-ru.gif); 
        }
        

        Displaying of Customized Menu Different from Template Menu for Specific Site Sections

        Such menu may be organized using a site template change (Control Panel > Settings > System settings > Websites > Websites, site edit, Template section, condition For file or folder). In addition, if the template is not complicated, verification may be set directly in the template code, and display the menus depending on a specific case:

        if($APPLICATION->GetCurPage() == "/index.php") {
        display of the menu for home page
        }
        else {
        display of the second menu
        }
        

        How to Keep Tree Menu Open at All Times?

        Solution:

        Take the standard template for tree menu and copy it into its template. After that, replace line 14 in the file template.php:
        <li class="close">
        
        with:
        <li>
        

        In this case, clicking the image will cause the unfolded options to hide.


        Having Opened a Page through a Tree Menu, How to Prevent the Menu from Rolling Up and Make It Show on Which Page You Are?

        Solution:

        Take the standard template for tree menu and copy it into its template. After that, replace line 14 in the file template.php:
        <li class="close">
        
        with:
        <li <?if (!$arItem["SELECTED"]):?>class="close"<?endif?>>
        

        Note: This solution works only up to the 2nd nesting level of the menu.

        The following code that must be introduced into the menu template file permits the menu from rolling up if the nesting level is higher than 2:

        ...
        <?if (!empty($arResult)):?>
        
        <?
        //analysis of open nodes of the tree
        $lastLevel = 0;
        $selected = false;
        
        foreach(array_reverse($arResult) as $arItem){
            if ($arItem["SELECTED"]) {
                $lastLevel = $arItem["DEPTH_LEVEL"];
                $selected = true;
            }
            if ($selected and $arItem["DEPTH_LEVEL"] < $lastLevel){
                $arResult[ $arItem["ITEM_INDEX"] ]["SELECTED"] = true;
                $lastLevel--;
            }
        }
        ?>
        
        <div class="menu-sitemap-tree">
        <ul>
        <?$previousLevel = 0;foreach($arResult as $arItem):?>
        ...
        

        Dropdown Menu with Inactive Options of the First Level

        The task is to ensure that after clicking an option of the main menu that has a dropdown menu of the second level, instead of going to that main menu option (this option must not be a link) the user goes to any of the second level options of the dropdown menu.

        If the main menu option has no second level menu, the user should go directly to that main menu option.

        To make it possible, all links must be eliminated from the menu display template code of the type:

        <?if ($arItem["DEPTH_LEVEL"] == 1):?>
                 <a href="<?=$arItem["LINK"]?>" class="<?if ($arItem["SELECTED"]):?>root-item-selected<?else:?>root-item<?endif?>"><?=$arItem["TEXT"]?></a>
        

        The result is as follows:

        <?if ($arItem["DEPTH_LEVEL"] == 1):?>
                 <li><?=$arItem["TEXT"]?></a>
        

        Hiding of a Side Menu According to the Page Properties

        Site template provides for the display of a side menu. This side menu must be hidden only on the pages with a specific property that cancels the display of a side menu.

        Solution:

        If the menu is located in the header, the function GetProperty may not be used because page properties are set after connection of the header. That is why the menu display may be “deferred” as follows:

        • Add the following code in the header where the menu is required:
        $APPLICATION->ShowProperty('menu');
        
        • In the page properties if the menu must be blocked:
        $APPLICATION->SetPageProperty('hide_menu', 'Y');
        
        • In the footer:
        if( 'Y' != $APPLICATION->GetPageProperty('hide_menu') ){
            ob_start();
            echo '<b>verification of the deferred menu!</b>';
            // ....here, the menu is displayed using a component or otherwise.... //
            $APPLICATION->SetPageProperty('menu', ob_get_clean() );
        }
        

        The menu itself is “displayed” in the footer if the hide_menu property value is not set to Y. It will not be actually displayed in the footer. Rather, it will go to the menu property in which the display can be “deferred” “higher” up the code using ShowProperty. If the menu is blocked, the property value of the menu will be empty, and the template will not display anything. If the menu is not blocked, the phrase “verification of the deferred menu!” will be displayed for this example (where $APPLICATION->ShowProperty('menu') is specified).


        Menu and cache

        Quote: I have ran out of the disk space completely. Took a look and it turned out, that the bitrix/managed_cache/MYSQL/menu folder's size is 16Gb! There are 2 menu at the site: horizontal - pagewise navigation and vertical - surfing via the catalog sections.

        Menu cache depends on the page URL. If the are many pages, the cache can be quite large as well. In this case, the more effective solution would be to disable cache in the component menu.

        Navigation chain

        Navigation chain (breadcrumbs) is a sequential list of links to site sections and pages that shows the level of “immersion” of the current page into the site structure.

        A navigation chain helps to display the nesting level of the current page, site section or goods catalog, starting from the home page to the current document. Values indicated in the navigation chain can be specified for each section or document individually.

        Navigation chain provides visitors with tools for easy orientation on site. It allows going to the main site page or going up on one or more levels in site hierarchy.

        Click to enlarge

        This section contains description of template structure and data used for navigation chain building and showing. Also this section includes description of ways for navigation chain showing management.

        Managing Navigation Chain via the System Interface

        By default, the navigation chain point names are managed by the system through the section properties.


        Site section header is set in the service file.section.php located in the relevant section. The following variables may be used in this file:

        • $sSectionName - section header;
        • $sChainTemplate - absolute path to navigation chain template (this variable is used very rarely).

        Example of the file .section.php:

        <?
        $sSectionName = "About us";
        $sChainTemplate = $_SERVER["DOCUMENT_ROOT"]."/en/about/chain_template.php";
        ?>

        The name of the link to the site section in the navigation chain is specified using the field Section Name in the setup form of the section properties.

        The section properties setup form may be accessed as follows:

        • from public section using the "Folder properties" button, located on the administrative panel. This button opens Folder properties form for current site section;

        • from administrative section using the "Folder properties" button, located on the context panel of Site Explorer. This button opens Folder properties form for current folder.

        To modify an item of navigation chain edit the value in the Section Name field and save changes.

        Information: You can exclude a link to any site section from the navigation chain. To do it, delete this section title from the Section Name field.

        Managing navigation chain via the source code

        The AddChainItem() function allows adding additional items to the navigation chain. Both static and dynamic values can be used as the navigation chain item.

        <?
        //--- The first parameter of the function AddChainItem() is the name 
        //--- to be shown in the navigation chain; 
        //--- the second parameter is the link URL.
        //--- Parameter values can be both static and dynamic.
        //--- In this example, section name is a static value, while
        //--- the link is generated dynamically.
        $APPLICATION->AddChainItem("Product details", "catalog.php?BID=".$arIBlock["ID"]."&ID=".$arSection["ID"]);
        
        //--- The next example shows how to generate both parameters dynamically. 
        //--- Current name of the catalog section is used as the name.
        $APPLICATION->AddChainItem($arSection["NAME"], "catalog.php?BID=".$arIBlock["ID"]."&ID=".$arSection["ID"]);
        ?>

        To display the title of a current page in the navigation chain, call the function AddChainItem() in file footer.php, that is included after the main content is generated.

        <?$APPLICATION->AddChainItem($APPLICATION->GetTitle());?>

        You can set some of the navigation chain elements to be displayed with no link, as a common text (for example, display the current page title without link):

        This elements are creating by adding to the navigation chain template (file chain_template.php) the following code:

        if (strlen($LINK)>0)
         $sChainBody .= "<a href="".$LINK."" class='".$strclass."'>".$TITLE."</a>";
        else
         $sChainBody .= "<font class='".$strclass."'>".$TITLE."</font>";

        Some visual components are able to add to navigation chain the current page or news title, or, for example, catalog item name.

        For example, the "Catalog" sequentially adds a catalog sections names according to the catalog structure.

         

        Forum and forum themes names are added to the navigation chain the same way.

        In this case the navigation chain element name for the current page is defined directly in the document with use of the AddChainItem() function.

        Navigation chain show

        Navigation chain is displayed using special code in the site template that uses a deferred function template:

        <?
        $APPLICATION->ShowNavChain();
        ?>

        Function for navigation chain call can be used not only in site template, but in a page or any visual component code placed on a page. For example, this function is utilized in the Search component.

        Note: Trial version contains the additional navigation chain template for use with the Search component. It can be found in the default component template folder /bitrix/components/bitrix/search.page/templates/.default/chain_template.php.

        Navigation chain display may be turned off on certain pages or in a certain site section. Navigation chain display may also be managed using page (section) properties. Do the following:

        • On the setting page of the Site Explorer module, Website Parameters section, create a property for the pages Do not show navigation chain with the code not_show_nav_chain. This property is preinstalled in the system.

        • If the navigation chain must not be displayed on a certain page(s) of a section, set the value for this property as Y.



        • in page source code with use of the function SetPageProperty().

          <?
            require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
            $APPLICATION->SetPageProperty("keywords", "Bitrix, content management, web-solution, site");
            $APPLICATION->SetPageProperty("description", " Bitrix Site Manager – is a powerful Content Management Solution");
            $APPLICATION->SetPageProperty("NOT_SHOW_NAV_CHAIN", "Y");
            $APPLICATION->SetTitle("Bitrix Site Manager");  
          ?>
           

        Managing Navigation Chain Template

        Navigation chain is connected in the site design template using the component Breadcrumb (bitrix:breadcrumb). Any number of templates, i.e. designs, may be set for these components. All of them are stored in the component folder /bitrix/components/bitrix/breadcrumb/templates/<template name>/. All the new templates will be displayed in the component settings. Thus, a navigation chain design template may be set for each site template. The structure of the navigation chain display template is similar to the menu display template.

        Navigation chain and its design template are managed in the same way as when working with other 2.0 components. Using control button in the site edit mode you can easily access the component parameter change form or copy the component template and then edit it.


        Building Navigation Chain and Developing Its External Appearance:

        1. Navigation chain points are gathered starting from site root and ending with the current section. The file .section.php should be connected for each section. If this file has the variable of $sChainTemplate initiated, its value will be used as a path to the navigation chain template. While going through sections, each subsequent value of this variable overrides the previous one. Thus, the “deeper” the section is in the site section hierarchy, the more “important” its variable $sChainTemplate becomes.
        2. If the path to the template is not determined after points of the navigation chain have been collected, the existence of the file is checked:

          /bitrix/templates/current site template ID/chain_template.php

          If such a file exists, the path to it is adopted as the path to the navigation chain template. Otherwise, the default value is used:

          /bitrix/templates/.default/chain_template.php

        If navigation chain is displayed, the navigation chain template will be connected each time at the next point of the chain. That is why its main task is to provide for the external appearance of only one point of the chain.

        The main variables used in the template are as follows:

        • $sChainProlog - is the HTML code displayed before the navigation chain;
        • $sChainBody - is the HTML code which determines external appearance of one point of the navigation chain;
        • $sChainEpilog - is the HTML code displayed after the navigation chain;
        • $strChain - is the HTML code of the entire navigation chain collected by the time of template connection.

        All variables shown will store HTML code determining the external appearance of the navigation chain.

        In addition, the following additional variables will be available in the template:

        • $TITLE is a header of a point of the navigation chain;
        • $LINK is a link on a point of the navigation chain;
        • $arCHAIN a copy of array of elements of the navigation chain;
        • $arCHAIN_LINK a link to array of elements of the navigation chain;
        • $ITEM_COUNT the number of array elements of the navigation chain;
        • $ITEM_INDEX a sequential number of a point of the navigation chain.

        Example of the navigation chain component template:

        <?
        if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
        
        if(empty($arResult))
          return "";
        
        $strReturn = '<ul class="breadcrumb-navigation">';
        for($index = 0, $itemSize = count($arResult); $index < $itemSize; $index++)
        {
           if($index > 0)
                  $strReturn .= '<li><span>&nbsp;&gt;&nbsp;</span></li>';
           $title = htmlspecialcharsex($arResult[$index]["TITLE"]);
           if($arResult[$index]["LINK"] <> "")
                  $strReturn .= '<li><a href="'.$arResult[$index]["LINK"].'" title="'.$title.'">'.$title.'</a></li>';
           else
                  $strReturn .= '<li>'.$title.'</li>';
        }
        $strReturn .= '</ul>';
        return $strReturn;
        ?>

        Note: When connecting the navigation chain using the function ShowNavChain(), its template may be additionally set for a separate site section.

        To do so, the variable $sChainTemplate must be determined directly in the file .section.php where the complete path to the navigation chain display template is set. Example:

        $sChainTemplate = "/bitrix/templates/demo/chain_template.php"

        The navigation chain template may also be set when using the function ShowNavChain() as one of the parameters of the function.

        $APPLICATION->ShowNavChain("/bitrix/templates/.default/chain_template_bottom.php")

        Examples

        How to Add Own Point to the Navigation Chain?

        Use the method AddChainItem() :

        <?
        $APPLICATION->AddChainItem("About us", "/about/index.php");
        ?>
        

        Only infoblock names are shown in the navigation chain. In addition, page headers (e.g., contacts) and “Home” header are not displayed. What is the problem?

        The value N must be set in the page properties in the field NOT_SHOW_NAV_CHAIN of the page properties.

        For the remaining pages, it is necessary to check if the headers are set in the section properties. It is section headers that are used to create points of the navigation chain.

        The home page can also fail to display due to the incorrectly set option Ordinal of item from which to build the chain in the parameters of the Breadcrumb component: 0 (default value) means that the navigation chain will be built starting from the site root. If the field Path for which the navigation chain is to be built is completed, the point number is included in the indicated path.


        The navigation chain has the catalog name repeated twice: Home/Catalog/Dresses/Dresses.


        The first link to the catalog looks as follows: /catalog/dresses/

        and the second: /catalog/dresses/section.php?SECTION_ID=0

        The first name is taken from the properties of the dresses directory (file .section.php) and the second is determined by the component located on the page (in this case, the address of the page section.php).

        Note: For example, the parameters of the News (bitrix:news) component contain the relevant options: Include information block into navigation chain and Add Section name to breadcrumb navigation.

        The element repetition in the navigation chain can also be caused by the presence of several components on the pages that are set to add their points to the chain.


        How to ensure that the navigation chain has only a physical section present?

        The complex component News is used to display a section from the infoblock. Apart from the physical section, an unclickable name of the section of the infoblock proper appears in the navigation chain. Neither section nor infoblock is included in the navigation chain settings.

        Add the following line in the template:

        "ADD_SECTIONS_CHAIN" => $arParams["ADD_SECTIONS_CHAIN"],

        Advertisement

        Several different types of advertising may be shown on the site. It may be both standard banner advertising or written advertising areas. Advertising may be permanent or shown with a certain probability set by the administrator. An advertising show may be regulated by specific site sections etc. Advertising is created and managed using tools of the Advertising module.

        Advertising is shown in specifically allocated areas of the site design template – advertising areas.

        A banner may be connected to the advertising area using the Banner component (bitrix:advertising.banner). The component displays the banner of a set type.

        Note: This component does not take into account the targeting by keywords. If the targeting must be taken into account, the function $APPLICATION->ShowBanner() should be used. The code used to connect a banner in the advertising area using PHP function of ShowBanner() is as follows:
        <?
        //--- Example of placing an advetising area in the left part of the design. 
        //--- Any other type can be selected similarly:
        //--- TOP, BOTTOM, COUNTER,… (first parameter of the function)
        //--- Both predefined and user-defined types can be used. 
        //--- Other two optional parameters can be used to specify the HTML code 
        //--- that is to wrap the advertising area.
        
        $APPLICATION->ShowBanner("LEFT", '<div align="center">', '<br></div><br>');
        ?>

        The type of advertising available for display in this area must be determined for each advertising area. Advertising banners of the type LEFT1, LEFT2, and TOP are used on the image shown above.

        Banner types

        Advertisement type is a conventional name for a group of advertising banners that share a common feature. For example:
        • Show place – all banners must be displayed in a specific place on the site page.
        • Subject matter (for example, the same products are advertised).
        • Advertiser (advertising for a specific company).
        • etc.

        The name of the advertising block type is set at the discretion of the administrator. For example, the TOP, LEFT, BOTTOM, etc. type may be set for the advertising set at the top of the page.

        Important! Banners from the same group should have the same size. It will permit you to prevent page deformation during display of the advertising.

        Advertisement types are managed through the administrative interface of the Advertising module (Control Panel > Services > Advertising > Banner types):

        An advertising banner or a list of banners of a selected advertising area can be managed directly from the public part of the site. For this, switch to the Edit mode and use one of the advertising area control buttons:

        Controlling advertising shows using keywords

        One of the methods that can be applied to control the banner shows and to target the advertising precisely is using keywords. The distinctive advantage of this method is that it allows to drive the advertising campaign aimed to reach the well-defined target group among your visitors.

        Using keywords, it is possible to:

        • Organize the display of advertising aimed at a specific group of site users. I.e. display advertising on the pages visited primarily by these users or pages that the subject matter may be of interest for this group of users.
        • Restrict the advertising shown on a site page, for example, to the extent the advertising content is connected to the information shown on the page.

        Control Mechanism

        Advertising on the site pages is controlled using desired and mandatory keywords of the site page and a set of keywords of the advertising banner. Two types of special keywords are used to manage the advertising shown on pages:

        • banner keywords;
        • page keywords:
          • desired: if a site page is assigned the desired keywords, all the banners that have at least one matching keyword in their keyword sets can be shown on that page.

            If no banners with the matching keywords can be found, then the page will show banners that are not assigned any keywords. In this situation, the system uses own standard algorithm to select banners to be displayed.

          • required: if a site page is assigned the required keywords, all the banners that have all keywords in their keyword sets can be shown on that page.

            If no such banners can be found, then the page will show banners that are not assigned any keywords. In this situation, the system uses own standard algorithm to select banners to be displayed.

        If the system fails to find banners satisfying any of the keywords, the page will show banners for which no keywords are set. These banners are selected and displayed based on a standard system algorithm (permitted/forbidden pages of contracts and banners, user groups, banner types, etc.).

        The general procedure for banner display on specific pages is as follows:

        • Determine the advertising available for display on specific site pages.
        • According to the tasks at hand, sets of specific keywords are determined for site pages.
        • A required set of keywords is set in the settings of advertising banners.

        Banner keywords are set in the Keywords field on the page of banner creation/editing, Targeting bookmark (Control Panel > Services > Advertising > Banners).

        Desired keywords of the page are managed using a special property adv_desired_target_keywords. Desired keywords of the page may be set using the function of SetDesiredKeywords.

        Please note! By default, if page code does not provide for any keywords for advertising, the function of SetDesiredKeywords is deemed using properties of the page adv_desired_target_keywords as a value parameter. If it is not set, the value of the keywords property is used as a function parameter.

        The SetDesiredKeywords method is called automatically at the time of page assembly; it must not be additionally called in the file header.php if there is no need to redefine the keywords for the advertising shown.

        The required keywords of the page are set using a preset function of SetRequiredKeywords of the system.

        Managing Template Service Data

        Editing Service Areas

        If a visual editor is used for creating (editing) a template, the service areas can be managed in a special form. This form can be accessed using the button Edit template areas located in the editor’s panel.

        Area editing form consists of two bookmarks: Top Area and Bottom Area.

        The top part of the template down to the <body> tag is edited in the first bookmark. You can set the contents of the area with default values. To do so, press the button to insert a set of standard functions into the form, such as page encoding, header and page metadata display, connection of style files, etc. Similarly, the bottom part of the template is edited in the Low part bookmark in which the contents can also be set using the default values.

        Attention! The use of the functions of ShowMeta(), ShowTitle(), ShowCSS() etc. permits you to initialize separate elements directly from a page script or from a component. E.g., page header may be added after displaying script output. Thus, if in earlier version’s page header initialization was required prior to connecting the main design, now the page header can be set directly from code in the page working area.

        Managing Page Encoding

        Support of any number of languages is one of important features of Bitrix Framework. The system permits:

        • Using a multi-language interface in the administrative section.
        • Creating any number of sites (depending on the license in different languages within one system.

        Note: The number of languages used in the system does not depend on the number of sites.

        This section contains information about the use of encoding for the correct display of information on the site pages. Having studied the section you will get an idea about the main principles of use and also about the ways to setup and connect different encodings.

        Use of Encodings

        In order to display national characters correctly, the appropriate encodings are used. When showing a page, the browser recognizes the encoding used and displays the characters accordingly.

        The list of code tables used to display the characters of the English, and German languages is provided below:

        LanguageEncoding
        English (en)windows-1252, iso-8859-1, latin1
        German (de)windows-1252, iso-8859-1, latin1

        The complete list of encodings used for different languages is provided in the product documentation.

        Note: Starting from version 7.0 the product supports a universal UTF-8 encoding. Using this encoding, the site contents may be simultaneously displayed in different languages.
        If UTF-8 is not used but the system requires a combination of various languages, a code table must be determined for each language that will be used to display text messages.

        Attention! Page encoding and database table encoding must be the same.


        Encoding shall be set separately for the administrative and public sections:

        • The encoding used in the public section shall be set up for each site (Control Panel > Settings > System settings > Websites > Websites):

          The encoding is set based on the language used on the site. In addition, when setting the language parameters, the time and data format can be set, which will permit you to display these data in the public section correctly (for example, during display of news, catalog of goods, etc.).

        • The encoding for the administrative section of the site is set up using the form of language parameters used in the system (Control Panel > Settings > System settings > Language Parameters > Regional Options and Culture).

          In addition, the time and data format may be determined when setting the language parameters.

          The indicated format will be used for the display of the date and time in the administrative section of the site.

        Determining the Current Encoding

        The current encoding used in the public section of the site is determined using the php constant LANG_CHARSET substituted to the site template header area.

        When applying a template to the site, the value of encoding parameter set in the site settings is requested. The constant LANG_CHARSET is given a value equal to the value of the encoding parameter.

        Example of the code used to establish page encoding is provided below:

        < head >
        …
        < meta http-equiv="Content-Type" content="text/html; charset=< ?echo LANG_CHARSET? >" >
        …
        < head > 

        Managing Document Header

        The use of header helps to draw users’ attention to a site page and also to give a general idea about its contents and purpose. Additional page headers should be set to reflect as a header of the browser window so that the user can accurately determine the window where the page they need is open.

        Page header and web browser window may be created and changed both using the tools of the administrative and public section.

        Note: Additional header for web browser window can be set using the title property reserved in the product.

        For convenient use of the property its name must be set (for example, Additional header (web browser window header)) in the Site Explorer module setting.


        Please refer to the page Work examples for more information about setting up several headers.

        Note: Some components may independently set a header and it should be taken into account.

        Managing Header in the Code

        Setting up and Displaying Page Header

        Page header may be set up as follows.

        • When editing the document with the help of the incorporated editor (in the Text, PHP, or HTML mode). In this case, the page header is set by way of substituting the following function in the code:
          <?
          $APPLICATION->SetTitle("About Company");
          ?>
        • Document header can be set dynamically by one of the components located on the page. In the example below, the name of the current information block is used as a page header:

          <?
          $arIBlock = GetIBlock($ID, "news")
          $APPLICATION->SetTitle($arIBlock["NAME"]);
          …
          ?>

        The document header is displayed by placing the function of ShowTitle() at the place of page header display:

        <H1><?$APPLICATION->ShowTitle()?></H1>

        If the function of ShowTitle() uses the parameter false for page header display, it means that there is no need to check the value of the title property to set the page header (e.g., Additional header (web browser window header)).

        <H1><?$APPLICATION->ShowTitle(false)?></H1>
        

        I.e. the header set by the SetTitle() function will be used as a page header.

        Note: For more information about the ShowTitle(false) function, please refer to the page Work examples.

        Set Up and Display of Web Browser Window Header

        Web browser window header is displayed using the ShowTitle() code located in the <head> area of the site design template.

        <head><title><?$APPLICATION->ShowTitle()?></title></head>

        There are various ways to set web browser window header. By default, the header is set in the title property of the page (e.g., Additional header (web browser window header)). If the value of this property is not indicated the browser window header will be set as equal to the current page header.

        Note: Browser window header can also be set using the function of SetPageProperty() or in the public part of the site. For more details, please refer to the page Work examples.

        Attention! If several identical header setting functions or components are located on the page, the header set in the last (the bottommost on the page) function/component will be used.

        Work Examples

        In this lesson we will review, stage by stage, the procedure for creating a page with different headers of the browser window and the page itself. Site template may require changes during this work.

        • Set a page header from the interface or using the function of $APPLICATION->SetTitle(“Page title”):
          <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
          $APPLICATION->SetTitle("Page title"); ?>
          ....
          <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php"); ?>
          

        • Add additional header through interface or using the function of $APPLICATION->SetPageProperty('title','Browser title'):

          <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
          $APPLICATION->SetTitle("Page title"); 
          $APPLICATION->SetPageProperty('title','Browser title'); ?>
          ...
          <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php"); ?>
          

          The function of $APPLICATION->SetPageProperty() in the system has a priority over the function of $APPLICATION->SetTitle(). That is why the browser and page headers will display the contents of the function exactly:

        • If different headers are to be used for page and web browser window, check the parameters of the CMain::ShowTitle() method displaying page header in the site template.

          Replace:

          $APPLICATION->ShowTitle()

          with:

          $APPLICATION->ShowTitle(false)

          In this case, the value of page property SetPageProperty('title','Browser title') will be ignored, and the header set by the function of SetTitle() will be used as a page header instead.


          Let us review the difference in operation of the function $APPLICATION->ShowTitle() with the false parameter using the following example, without changing the template code:

          <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
          $APPLICATION->SetTitle("Page title"); 
          $APPLICATION->SetPageProperty('title','Browser title'); 
          $APPLICATION->ShowTitle(); 
          <br>
          $APPLICATION->ShowTitle(false); ?>
          ....
          <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
          

        Managing Metadata Values

        As a rule, the main purpose of using metadata is the optimization of site for search systems. Search systems use metadata for indexation of site documents.

        Keyword mechanism and description of site pages and sections are good examples of product metadata management. By default, the installation package of the product includes the management of exactly these two types of metadata (similarly, the list of possible options may be extended).

        Managing Metadata Values through Visual Interface

        In order to have a possibility to manage metadata values, the relevant properties must be created in the settings of the Site Explorer module (Control Panel > Settings > System settings > Module settings):

        Important! The names of property types used for managing page metadata must coincide with the names of meta tags in HTML. E.g., keywords and description property types must be the same as names (name) of the relevant meta tags: keywords and description.

        Note: A set of properties can be set separately for each site that works under control of the system.

        Attention! The values of properties set for a folder will be used by default for all pages and subsections of the relevant site section (if no proper values of these properties are set for them).

        More detailed information about properties management is available in the Properties of pages and folders section.

        Managing Metadata in a Code

        In order to display the metadata values in the page code the function of ShowMeta() located in the prologue of the site design template must be used:

        < head >
        …
        < ?$APPLICATION->ShowMeta("keywords")?>
        < ?$APPLICATION->ShowMeta("description")?>
        …
        </head>

        Let us assume that the following values are set for keywords and description properties of a page:

        The function of SetPageProperty() will apply the values of these properties to a page:

        <?
        $APPLICATION->SetPageProperty("keywords", "sofas, chairs, office furniture, kitchen furniture, nursery furniture");
        $APPLICATION->SetPageProperty("description", "We use only the high quality equipment to manufacture our furniture to achieve the superior quality.");
        ?>
        Note: Section properties can be set up using the function of SetDirProperty() (e.g., in the code of the file .section.php):
        < ?
        …
        $APPLICATION->SetDirProperty("keywords", "design, web, site");
        
        $APPLICATION->SetDirProperty("description", "Site Manager System"); … ?>

        In this case, the following HTML code will be introduced into the page code as a result of the function ShowMeta().

        <meta name="keywords" content="design, web, site">
        <meta name="description" content="Site Manager System">

        If no property value is set for the page itself, the value of the property of a higher section (recursively to the site root) will be selected. If the property value is not determined, the value of the relevant meta tag will remain undetermined. Page properties may be set dynamically from the code of the components located on the page. For example, for pages showing catalog or news information, the page properties may be set in accordance with determined properties of infoblock elements:

         $APPLICATION->SetPageProperty("description",$arIBlockElement["PROPERTIES"][$META_DESCRIPTION]["VALUE"]);

        In this case, the value of the property of an information block element with meta_description code will be used as a value for the description page property. Thus, the properties of keywords and description may be created for catalog elements and dynamically substituted to the page code.

        CSS management

        Generally CSS is a group of rules defining layout of some page elements. The CSS technology allows to store all information about page layout, utilized fonts and colors, menu layout styles and etc. in one or several exact files.

        Using the CSS simplifies the page designing. Moreover, if you change a site design you do not need to modify each site page. It may be enough to make necessary modifications in corresponding CSS files.

        For example, using the CSS you can modify a forum layout (in this example forum CSS files are stored separately from the site template CSS).


        .forumborder {background-color:#B8B8B8;}
        .forumhead {background-color:#DDDDDD;}
        .forumbody {background-color:#F5F5F5;}
        .forumbodytext {
        	font-family: Tahoma, Arial, Verdana, sans-serif;
        	font-size: 80%;
        	color:#000000;
        }
        .forumheadtext {
        	font-family: Tahoma, Arial, Verdana, sans-serif;
        	font-size: 80%;
        	color:#011B46;
        …

        .forumborder {background-color:#96C0FA;}
        .forumhead {background-color:#A9CAF7;}
        .forumbody {background-color:#D7E6FB;}
        .forumbodytext {
        	font-family: Tahoma, Arial, Verdana, sans-serif;
        	font-size: 80%;
        	color:#042A69;
        }
        .forumheadtext {
        	font-family: Tahoma, Arial, Verdana, sans-serif;
        	font-size: 80%;
        	color:#011B46;
        …

        Stylesheets are uniquely customized for each site template in the system; each stylesheet set is stored in the folder of the corresponding template, as well as other files comprising the interface of the template. Stylesheets used in site templates are stored in the styles.css files. Additional stylesheets used for exact site elements (for example, for forum layout) a stored in files with such names as forum.css.

        Separate CSS files storage

        Bitrix software products features mechanism that allows to perform a separate storage of CCS files:

        • Styles, used in the site template are stored separately in the template_styles.css. file. It is the main CSS file of a template.
        • Styles, used when customizing the page content (site styles) are stored in the styles.css file. Styles that are stored in this file are displayed in the styles drop-down list when editing pages in the visual editor.

        Designation of these files can be divided arbitrarily, depending on the context. Specifically, the styles that are responsible for content design not only for a template, but for the visual editor, can be stored in the styles.css file.

        For example: corresponding display styles are defined for all headers on the site, i. e. they are used both to define page content and design of information block content, that is located outside the #WORK_AREA#. As a result, these header styles can be placed in both the template_styles.css and styles.css file. But because both of these files are connected to the template, all the header design styles must be placed only into the styles.css file, because it is connected to the page as well.

        If headers must be modified when editing a page in the visual editor, then correspondingly the styles that are responsible for the site template design must be placed in the template_styles.css file, and for the visual editor - into the styles.css file.

        Splitting styles between those two files must be done carefully. For example, the site background must be done in grey color, and the background color in the visual editor must be red - the background-color:#ccc; must be specified for the body tag inside the template_styles.css file; and for the same tag - background-color:#ff0000; must be defined in the styles.css file.

        Files stored on the page site are connected as follows:

        1. styles.css
        2. template_styles.css

        As a result, the body background will become grey at the site, because the style in the last connected template_styles.css file will supersede the style, defined in the styles.css file. The background in the visual editor will become red, because the visual editor content is the iframe that connects styles only from the styles.css file, and they are inserted directly into the head area via the <style> tag.

        If to add the increase of priority via !important to the styles.css file, the style from this file will supersede the style, defined in the template_styles.css; the site background will become red as well, despite the fact that the file of template styles is connected last.

        Site styles

        The site stylesheet (the styles.css file) is customized at the design template edit page (Control Panel > Settings > System settings > Websites > Site templates) at the Site Styles tab. The important element when creating the stylesheet is the style name. The names must be created only for those styles that are planned to be used when editing pages in the visual HTML editor ((Format) section).

        The styles will be available in the visual editor from the drop-down list under the names, defined in this form. The name that are specified here, will be stored in the /.styles.php file (file with style names).

        Creation of design template stylesheet (the template_styles.css file) is performed in the Template styles tab of the site template edit form

        Managing CCS styles in the visual HTML editor

        Style names are important elements when customizing a stylesheet. Style names can be added directly into the .styles.php file. This file located in the site template folder and has the following format:

        <?
        $arStyles = Array(
          "information-block-head" => "Information block header",
         "content-block-head" => "Header with padding",
         "information-block-body" => "Information block element text",
         "tablehead" => "Table - header",
         "tablebody" => "Table - body",
        );
        return $arStyles;
        ?>

        Additionally, the style names can be added in the template edit form at the Site styles tab. In this case, the .styles.php file is created automatically.

        Style names will be displayed in the drop-down list in the visual editor.

        If the Permit the display of styles without names in the HTML visual editor option is selected the Visual editor tab in the module Structure configuration settings form, then all available styles for the page will be presented in the list, including those styles, for which the names are not specified. The name, defined when creating the style: information-block, content-block and etc. will be used to designate the style with unspecified name.

        Also, the styles, applicable to specific html-elements of the page (for examples, table borders, cells, images and etc.) can be created in the stylesheet. When selecting such elements, the list will show the styles available for them.

        Example of Site Template Stylesheet

        Styles for left menu:

        .leftmenu, .leftmenuact {font-family: Arial, Helvetica, sans-serif;
         font-size:12px; font-weight: bold; text-decoration: none;}
        .leftmenu {color: #3F3F3F;}
        .leftmenuact  {color:#FF5235;}

        Styles for top menu:

        .topmenu  {font-family: Arial, Helvetica, sans-serif; font-size:12px;
         font-weight: bold; color: #FFFFFF; text-decoration: none;}
        .topmenuact  {font-family: Arial, Helvetica, sans-serif; font-size:12px; 
         font-weight: bold; color: #FAC535; text-decoration: none;}

        Styles for table:

        .tableborder  {background-color:#9C9A9C;}
        .tablehead  {background-color:#D8D9DA;}
        .tablebody  {background-color:#F8F8F8;}
        .tablebodytext, .tableheadtext {font-family: Arial, Helvetica, sans-serif;
         font-size:12px;}
        .tablebodytext {color:#000000;}
        .tableheadtext {color:#000066;}

        Connecting CSS

        Stylesheets are connected to the site template in the prolog area via the ShowCSS() function.

        <?
        $APPLICATION->ShowCSS(); 
        ?>

        The ShowCSS() function connects CSS file from the current site template, as well as all additional styles, defined for the this page by the function SetAdditionalCSS().

        <?	
        $APPLICATION->SetAdditionalCSS("/bitrix/templates/demo/additional.css");
        ?>

        Additional styles can be used, for example, to customize design elements of forum, web forms, tables, certain menu types, etc.


        When using the ShowCSS() function without parameters, styles will be connected as the link to the CSS file:

        <LINK href="/bitrix/templates/demo/styles.css" type="text/css" rel="STYLESHEET">

        The styles connected via the SetAdditionalCSS(), will be included into the page code using the PHP function require() (i. e. such styles will be fully included into the final page code).


        When using the ShowCSS() function with the parameter false, the CCS file for the current design will be included into the page code via the require():

        <?
        $APPLICATION->ShowCSS(false);
        ?>

        Setup of External Appearance of Additional Elements of the Site Design

        Setup of Error Messages

        It is often necessary to setup error messages to have an error message clearly shown in the site design.

        In order to set up the design of a database connection error message, the file /bitrix/php_interface/dbconn_error.php should be edited.

        In order to set up the design of a database query error message, the file /bitrix/php_interface/dbquery_error.php should be edited.


        Setup of a File to be Connected when Closing the Site

        In order to set up the design of a file to be connected when closing the public part of the site the file /bitrix/modules/main/include/site_closed.php should be copied and placed into /bitrix/php_interface/<language>/ or into /bitrix/php_interface/include/.


        Setup of a Page by Page Navigation Design

        A page by page information display is organized using the PHP function of NavPrint(), a function for displaying links for a page by page navigation. The following parameters may be used to manage a page by page navigation design: NavPrint($title, $show_allways=false, $StyleText="text", $template_path) where:
        $title – is the name of displayed elements;
        $show_allways – if the value of the parameter is false, the function will not display navigation links should all entries fit on one page. If true, the links for a page by page navigation will always be displayed;
        $StyleText – CSS class of a font to display navigation links;
        $template_path – a path to the template for showing navigation links.

        Information Blocks

        Information Blocks - is a module that permits making catalogs and managing different types (blocks) of homogeneous information. Different types of transient content may be posted using information blocks: goods catalogs, news blocks, reference books, etc.

        Information blocks is a key point of Bitrix Framework. Almost everything that is being done in the system is more or less connected with this module, even if it is not displayed directly.

        Information blocks constitute the next level of abstraction over DBMS regular tables; it is a kind of "database within a database"; That is why all of the rules used during database design partially apply to information blocks.

        Infoblocks is an entity creating 4 tables in the database’s physical structure. These 4 tables remain unchanged during changes of data structure – object types, object instances, object properties, and object property values.

        This approach has the following advantages:

        • Convenient control over the structure data from the data application,
        • Versatility of methods,
        • Common data structure for any project,
        • Possibility to change data types for fields many times without eliminating data themselves.

        Disadvantages:

        • Higher performance requirements,
        • Nontransparent direct data access.

        Specifics of the Arrangement of Elements by Sections

        Arrangement of the elements of infoblocks by sections may greatly facilitate the navigation around an infoblock in the administrative interface. Faceted arrangement makes navigation even more convenient.

        In addition, an infoblock with sections already contains a description of how it should be displayed to the user. I.e. it is sufficient to bypass it using the very common tree-avoidance algorithm in order to display it in the most convenient form.

        The work specifics consist in a certain inconvenience when it comes to working precisely with sections, one by one. Thus, for example, if in addition to the Articles infoblock there is a Books infoblock, it is highly possible that its elements will also require classification by publication date and by subjects. In this case, you will have to create the same section structure once again. Furthermore, it will not be very easy to show, for example, the list of all of the materials (articles and books) on the same subjects by arranging them by publishing date. Moreover, it will be difficult to display the common index of subjects in the website menu.

        In this case, a separate infoblock Subjects should be formed adding a reference type property Subject to the Books and Articles infoblocks, and a Publication Date property of the Date type. Then, it will be easier to navigate in the administrative interface using these property filters.

        Working with Infoblocks Using Standard Means

        Work Order

        When creating a web section using information blocks, a certain work order should be followed. This order may differ depending on the degree of project readiness and a complexity of a specific assignment. Almost in every case you will have to:

        • Think through the entire structure of infoblocks carefully.
        • Create the necessary infoblock types and set up their parameters.
        • Create infoblocks and set up their parameters.
        • Create structure inside an infoblock.
        • Create infoblock elements.
        • Create a physical page (in case a complex component is used) or pages (if simple components are used), locate a component (components) on it, and then set up its parameters.
        • Adjust the component operation according to the assignment and website requirements (component template customization, use of the result_modifier.php or component_epilog, customization of the component itself).

        Standard Capabilities

        Built-in tools of the Information Blocks module are extensive enough. There is no limit either to infoblock types, infoblock number, number of properties of each infoblock, or number of sections or elements.

        Infoblock Properties

        Elements of each infoblock have a set of system properties that may be extended with user properties. The infoblock properties differ according to their types:

        • String - the property value is set as a text line;
        • Number - the property value is set as a number;
        • List - the property value is selected from the list;
        • File - a file is used as the property value;
        • Link to section - using this property it is possible to link an element of this infoblock and sections of another information block;
        • Link to elements - setting links among information block elements one by one;
        • HTML/text - the property value is set as a text with HTML tags;
        • Link to elements by XML_ID - the link is stored as a string, and the value is XML_ID of the linked element;
        • Bind to Google Maps - setting a link between an infoblock element and a Google Map component;
        • Bind to Yandex Maps - setting a link between an infoblock element and a Yandex.Map component;
        • Counter - is a substitute to autoincrement for databases. The value will increase by one each time an infoblock element is added. The starting value is set at random. This feature can be used for incoming document registers etc. where the continuous numbering of documents is needed.
        • Link to user - a link between an element of this infoblock and system users can be set using this property
        • Date/Time - the property value is set as a date/time;
        • Video - setting a link between a list element and a media file;
        • Link to elements (drop-down list) - setting a link between elements using a list;
        • Link to forum topic - using this property, a link can be set between an element of this infoblock and forum subjects;
        • Link to file (on server) - using this property a link can be set between an infoblock element and a file on a remote server;
        • Link to elements autocomplete text box - setting a link to autocomplete elements;
        • Link to product items (SKU) - setting a link to products (SKU).

        Each type of properties has its own set of parameters established in relevant forms. Properties may be multiple and of mandatory completion.

        Note. It is recommended that only the characteristics that are to be used as filters should be converted into separate properties. The remaining characteristics should be incorporated in product description as text.

        Properties of Infoblock Sections

        It is possible to set the user properties for infoblock sections. The code of user fields must always contain the prefix UF_. The list of field types is somewhat smaller than that for the infoblock proper:

        • Video
        • Text
        • Integer
        • Number
        • Date/Time
        • True/False
        • File
        • List
        • Link to information block sections
        • Bind To Information Block Elements
        • Template
        • Poll

        Similarly to the properties of the infoblock itself, section properties may be multiple and mandatory. In addition, it is possible to establish whether the property will participate in search and sorting, whether the user can edit the property value, and whether the property will be displayed in the general list of properties.

        Export-Import

        Adding a big number of infoblock elements manually is a very laborious task Data import/export can be applied using different file formats in order to make the information adding process easier. The following formats are supported:

        • RSS
        • CSV
        • XML

        Export and import in RSS format are arranged using the special components RSS news (export) (bitrix:rss.out) and RSS news (import) (bitrix:rss.show), accordingly.

        Data from the infoblock are exported to CSV file using the form Information block export (Control Panel > Content > Information blocks > Export > CSV). Data stored in a separate CSV file, are imported to the information block using the form of Information block import (Control Panel > Content > Information blocks > Import > CSV).

        Note: Starting from module version 14.0.5, the section nesting depth for CSV export/import is determined by the settings of the Information Blocks module.

        In earlier versions, export to CSV was limited to three nesting levels.

        Note: If an infoblock is to be exported as a product catalog, the following path should be used: Control Panel > e-Store > Settings > Data export. In addition, import from a CSV file is possible: as a product catalog. In this case, it is necessary to use the following path: Control Panel > e-Store > Settings > Data import.

        The functional capacity of the infoblock import/export feature permits moving to and from XML not only the contents of the infoblocks but also their properties and images. Export can be made at the page Export to XML (Control Panel > Content > Information blocks > Export > XML). Import can be made at the page XML Import (Control Panel > Content > Information blocks > Import > XML).

        Setting Up Fields of the Element Adding Form

        The task of creating a large number of elements of an information block can be made easy by using the presettings of the fields of the element adding form. The settings are made in the Administrative section using the tab Fields of the infoblock editing form (Control Panel > Content > Information blocks > Information block types).

        Note: The tab Fields permits you to establish preset data for the infoblock element fields.

        This form has three columns:

        • Element field – a field to which the setting will apply
        • Active – a checkbox means that this field is mandatory. The element will not be created if the relevant checkbox is not checked.

          Note: There are fields that are mandatory according to the system requirements. These fields cannot be checked or unchecked.
        • Default value – defines a default value to be acquired by this field. If any line in this column is empty, it means that this field cannot be given a default value.

        Let us take a closer look at certain fields:

        The form type for this field that will open by default shall be set in the fields Description type type (for a preview text and a detailed description): a text field or a visual editor. You have to decide what type of field suits you better. For a preview text, the – text description type is recommended; and for a detailed description – html. It permits using text with html tags when importing data from a CSV file. In this case, the description will be imported with an already set format.

        Fields in the Default value can be used as a prompt for the content manager about the data that should be introduced in the field. For example, a text can be entered in the fields Preview text and Detailed description to help the content manager to complete forms. Let us suppose that the comment for the preview text will be: "Introduce a summary of the piece of news", and for the detailed description: "Introduce full text of the piece of news".

        Note: Similarly, the tab Section Fields can be set up.

        System Processing of Photographs

        As a rule, several pictures are used on websites for an infoblock element: a small preview and a big picture. If multiple elements must be created, normally it takes a lot of time to create small pictures from big ones. However, it is actually not necessary. Just upload one big picture, and indicate in the Fields tab the way you would like it to be processed.

        Let us set up the infoblock operation in such a way that when we upload one big picture we obtain one small and one big picture, both of a preset size and with a preset quality.

        • Check the box Auto-create preview image from detail image. Now, the preview picture will be created from the big picture.
        • Check the box Auto-resize large images. The picture will be scaled down to the size set in the fields below.
        • Enter the picture size in pixels in the fields Maximum width and Maximum height. Picture size will be change proportionally. Setting the size in these fields guarantees that a preview picture will not accidentally damage the page layout.
        • Check the box Ignore scaling error, to display the picture “as is” in case image processing fails.

          Note: The preset image resizing mechanism works with traditional graphic formats: jpg, gif and png. If you upload a picture in a different format, for example bmp, it will not be processed. The system will either display the picture in its original size, or show a message that the picture cannot be processed. It depends on the checkbox Ignore scaling error as to which of these two options will occur.

        In order to set up processing quality, you can also use the options of the Fields tab. Please note that free server resources are required to use these fields.

        • Check the box Preserve quality when scaling.
        • Set an acceptable quality level in percentage in the field Quality.

        Enter similar settings in the Detailed image fields in order to set up a picture shown in detailed view.

        Setting Up the Element Adding Form Display

        The form for editing/adding an infoblock element can be changed and set up according to the requirements of the website content manager. The changed form is displayed and works both for adding/editing an element to the administrative part and for adding/editing an element to the public part.

        Note: The form can be set up only from the administrative part of the website.

        Follow the steps below in order to set up the form:

        • Go to the page containing the list of the infoblock elements for which a form should be set up.
        • Open for editing any element of the infoblock and click Settings .

        • The window Customize form appearance will open up:

        This form permits you to rename and change the sequence order both for fields and tabs of the element editing form. You can also move fields not only within one tab but also from one tab to another, thus forming a visual appearance of the element adding/editing form which is convenient to you.

        Set up tabs and fields as necessary:

        • In the list of Tabs (Selected fields) check the tabs (fields) you do not need by using the mouse and a Ctrl button and click Delete.
        • Using the buttons Up/Down, change the order of the chosen tabs or fields.
        • Edit the names of the chosen tabs/fields using the button Rename.
        • In order to add a new tab (or field separator) to the form, click Add. In the window that opens up, enter the name of the tab (or separator) and click OK:

          New tab (or separator) will appear in the list of Tabs (or Selected fields).

        • In order to move fields from one tab to another, first select a tab in the list of Tabs to which fields are to be added. Then, select a tab in the list Available tabs and the necessary fields in the list Available fields, that will be moved. After that, click ">":

        The option Set these settings for all users by default permits you to establish form settings for all users as default settings.

        Save the changes made. Now, you have a changed infoblock element editing form with no unnecessary fields where the remaining fields are grouped up in a necessary order.

        Once set up is completed, the form looks like this:

        This form will also be used to create/edit elements in the public section.

        Note: The settings of the external appearance of the element creating/editing form can be reverted in the administrative part of the website using the command Disable custom form settings from the menu of the Settings button:

        Setting Up Prompts

        It is rather unusual to find a highly qualified employee working as a content manager in website support. Moreover, in case of a large number of created properties of an information block, even a highly qualified employee may experience difficulties when it comes to deciding which value should be entered in any other property field. A website administrator can create prompts to make it easier for a content manager to complete forms. The prompts look as follows in the form itself:

        Just use the field Info caption for '?' sign in the properties form of the information block.

        Storage Types for Infoblocks

        When creating information blocks, infoblock properties should be stored in a separate table, and all of the property values of each element shall be stored in the same string. This technique is called Information Blocks 2.0 and permits you to significantly speed up system operation and also to lift a number of restrictions of the previous version of infoblocks. For example, now there is no need to make the additional request CIBlockElement::GetProperty when selecting property values with the function CIBlockElement::GetList.

        Infoblock 2.0 performance options:

        • When selecting elements, property values can be obtained at once, because the number of attached tables in the request is not increased with each property and is always equal to 1.
        • Sorting by property values is processed similarly to infoblocks 1.0 (except for multiple properties).
        • Selection of multiple property values does not lead to a Cartesian product of a query result; property values are transferred as an array.
        • For combined filters by non-multiple (single) properties, now there is an option to create a composite database index manually to speed up sampling operations.
        • “Straight through” element sampling is not possible for infoblocks 2.0 when an infoblock type and a symbol code of a property is indicated in the filter. Instead, IBLOCK_ID must be specified in the filter.

        Full compatibility with API is important. I.e. the technique for using infoblocks, properties, elements, and their values is the same for both versions of infoblocks.

        Connection between Infoblocks

        Bitrix Framework permits creating interconnections between information blocks using properties of the type Link to elements, Link to section, Link to elements (drop-down list), Link to elementы autocomplete text box and Link to product items (SKU).

        Infoblocks 2.0

        When creating information blocks, infoblock properties should be stored in a separate table, and all property values of each element are stored in the same string. This technique is called Information Blocks 2.0 and permits you to significantly speed up system operation and also to lift a number of restrictions of the previous version of infoblocks. For example, now there is no need to make the additional request CIBlockElement::GetProperty when selecting property values with the function CIBlockElement::GetList.

        Note. Documentation to Bitrix Framework, forum messages on the company’s website, and other places may contain the former name of the technique: infoblocks +.

        Infoblock 2.0 performance options:

        • When selecting elements, property values can be obtained at once, because the number of attached tables in the request is not increased with each property and is always equal to 1.
        • Sorting by property values is processed similarly to infoblocks 1.0 (except for multiple properties).
        • Selection of multiple property values does not lead to a Cartesian product of a query result; property values are transferred as an array.
        • For combined filters by non-multiple (single) properties, now there is an option to create a composite database index manually to speed up sampling operations.
        • “Straight through” element sampling is not possible for infoblocks 2.0 when an infoblock type and a symbol code of a property is indicated in the filter. Instead, IBLOCK_ID must be specified in the filter.

        Full compatibility with API is important. I.e. the technique for using infoblocks, properties, elements, and their values is the same for both versions of infoblocks.

        Storing properties in one common table and managing them using metadata from IBLOCK_PROPERTY_ID. is a very convenient feature for a developer because any metadata can be adjusted at any time without affecting any other information. This drawback is common for regular infoblocks.

        If information is stored in infoblocks 2.0 and a property changes its type, e.g. from Number to Line,the storage type in the database itself will also change.

        From the point of view of performance, infoblocks 2.0 scores better for small reference tables with a small number (20-30) of rarely changed properties. It makes no sense to shift a newsfeed to this type of infoblocks. You will gain in terms of the number of queries, but lose in terms of the query performance time.

        Infoblock 2.0 databases have a physical limit to the number of infoblock properties. At this time, it is not controlled in the system because it depends on a number of unpredictable factors: property types, MySQL configuration, and others. When this physical limit is exceeded, you will obtain a MySQL error that is unusual for Bitrix Framework. However, no data will be lost in this case.

        A big advantage of infoblocks 2.0 is the possibility to use composite indexes. However, the situation when sorting is made by = and by several fields simultaneously is quite unusual.

        Information Block Level

        Information block has a VERSION, attribute that determines whether an information block property value will be stored in a common or an allocated storage when creating a new infoblock. If an allocated storage for a specific infoblock is selected, the database creates two additional tables wherein the names will contain the infoblock indicator. One of the tables will store multiple properties and another one single and cached values of multiple properties.

        There is a link to a “converter” between storage types at the editing stage of an infoblock. It should be used with utmost care because the duration of the conversion process depends on the total volume of infoblock property values. Throughout the entire conversion, the infoblock is in an inconsistent state (only a part of values is transferred). In a test configuration for a MySQL version the conversion speed is approximately 1,500 elements per a 20 second step.

        The Fetch method is redefined in the CIBlockResult class.The method is responsible for caching values of multiple properties of an element that participate in sampling. For the same properties of the list type, the pairs ID=>VALUE are selected from the reference table.

        API provides for a VERSION parameter in the field array $arFields of the method CIBlock::Add. Its values are: 1 – for general storage and 2 – for allocated (new) storage.

        Information Block Level of Properties

        During the editing of properties (change of a multiplicity attribute or a property type), additional table management operations are performed for the properties stored in the allocated storage, such as delete/add columns, insert/update, or delete a big number of entries. It is best to avoid doing this unless it is absolutely necessary. It is recommended to change the type or multiplicity of one property at a time. Moreover, for single properties, it is recommended to convert them first to multiple properties and then change the type, and the other way around for multiple properties – first comes the type, and then conversion to a single property.

        Level of Elements of an Information Block

        When adding an element, a relevant entry is made into the table that stores the property values of the element.

        Level of Property Values of Information Block Elements

        The values of single properties of an infoblock with the allocated ID storage are composite and consist of an element ID and a property ID separated by a colon. When updating multiple properties, the cache of these values resets.

        Property values are stored in 2 tables (description of tables and their structure is provided for reference only and is subject to change in later versions):

        • b_iblock_element_prop_mNN - for multiple. It has the same structure as b_iblock_element_property;
        • b_iblock_element_prop_sNN - for single. It has the field IBLOCK_ELEMENT_ID - infoblock element ID to which the properties:
          • PROPERTY_XX - stores values of a single property XX or cache of values for a multiple property;
          • DESCRIPTION_XX - stores description for a single property.

        How to achieve the best performance using Infoblocks 2.0?

        In order to take advantage of the data storage structure used in new infoblocks, component behavior must be modified to a certain extent.

        For example: if the code template was more or less like this:

        <?
        //Determine the array of necessary fields of an element
        $arSelect = array(
            "ID",
            "IBLOCK_ID",
            "IBLOCK_SECTION_ID",
            "NAME",
            "PREVIEW_PICTURE",
            "DETAIL_PICTURE",
            "DETAIL_PAGE_URL",
        );
        //Obtain the list of elements. (+1 query)
        if($rsElements = GetIBlockElementListEx($IBLOCK_TYPE, false, false, array($ELEMENT_SORT_FIELD => $ELEMENT_SORT_ORDER), 
        $ELEMENT_COUNT, $arFilter, $arSelect))
        {
            //Initialization of a page by page display.
            $rsElements->NavStart($ELEMENT_COUNT);
            $count = intval($rsElements->SelectedRowsCount());
            if ($count>0)
            {
                //For each element:
                while ($obElement = $rsElements->GetNextElement())
                {
                    $arElement = $obElement->GetFields();
                    //Obtain its properties. (+1 query)
                    $arProperty = $obElement->GetProperties();
                    //Below property values can be used.
                    //For example:
                    echo $arProperty["SOURCE"],"
        "; //etc. } } } ?>

        Now, after conversion to a new storage type, it is possible to avoid queries in the cycle:

        <?
        //Determine the array of necessary fields of an element
        $arSelect = array(
            "ID",
            "IBLOCK_ID",
            "IBLOCK_SECTION_ID",
            "NAME",
            "PREVIEW_PICTURE",
            "DETAIL_PICTURE",
            "DETAIL_PAGE_URL",
            "PROPERTY_SOURCE", //Select a property we need
            // And all other which may be needed
            //directly in the list
        );
        //Obtain the list of elements. (+1 query)
        if($rsElements = GetIBlockElementListEx($IBLOCK_TYPE, false, false, array($ELEMENT_SORT_FIELD => $ELEMENT_SORT_ORDER), 
        Array("nPageSize"=>$ELEMENT_COUNT), $arFilter, $arSelect))
        {
            //Initialization of a page by page display.
            $rsElements->NavStart($ELEMENT_COUNT);
            if($obElement = $rsElements->GetNextElement())
            {
                //For each element:
                do
                {
                    $arElement = $obElement->GetFields();
                    //Below property values can be used.
                    //For example:
                    echo $arElement["PROPERTY_SOURCE"],"
        "; //etc. } while ($obElement = $rsElements->GetNextElement()) } } ?>

        Action Plan in Case of Problems

        Should any problems occur in project operation, we recommend that the following algorithm be used in order to eliminate the problems:

        • Establish a specific goal. The optimization and improvement has no limits. For example: each page with a catalog of your goods must open within a set amount of time, say, 0.2 second. Only establishing these specific goals and achieving them can be efficient, unlike the vague and formalized request “to make it work better”.
        • Find and remove irrelevant queries using the tool Performance monitor (Settings > Performance > SQL Queries).

          An irrelevant query is:

          • A query in a cycle (should be removed from the cycle to a variable).
          • Queries that collect additional data in a cycle. (It is better to collect data in a filter, then display data in a single query, and after that introduce an additional cycle – breaking the data down; in this case, queries have no linear dependence on the number of elements).
        • Remove unused data from queries ($arSelect). Specify the fields to be used. It drastically improves performance because such a specific indication of data to be used means a lesser volume of sorting for the database. The database performs such sorting not in the hard drive but in the RAM.
        • Assess the possibility of using PROPERTY_* in combination with infoblocks 2.0. Infoblocks 2.0 store their properties in a separate table. This table is attached at the selection stage when PROPERTY_* is mentioned. And no properties are attached until values of these properties are selected.

          When can we not use it? For example, in case of a small selection from a big number of elements (10 pieces of news out of several thousands of entries). It is not a straightforward aspect, and it depends very little on the developer. The reason is that sampling from infoblocks and infoblocks 2.0 leads to a different result. In simple infoblock entries start reproducing when selected. And infoblocks 2.0 return the array of property values. If a template code does not provide for this situation, the change of regular infoblocks to infoblocks 2.0 will destruct the template.

        • Review the execution plan of the most heavy queries and add/delete indexes.

        Filtering

        Sometimes caching is bad. If we choose to cache a catalog with a multivariate filter in the conditions of dense website traffic, cache generation may exceed 200 MB per minute. Any disc quota limit will be filled fast enough. Problems with cleaning this cache may also occur. Deactivating such cache will reduce the number of writing operations.

        Do not be afraid of creating indexes. It is impossible to say which indexes must be created in each particular case by default; each specific situation must be examined without fail. The tool Analyze indexes helps you do that (Settings > Performance > Database indexes > Analyze indexes).

        One of the indexes most frequently used is the index by the list-based property. This index is intended for simple infoblocks. b_iblock_element_property - in this table property values are stored. So we index it: property value, its ID, and an element ID. When the list-based property filter is in place, creating such an index actually makes the query of MySQL to said table unnecessary because all of the query fields are available in the index.

        Indexes are needed for specific sampling in specific projects. Depending on project architecture and logics, slow queries receive their own indexes, and they need such own indexes; often such indexes are composite.

        Type of filtration

        In the majority of functions and methods of the module of information blocks with list selection, filters admit different sorting types. Sorting type symbols shall be indicated directly before the name of the field to be sorted.

        Types:

        • (empty) – for string fields means mask search (in mask: "%" - an arbitrary number of any symbols, "_" - one arbitrary symbol); for non-string fields the search is "equal".
        <?
        // find elements in which the name begins with "#"
        $res = CIBlockElement::GetList(Array(), Array("NAME"=>"%#"));
        
        // find elements with identifier 100
        $res = CIBlockElement::GetList(Array(), Array("ID"=>"100"));
        ?>
        • "!" - for strings – an expression that does not fall within a mask, or unequal (for other types of fields).
        <?
        // find elements in which the name does not begin with "#"
        $res = CIBlockElement::GetList(Array(), Array("!NAME"=>"#%"));
        ?>
        • "?" - using the logics, works only for string properties.
        <?
        // find elements in which the name contains "One" or "Two"
        $res = CIBlockElement::GetList(Array(), Array("?NAME"=>"One | Two"));
        ?>
        • "<" - less;
        • "<=" - less or equal;
        • ">" - more;
        • ">=" - more or equal.
        <?
        // find elements in which the name begins with "A"
        $res = CIBlockElement::GetList(Array(), Array(">=NAME"=>"A", "<NAME"=>"B"));
        
        // find elements with an identifier of more than 100
        $res = CIBlockElement::GetList(Array(), Array(">ID"=>"100"));
        ?>
        • "=" - equal;
        • "!=" - non-equal.
        <?
        // find elements in which the name is equal to "ELEMENT%1"
        $res = CIBlockElement::GetList(Array(), Array("=NAME"=>"ELEMENT%1"));
        ?>
        • "%" - substring;
        • "!%" - not a substring.
        // find elements in which the name contains the sequence "123"
        $res = CIBlockElement::GetList(Array(), Array("%NAME"=>"123"));
        • "><" - between;
        • "!><" - not between.

        As an argument, these types of filters admit an array ("value FROM", "value TO")

        <?
        // ind elements in which the name begins between "A" and "B" or between "D" and "E"
        $res = CIBlockElement::GetList(Array(), Array("><NAME"=>Array(Array("A", "B"), Array("D", "E"))));
        
        // find elements in which the activity start date is outside the year 2003
        $res = CIBlockElement::GetList(Array(),
                                       Array("!><DATE_ACTIVE_FROM"=>
                                             Array(date($DB->DateFormatToPHP(CLang::GetDateFormat("FULL")), 
                                                        mktime(0,0,0,1,1,2003)),
                                                   date($DB->DateFormatToPHP(CLang::GetDateFormat("FULL")), 
                                                        mktime(0,0,0,1,1,2004)))));
        ?>

        Some Specific Cases of Sorting

        $arFilter = array("PROPERTY_CML2_SCAN_CODE"=>false ) - is used to select all elements with an empty property; 
        $arFilter = array("PROPERTY_CML2_SCAN_CODE"=>"" ) - is used to select all elements;  
        $arFilter = array("PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the exact match of a property with the preset string is checked; 
        $arFilter = array("?PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the availability of preset substring in a property is checked;
        $arFilter = array("!PROPERTY_CML2_SCAN_CODE"=>false ) - is used to select only elements with a property filled in; 
        $arFilter = array("!PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the absence of the exact match with the preset string is checked; 
        $arFilter = array("!?PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the absence of the preset substring in a property is checked. 
        

        Sorting Set Up to Display Related Elements

        Task: Let us assume that there are 2 infoblocks that are interrelated by one of the properties. How should we set up the sorting so that among the infoblock elements only related elements can be displayed?

        Solution:

        <? 
          $arrFilter = array(); 
          $arrFilter['!PROPERTY_<property_code>'] = false; 
        ?>

        Sorting Set Up Using a "Date/Time" Type of Property

        A "Date/time" type of property is stored as string in the format YYYY-MM-DD HH:MI:SS, That is why the value for sorting is formed as follows:

        $cat_filter[">"."PROPERTY_available"] = date("Y-m-d");


        Filtering iblock elements without filter component

        If simple components are used to publish an information block, elements can be sorted without using the Filter component and without customizing the component used to display the list of elements. Such sorting is based on the use of the parameter Name of the array with values used to filter elements (FILTER_NAME) and is available in the following components: bitrix:catalog.section, bitrix:catalog.sections.top and bitrix:news.list.

        Filter array can be defined directly on the page before connecting a list component. However, in this case, you will have to create several pages placing a component and determining the filter array on each page. There is a simpler way: variables of the filter array may be submitted in a link.

        In our example, variables for the filter will be submitted in a link using the GETmethod, and the filter $arrFilter will be determined from the array $_GET. Infoblock element publishing will be performed using the list component Section elements Section elements (bitrix:catalog.section).

        Let us assume that we have the Products infoblock, and we will sort its elements using the property Manufacturer (MANUFACTURER):

        Let us create a start page with a set of links (in our case, it will be a list of manufacturer countries):

        The page code will be as follows::

        <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");?>
        <p>Filter by manufacturer:</p>
        
        <ul>
        <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Germany">Germany</a></li>
        <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Italy">Italy</a></li>
        <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Holland">Holland</a></li>
        <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Ukraine">Ukraine</a></li>
        <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Austria">Austria</a></li>
        <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Sweden">Sweden</a></li>
        </ul>
        <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
        

        Now let us create the page filter.php and place the component (bitrix:catalog.section) there. Then, we should set a necessary infoblock in the component settings and fill in the field Name of the array with values used to filter elements with the value arrFilter.

        Before connecting the component, let us add the following code:

        $manufacturer = $_GET["MANUFACTURER"]; 
        $arrFilter=array("PROPERTY"=>array("MANUFACTURER"=>"$manufacturer")); 
        

        As a result, when passing from the home page (e.g., following the link Italy), the list of goods of the section with identifier 2 manufactured in Italy will open:

        Handling iblocks via API

        The needs of website owners are very diverse. The standard functionality of Bitrix Framework cannot resolve all tasks that may be set for the developer when it comes to creating Internet project. API must be used to implement non-standard tasks. API of infoblocks should be studied with special attention. They are most widely used in programming.

        Attention! Direct database queries are strongly discouraged. In this case, the operation of system basic functions is not guaranteed. In addition, it may lead to data disintegration.

        API of a module consists of several high-level functions to select data in the public section of a website and a set of classes with low level methods for a more specialized work.

        Before using the module, please make sure it is installed and activate it using the following structure:

        <?
        if(CModule::IncludeModule("iblock"))
        {
           //here module functions and classes can be used
        }
        ?>

        Functions with simple parameters and preset filters may be used to obtain data during display in the public section of the website. These functions select by default the values that are suitable for the sorting location, namely only those active, related to the current website, suitable in terms of access rights, etc.

        All work with dates through API (insert, selection, filters, etc.) is done in the format of the current website or, if in the administrative part, in the format of the current language.

        Some API functions are available at all times, i.e. described in the main module, and some functions depend on the module used and can be present or absence in different versions of the product.

        For most classes of Bitrix Framework the following functions are available:

        • Data selection (GetList).
        • Adding a new element (Add).
        • Updating and deleting an element (Update).
        • Deleting an element (Delete).
        • And other functions.

        For most modules, a specialized class structure, event mechanism, and additional functions are available. In particular, for the module of Information blocks the description is provided for:

        • All tables used in the database, including table fields.
        • Classes for work with infoblock types, infoblocks, elements, sections, and fields.
        • Events occurring when adding, changing, and deleting module objects.
        • Functions that extend kernel possibilities.
        • Means to create the user’s forms for editing and the user’s data types.
        • Other information.

        The lessons of this chapter will address some examples of using the API of information blocks.


        Work with the User Properties of Infoblocks

        Examples of tasks that may occur while working with elements, sections, and infoblock properties.

      • Obtain values of all of the properties of an element knowing its ID
      • Obtain properties of the elements using the method CIBlockElement::GetList
      • Add a property of the type TEXT/html for an element
      • Complete a multiple property of the File type
      • Complete a multiple property of the List type with a display as checkboxes
      • Obtain a user property of a section

      • Task 1:
        Obtain values of all of the properties of an element knowing its ID.

        1	<? $db_props = CIBlockElement::GetProperty(IBLOCK_ID, ELEMENT_ID, "sort", "asc", array());
        2	$PROPS = array();
        3	while($ar_props = $db_props->Fetch())
        4	$PROPS[$ar_props['CODE']] = $ar_props['VALUE'];?>

        Now the symbol code of the property is the key of the associative array $PROPS, I.e. if you need a value of the property with the code price, it will be stored in $PROPS['price'].


        Task 2:
        Obtain properties of the elements using the method CIBlockElement::GetList

        1	<? $arSelect = array("ID", "NAME", "PROPERTY_prop_code_1", "PROPERTY_prop_code_2");
        2	$res = CIBlockElement::GetList(array(), array(), false, array(), $arSelect);?>

        Then, you have to use the cycle and obtain the properties with symbol codes prop_code_1 and prop_code_2.


        Task 3:
        Add a property of the type TEXT/html for an element.

        If the property is not a multiple property:

        01	<? $element = new CIBlockElement;
        02	$PROP = array();
        03	$PROP['property symbol code']['VALUE']['TYPE'] = 'text'; // or html
        04	$PROP['property symbol code']['VALUE']['TEXT'] = 'property value';
        05	$arLoadArray = array(
        06	  "IBLOCK_ID"      => IBLOCK_ID,
        07	  "PROPERTY_VALUES"=> $PROP,
        08	  "NAME"           => "Name of the element"
        09	  );
        10	$element->Add($arLoadArray);?>

        If the property is a multiple property:

        01	<? // In $ITEMS multiple property values are stored
        02	foreach($ITEMS as $item)
        03	{
        04	    $VALUES[]['VALUE'] = array(
                          'TYPE' => 'text', // or html
                          'TEXT' => $item,
                    );
        05	    $VALUES[]['VALUE']['TEXT']= $item;
        06	}
        07	$element = new CIBlockElement;
        08	$PROPS = array();
        09	$PROPS['property symbol code'] = $VALUES;
        10	$arLoadArray = array(
        11	  "IBLOCK_ID"      => IBLOCK_ID,
        12	  "PROPERTY_VALUES"=> $PROPS,
        13	  "NAME"           => "Name of the element"
        14	  );
        15	$element->Add($arLoadArray);?>


        Task 4: Complete a multiple property of the File type. Quite often when adding an element to an infoblock several files may need to be attached to it. It can be conveniently done by creating a multiple property of the File type for the infoblock and store files there. Here is an example of the property completed:

        01	<?
        02	$arFiles = array();
        03	for($i = 1; $i < 10; $i++)
        04	{
        05	    if(file_exists($_SERVER['DOCUMENT_ROOT'].'/images/image_'.$i.'.jpg'))
        06	    {
        07	        $arFiles[] = array('VALUE' => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"].'/images/image_'.$i.'.jpg'), 'DESCRIPTION' => '');
        08	    }
        09	}
        10	?>

        After that, the array $arFiles is transferred as a property value when an element is added.


        Task 5:
        Complete a multiple property of the List type with a display as checkboxes. In this case, each element of the list has its own ID. It can be looked up by going to detailed editing of the property. The property shall be completed as follows:

        1	<?
        2	if($first_condition == true) $values[] = array('VALUE' => 1);
        3	if($second_condition == true) $values[] = array('VALUE' => 2);
        4	CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array('property_code' => $values));
        5	?>

        In this case, when performing the first and second condition, we should check the list elements from ID =1 and ID=2 accordingly. $ELEMENT_ID, $IBLOCK_ID and property_code shall be replaced with necessary values.


        Task 6:
        Obtain a user property of a section

        1	<? $section_props = CIBlockSection::GetList(array(), array('IBLOCK_ID' => IBLOCK_ID, 'ID' => SECTION_ID),  true, array("UF_ADDITIONAL_PRICE"));
        2	$props_array = $section_props->GetNext(); ?>

        Now, the value of the property $props_array['UF_ADDITIONAL_PRICE'] of an infoblock section is located in UF_ADDITIONAL_PRICE.

        Infoblock Copy

        Infoblock copy is not provided for in Bitrix Framework as a standard option, although sometimes it may become necessary, and it can be done. Automation of this process will be a good example of using infoblock API.

        The Use of XML Import

        Infoblocks can be copied using the XML import/export function:

        • Download the necessary infoblock by exporting in XML.
        • Open the XML file for editing and carefully adjust the infoblock ID where necessary. In the beginning of the XML, the ID node may be replaced with the Title node:
          <?xml version="1.0" encoding="UTF-8"?>
          <CommerceInformation SchemaVersion="2.021" CreationDate="2010-03-20T09:55:13">
             <Metadata>
                <Id>2
                <Title>Notebooks
                <Properties>
                   <Property>
                      <Id>CML2_CODE
                      <Title>Symbol code
          Find the following code after the description of the infoblock and its properties:
          <Catalog>
                <Id>2
                <MetadataId>2
                <Title>Notebooks
          Establish data in accordance with the amendments made above in the nodes ID, MetadataId, and Title.

        Copy Automation

        Use the script provided below to import metadata from the information block created earlier when generating a new one:

        Metadata copy setting is made using three fields:

        • Copy IB. This field is mandatory and is always preset. The IB from which metadata are to be imported shall be indicated in this section (except for property description).
        • Copy properties of an IB to a new IB. This field is not mandatory. It may be used to import only metadata of the properties of any infoblock to a new information block. If the field is not completed, property metadata will be taken from the infoblock indicated in the field Copy IB.
        • Copy of IB to type. This field is not mandatory. It may be completed if a new information block must be generated in any type of IB. If no setting is indicated, the type of infoblock indicated in the field Copy IB will be used.

          After copy, the new infoblock will have the name of the old infoblock with the suffix _new.

        Script code:

        CModule::IncludeModule("iblock");
            if(intval($_REQUEST["IBLOCK_ID_FIELDS"])>0){
                $bError = false;
                $IBLOCK_ID = intval($_REQUEST["IBLOCK_ID_FIELDS"]);
                $ib = new CIBlock;
                $arFields = CIBlock::GetArrayByID($IBLOCK_ID);
                $arFields["GROUP_ID"] = CIBlock::GetGroupPermissions($IBLOCK_ID);
                $arFields["NAME"] = $arFields["NAME"]."_new";
                unset($arFields["ID"]);
                if($_REQUEST["IBLOCK_TYPE_ID"]!="empty")
                    $arFields["IBLOCK_TYPE_ID"]=$_REQUEST["IBLOCK_TYPE_ID"];
                $ID = $ib->Add($arFields);
                    if(intval($ID)<=0)
                        $bError = true;        
                if($_REQUEST["IBLOCK_ID_PROPS"]!="empty")
                    $iblock_prop=intval($_REQUEST["IBLOCK_ID_PROPS"]);
                else
                    $iblock_prop=$IBLOCK_ID;
        
         $iblock_prop_new = $ID;
                $ibp = new CIBlockProperty;
                $properties = CIBlockProperty::GetList(Array("sort"=>"asc", "name"=>"asc"), Array("ACTIVE"=>"Y", "IBLOCK_ID"=>$iblock_prop));
                while ($prop_fields = $properties->GetNext()){
                    if($prop_fields["PROPERTY_TYPE"] == "L"){
                        $property_enums = CIBlockPropertyEnum::GetList(Array("DEF"=>"DESC", "SORT"=>"ASC"),
                                                                       Array("IBLOCK_ID"=>$iblock_prop, "CODE"=>$prop_fields["CODE"]));
                        while($enum_fields = $property_enums->GetNext()){
                            $prop_fields["VALUES"][] = Array(
                              "VALUE" => $enum_fields["VALUE"],
                              "DEF" => $enum_fields["DEF"],
                              "SORT" => $enum_fields["SORT"]
                            );
                        }
                    }
                    $prop_fields["IBLOCK_ID"]=$iblock_prop_new;
                    unset($prop_fields["ID"]);
                    foreach($prop_fields as $k=>$v){
                        if(!is_array($v))$prop_fields[$k]=trim($v);
                        if($k{0}=='~') unset($prop_fields[$k]);
                    }
                    $PropID = $ibp->Add($prop_fields);
                    if(intval($PropID)<=0)
                        $bError = true;
                }
                if(!$bError && $IBLOCK_ID>0)
                    LocalRedirect($APPLICATION->GetCurPageParam("success=Y",array("success","IBLOCK_ID_FIELDS")));
                else 
                    LocalRedirect($APPLICATION->GetCurPageParam("error=Y",array("success","IBLOCK_ID_FIELDS")));
            }
           $str .='<form action='.$APPLICATION->GetCurPageParam().' method="post">[table]';    
            if($_REQUEST["success"]=="Y") $str .='[tr][td]<font color="green">IB is copied successfully</font>[b][/td][/tr]';
            elseif($_REQUEST["error"]=="Y") $str .='[tr][td]<font color="red">Error</font><br/>[/td][/tr]';
            $str .='[tr][td]Copy of IB metadata to a new IB:[/b]<br/>[/td][/tr]';
            $res = CIBlock::GetList(Array(),Array(),true);
                while($ar_res = $res->Fetch())
                    $arRes[]=$ar_res;
            $str .='[tr][td]Copy IB:<br><select name="IBLOCK_ID_FIELDS">';
            foreach($arRes as $vRes)    
                $str .= '<option value='.$vRes['ID'].'>'.$vRes['NAME'].' ['.$vRes["ID"].']</option>';
            $str .='</select>[/td]';
            $str .='[td]Copy to new IB properties of another IB: *<br><select name="IBLOCK_ID_PROPS">';
            $str .='<option value="empty">';
            foreach($arRes as $vRes)    
                $str .= '<option value='.$vRes['ID'].'>'.$vRes['NAME'].' ['.$vRes["ID"].']</option>';
            $str .='</select>[/td][/tr]';
            $str .='[tr][td]Copy IB to type:<br><select name="IBLOCK_TYPE_ID">';
            $str .='<option value="empty">';
            $db_iblock_type = CIBlockType::GetList();
            while($ar_iblock_type = $db_iblock_type->Fetch()){
               if($arIBType = CIBlockType::GetByIDLang($ar_iblock_type["ID"], LANG))
                  $str .= '<option value='.$ar_iblock_type["ID"].'>'.htmlspecialcharsex($arIBType["NAME"])."</option>";
            }
            $str .='</select>[/td][/tr]';
            $str .='[tr][td]<br/>if the value is not specified, IB metadata of the "Properties" section will be collected from the IB of the 1st field[/td][/tr]';
            $str .='[tr][td]<input type="submit" value="copy">[/td][/tr]';
            $str .='[/table]</form>';    
            echo $str;

        The script can prove to be of invaluable help, for example, when copying IB without using XML export/import mechanisms of information blocks.

        This tool is recommended for infoblocks where there are many list-based properties or generally a big number of properties that require a detailed setup.

        The script must be located in the website root.

        Infoblocks in Document Flow

        When working in document flow mode 2, infoblock elements are created: one “final” with an empty WF_PARENT_ELEMENT_ID (which we can see in the administrative part), and another “temporary” with WF_PARENT_ELEMENT_ID equal to the ID of the just created final element. The “temporary” element turns into “final” when it reaches a final status Published in the document flow or any other status flaged as IS_FINAL. This flag cannot be set using the API methods of Bitrix Framework; it only can be set by editing the database. Accordingly, entries will be made to the infoblock up to this moment; however, for standard API methods with default parameters these entries will not be available.

        The principal difference between the “temporary” elements and “final” is the field WF_PARENT_ELEMENT_ID, which is empty for the “final” elements and contains an identifier of the “final” element for temporary elements. Should a new element be created in the document flow (provided that the starting status of the document flow is not final), an element will be created, and its own identifier will be written in the field WF_PARENT_ELEMENT_ID. Upon the subsequent promotion of the element to any other document flow status, including the final one, new elements with the field WF_PARENT_ELEMENT_ID will be created in the infoblock.

        Upon the promotion of an element to a final status, the initial element will be updated in such a manner so that the field WF_PARENT_ELEMENT_ID becomes empty, and the field WF_STATUS_ID becomes equal to the value of the final status (1 is the most frequent value). After the subsequent promotion of the element to any temporary status the cycle will be repeated with a slight difference that the current published element will be used as a starting point.

        By default, API only permits working with published elements. If a published element is promoted to any other status, API will return the element version that corresponds to the published one.

        In order to obtain the list of all of the elements (including unpublished ones), the method CIBlockElement::GetList must have the following parameter in the filter properties: "SHOW_HISTORY" => "Y".

        In addition, it is possible to obtain the latest version of an element in the document flow by its ID using the function CIBlockElement::WF_GetLast, nd vice versa to obtain the original ID of an element knowing the latest version of such an element through the function CIBlockElement::GetRealElement.

        Event handlers also deserve some attention, among them OnAfterIBlockElementUpdate. Since in case of working with document flow a direct Update only occurs in case of the promotion of an element to the final status, handlers of this event should not be expected when an element is promoted to “temporary” statuses. Instead, add a handler to the event OnAfterIBlockElementAdd and watch the fields "WF" = "Y" (a sign that the element participates in the document flow) and WF_PARENT_ELEMENT_ID (identifier of the “final” element).

        All change history of an element can be displayed using the function CIBlockElement::WF_GetHistoryList. In order to obtain detailed information about the temporary elements obtained using this function, the functions CIBlockElement::GetByID and CIBlockElement::GetProperty should be used.

        SEO in Infoblocks: Calculated Properties

        Starting from version 14.0.0, the tab SEO is available in the editing form of the infoblock, its sections, and elements. This functionality is based on the following techniques:

        • Storage – meaning a mechanism of inherited properties (property values apply top to bottom over the infoblock hierarchy: from infoblock through sections down to an element);
        • Template engine is a template builder that uses substitutions and functions.

        Let us consider each of these techniques in more detail.

        Storage

        All templates to be inherited by the calculated inherited properties are stored in the table b_iblock_iproperty. It is a single table that stores templates for three entities: elements, sections, and infoblocks.

        Templates are linked to an infoblock, section, or element using two fields: ENTITY_TYPE and ENTITY_ID. In order to determine which templates should be used for each entity, an internal search by existing infoblock tables is performed. Calculated values are stored in 3 different tables for elements, sections, and infoblocks separately.

        When handling the data of the table b_iblock_iproperty (when we change, delete, or make additions to the template) no calculations are made, only the reset of values calculated previously. The calculation operation is postponed until the values are called for (read). At this time, templates are searched from bottom to top over the infoblock hierarchy (for an element, these will be templates of the element proper, its sections (up to the root), and infoblock templates). After that, the templates will be calculated and the obtained values will be stored in cache tables to be retrieved during subsequent read operations.

        Classes of inherited properties use all of the capacity of object-oriented programming and belong to a new D7 core. They are in the name space of Bitrix\Iblock\InheritedProperty and are pretty easy to use:

        use Bitrix\Iblock\InheritedProperty; 
        
        //OOP  ElementTemplates or SectionTemplates or IblockTemplates )) 
        $ipropTemplates = new InheritedProperty\ElementTemplates($IBLOCK_ID, $ELEMENT_ID);
        //Install template for an element
        $ipropTemplates->set(array(
                 "MY_PROP_CODE" => "{=this.Name}",
                 “SOME_CODE" => "", //Delete template
        ));
        //Obtain templates for "editing" 
        $templates = $ipropTemplates->findTemplates();
        //Delete all own element templates
        $ipropTemplates->delete();
        
        //OOP  ElementValues or SectionValues or IblockValues )) 
        $ipropValues = new InheritedProperty\ElementValues($IBLOCK_ID, $ELEMENT_ID);
        //Obtain values 
        $values = $ipropValues->getValues();
        echo $values [" MY_PROP_CODE "]; 
        //Reset cache
        $ipropValues->clearValues(); 
        

        • Create a class instance depending on the entity type (for elements it will be ElementTemplates, for sections - SectionTemplates and for infoblock - IblockTemplates).
        • Use the set method for template handling.

          Note: The set method is currently used in the methods Add and Update of the classes CIBlock, CIBlockSection and CIBlockElement (the field IPROPERTY_TEMPLATESis processed).

        • Use the method getValues (it can be found in infoblock components) in order to display data calculated during selection according to set templates.
        • The method clearValues permits you to reset the cached values and recalculate.

        Templates

        Templates are built irrespective of the storage mechanism, and thus dynamic forms may be used. The following components are used to build a template:

        • First, it is just a text to be calculated into the same simple text.
        • Second, the substitutions which start inside curly brackets with an equal sign (e.g., {=this.Name}). This pseudoobjective syntaxis permits you to implement an economic model with pending data queries. The template may use the following areas: this, parent, sections, iblock, property or catalog. Fields can be very different: name, code, previewtext, detailtext, property_CODE, etc. (See files with classes in the folder /bitrix/modules/iblock/lib). The number of DB queries directly depends on the number of areas used in the template.
        • Third, the functions (e.g., {=concat " \ " "!" iblock.name sections.name this.name}). There is a set of built-in functions (upper, lower, translit, concat, limit, contrast, min, max и distinct) and the event OnTemplateGetFunctionClass, which permits you to write an own function.

        Templates can have modifiers: lower casing (/l) and transliteration (/t-). They are displayed in separate checkboxes in the SEO tab interface.

        Furthermore, all templates support nesting. For example:

        //For an element, a preview and detailed descriptions of its section are selected, then
        //first 50 words are selected. After that, they joined with the first 50 words of the element preview.
        //Out of them, 20 of the most contrasted words are selected, and all of them are converted to lower case.
        
        
        {=lower {=contrast 20 " .,?!" {=limit 50 " .,?!" this.previewtext} {=limit 50 " .,?!" parent.previewtext parent.detailtext}}}
        

        Let us have a look at the template code:

        use Bitrix\Iblock\Template;
        //Connect the infoblock module.
        if (\Bitrix\Main\Loader::includeModule('iblock'))
        {
              //Set a template.
              $template = "Name: {=this.Name}. Code:{=this.code}";
              //We will take the source data from the element.
              $entity = new Template\Entity\Element($ELEMENT_ID);
              //Do not forget about safety.
              echo \Bitrix\Main\Text\String::htmlEncode(
                      //Calculate value according to the template.
                      Template\Engine::process($entity, $template) 
             );
        }
        

        The entity object must be created. Parsing and template calculation are covered with the process, static method to which entity and template are submitted. The method process can also be used in the cycle with one entity and different template, and in this case the data will be «reused», i.e. there will be no odd queries. In addition, pay attention to the method htmlEncode, that is used to form safe html.


        Highload blocks

        The Information block module is considered as "heavily resource-demanding" for creating "lightweight" directories or storage of large data volumes (when module can behave sub-optimally). Now an option to create analog to iblocks (Highload blocks module), but a lot simpler and "lightweight" is available. New module is available from product version 14.0 and is written on the new core D7. Data structure for this module provides for use of high-load projects.

        Note: Highload blocks and traditional information blocks are conceptually two different things. That's why there is no option to convert traditional iblocks into highload blocks. Technically, a mirroring structure can be created and data moved, however it makes sense only for a specific project, if required.

        Highload blocks

        Highload blocks - are fast listing directories, without hierarchy support, with limited property support. They could query the database, including via HandlerSocket and handle large volumes of data. Highload blocks store elements in their tables and use their own indexes.

        Note: with exception to module Highload blocks, core D7 can query database via HandlerSocket for all ORM entities.

        Previously, data was stored using the module that closed tables via Table Module design pattern. Highload blocks have additional layer called Table Data Gateway that supports all techniques for handling tables, with business logic located in expanding class.

        Highload blocks is a logic for handling data, nothing more. For specific data use, developer must envisage implementation of business logic by the application itself. Accordingly, Highloadblock class must be expanded to implement this logic:

        use Bitrix\Highloadblock as HL;
        $hlblock   = HL\HighloadBlockTable::getById( # )->fetch();
        $entity   = HL\HighloadBlockTable::compileEntity( $hlblock ); //class generation 
        $entityClass = $entity->getDataClass();
        
        class MyDomainObjectTable extends #entityClass# {
        …//our project business logic, overview the contents of $entityClass and write it into #entityClass#
        } 
        

        Highload blocks advantages

        • Low overhead cost (PHP, less SQL queries).
        • Low risk of blockages in database: because data is are stored in respective tables, there is no unified, global table that can be blocked upon high loads (data retrieval and simultaneous import).
        • Thousands, millions entities, directories.
        • Reduce load on the database, hosting.

        About performance and resources

        Highloadblock is a middleware between user and ORM. OOP (object-oriented programming) leads to an increased use of memory and CPU, offering a more convenient and effective development process. Highloadblocks are intended for high-loads experienced by the system. Their advantages are in the area of architecture, allowing easier scalability, management and risk assessment.

        Database handling is the specific advantage database handling. Developer receives:

        • separate tables for entities (can be noticeable when handling large volumes),
        • possibility to easy index assignment for necessary selections,
        • possibility to transparently use handlersocket that significantly reduces load to DBMS.

        Module architecture

        Description

        In contrast to Information blocks module, Highload blocks module can store each entity in their own individual tables, without associating with any module. Entity fields are [ds]core's user properties. [/ds][di]It is necessary to differentiate User fields in system modules and properties used within information blocks, although system forms (forms for creating/editing an iblock section and others) use a "user property" term[/di]

        Entities are generated in admin interface, without additional programming.

        Access to data is provided based on ORM.

        Data allocation can be performed both to a single database and to different databases in general. This way a project's scaling out becomes possible, when some objects are located at one server, and some data - on another.

        The Highload blocks module supports NoSQL concept based on MySQL database. Database connection is provided with both handlersocket extension for MySQL, as well as without it. Handlersocket use requires additional settings.

        For example, when project supports NoSQL, with all required installed drivers for MySQL and core/kernel configuration had been established connection via these drivers. As result, Highload blocks module allows creating entities and building project in a manner that data retrieval from database was performed via the driver by primary key.

        The following actions must be performed to use ORM entity for Highload blocks:

        • Initialize entity in the system:
          //first, select entity data from the database
          $hldata = Bitrix\Highloadblock\HighloadBlockTable::getById($ID)->fetch();
          
          //then initialize entity class
          $hlentity = Bitrix\Highloadblock\HighloadBlockTable::compileEntity($hldata);
          
        • Next, creates queries with this entity:
          $query = new Entity\Query($hlentity);
          
          or use via DataManager interface:
          $hlDataClass = $hldata['NAME'].'Table';
          $hlDataClass::getList();
          

        All further handling of Highload blocks is governed by the ORM rules, because entity, created in the Highload blocks is an ORM entity.

        Module contains two components: list of entity records (highloadblock.list) and entity detail view (highloadblock.view).

        List association

        Association of directory to Highload block is performed via the USER_TYPE_SETTINGS properties. They contain table name.

        Association of property values uses UF_XML_ID (completing the field is required), otherwise element's "list" property values are not saved.

        Highloadblock and handlersocket

        Traditional ACID Databases generally complicate project implementation for specific tasks. Such frameworks as NoSQL and HandlerSocket were designed for such tasks (in a form of plugin to standard MySQL).

        HandlerSocket allows customer application to connect directly to MySQL data engine to reduce excessive load, specific to traditional queries via the SQL interface and unacceptable to highly loaded databases.

        Example comparison for number of queries, permitted by different database handling methods:

        Query typeNumber of queries per secondCPU load
        MySQL via SQL client 105 000 User processes: 60%
        System processes: 28%
        memcached 420 000 User processes: 8%
        System processes: 88%
        MySQL via client's HandlerSocket 750 000 User processes: 45%
        System processes: 53%

        The difference between querying via MySQL client and via HandlerSocket is that in the second case, parsing, table openings, execution schedule optimization are skipped. It means that querying is performed directly and the load to MySQL decreases significantly. Queries are not executed quicker, but reduces server load.

        Connecting HandlerSocket is specified in the file Core parameter settings.

        You can install the plugin either by directly downloading MySQL source files and assembling the plugin, or by installing PerconaServer or MariaDB, with plugin enabled by default.

        HS API is called when querying the Highloadblock ($obj = $entityClass::getById( $arData["ID"] )->fetch();) via HandlerSocket (query open_index and find in MySQL) with call result processing by the application.

        HandlerSocket launches a thread pool inside the database, operating in asynchronous mode. (NGINX operates in a similar manner) In standard cases MySQL operates with a single thread for each client connection (thread) and works inside it. However, in case of HandlerSocket, it uses a pool of threads with multiplex system pool/select calls. That's why, for example, 5 threads can process hundreds of thousands queries.

        Highload blocks handling examples

        <?
        //Preparation:
        if (CModule::IncludeModule('highloadblock')) {
           $arHLBlock = Bitrix\Highloadblock\HighloadBlockTable::getById(1)->fetch();
           $obEntity = Bitrix\Highloadblock\HighloadBlockTable::compileEntity($arHLBlock);
           $strEntityDataClass = $obEntity->getDataClass();
        }
        
        //Adding:
        if (CModule::IncludeModule('highloadblock')) {
           $arElementFields = array(
              'UF_NAME' => $arPost['name'],
              'UF_MESSAGE' => $arPost['message'],
              'UF_DATETIME' => new \Bitrix\Main\Type\DateTime
           );
           $obResult = $strEntityDataClass::add($arElementFields);
           $ID = $obResult->getID();
           $bSuccess = $obResult->isSuccess();
        }
        
        //Retrieving the list:
        if (CModule::IncludeModule('highloadblock')) {
           $rsData = $strEntityDataClass::getList(array(
              'select' => array('ID','UF_NAME','UF_MESSAGE','UF_DATETIME'),
              'order' => array('ID' => 'ASC'),
              'limit' => '50',
           ));
           while ($arItem = $rsData->Fetch()) {
              $arItems[] = $arItem;
           }
        }
        ?>

        Arbitrary value selection:

        $q = new Entity\Query($entity);
               $q->setSelect(array('*'));
               $q->setFilter($arFilter);
               $q->setLimit(1);
               $q->registerRuntimeField(
                   'RAND', array('data_type' => 'float', 'expression' => array('RAND()'))
               );
               $q->addOrder("RAND", "ASC");
               $result = $q->exec();
        

        User Forms for Element Editing

        The form of adding/changing elements of information blocks is one of those most frequently used, and this form is definitely one of the most popular in the administrative section of online stores or information editions. Although the form’s interface and fields change depending on the information block settings, the interface of the element editing form can be set up using standard tools of the system; however, these tools may prove insufficient for certain specific tasks.

        In this case, one or two (depending on the task) additional files should be created in /bitrix/php_interface/include/:

        After that, the paths to these files shall be set:

        File with the Element Editing Form

        Let us create, for example, a file my_iblock_element_edit.php in the folder /bitrix/php_interface/include/, and copy a code from the file /bitrix/modules/iblock/admin/iblock_element_edit.php into it from the string:

        	//////////////////////////
        	//START of the custom form
        	//////////////////////////
        

        up to the string:

        	//////////////////////////
        	//END of the custom form
        	//////////////////////////
        

        Now we can start editing the file, i.e. changing the external appearance of the editing form of the infoblock element according to our needs:

        • You can remove the infoblock fields that you do not need. The following structures are used in order to display form fields:

          <?
          $tabControl->BeginCustomField("ACTIVE_TO", GetMessage("IBLOCK_FIELD_ACTIVE_PERIOD_TO"), $arIBlock["FIELDS"]["ACTIVE_TO"]["IS_REQUIRED"] === "Y");
          ?>
          	<tr id="tr_ACTIVE_TO">
          		<td><?echo $tabControl->GetCustomLabelHTML()?>:</td>
          		<td><?echo CAdminCalendar::CalendarDate("ACTIVE_TO", $str_ACTIVE_TO, 19, true)?></td>
          	</tr>
          <?
          $tabControl->EndCustomField("ACTIVE_TO", '<input type="hidden" id="ACTIVE_TO" name="ACTIVE_TO" value="'.$str_ACTIVE_TO.'">');
          ?>
          

        • The function _ShowPropertyField() is used to display infoblock elements in the property form:

          <?
          $prop_code = "AUTHOR";
          $prop_fields = $PROP[$prop_code];
          $prop_values = $prop_fields["VALUE"];  
          
          $tabControl->BeginCustomField("PROPERTY_".$prop_fields["ID"],
                                        $prop_fields["NAME"],
                                        $prop_fields["IS_REQUIRED"]==="Y"); 
          ?>
          
          <tr id="tr_PROPERTY_<?echo $prop_fields["ID"];?>">
            <td class="adm-detail-valign-top" width="40%"><?if($prop_fields["HINT"]!=""):
          	?><span id="hint_<?echo $prop_fields["ID"];?>"></span>
                    <script>BX.hint_replace(BX('hint_<?echo $prop_fields["ID"];?>'), '<?echo CUtil::JSEscape($prop_fields["HINT"])?>');</script>
             <?endif;?><?echo $tabControl->GetCustomLabelHTML();?>:</td>
            <td width="60%"><?_ShowPropertyField('PROP['.$prop_fields["ID"].']',
                                                 $prop_fields,
                                                 $prop_fields["VALUE"],
                                                 (($historyId <= 0) && (!$bVarsFromForm) && ($ID<=0)),
                                                 $bVarsFromForm, 50000,
                                                 $tabControl->GetFormName(),
                                                 $bCopy);?></td>
          </tr>
          
          <?  
          $tabControl->EndCustomField("PROPERTY_".$prop_fields["ID"], $hidden);  
          ?>
          

        When an own form on the element editing page is used, the Settings button disappears. This button permits you to sort and set up the display of the form fields of an element.

        The following code shall be added to the file in order to avoid adding a field sorting mechanism to my_iblock_element_edit.php and to maintain the standard function:

        <?
        // "Settings" button
        $aMenu = array();
        if (false == ((true == defined('BT_UT_AUTOCOMPLETE')) && (1 == BT_UT_AUTOCOMPLETE)))
        {
           $link = DeleteParam(array("mode"));
           $link = $GLOBALS["APPLICATION"]->GetCurPage()."?mode=settings".($link <> ""? "&".$link:"");
           $aMenu[] = array(
              "TEXT"=>GetMessage("IBEL_E_SETTINGS"),
              "TITLE"=>GetMessage("IBEL_E_SETTINGS_TITLE"),
              "LINK"=>"javascript:".$tabControl->GetName().".ShowSettings('".urlencode($link)."')",
              "ICON"=>"btn_settings",
           );
           
           $context = new CAdminContextMenu($aMenu);
           $context->Show();
        }
        ?>
        

        Important! Do not forget to indicate the path to this file in the infoblock settings.

        The file responsible for processing the element fields before saving the element

        In order to change the fields that are to be saved, homonymous fields in the arrays $_POST and $_FILES, must be modified, and the values of all of the properties must be modified in the array $PROP.

        For example, let us create a file before_iblock_element_edit.php in /bitrix/php_interface/include/.

        Let us use the following condition to make sure that the detailed description of the element is entered:

        if (strlen($_POST['DETAIL_TEXT'])<=0)
           $error = new _CIBlockError(2, 'DESCRIPTION_REQUIRED', 'Add article text');

        The _CIBlockError object constructor admits 3 parameters: error severity level, arbitrary identifier, and the error message. If the value of this object is set in the variable $error on the editing page, the changes made will not be saved. To prevent a loss of the values that come from the form, you have to initialize the variable $bVarsFromForm=true after initializing the variable $error. It is the variable $bVarsFromForm that indicates that the values to be displayed in the fields must be those that came from the form.

        Let us use the function BXIBlockAfterSave to automatically create a small picture based on the larger one. If we define it before saving the element, it will be retrieved automatically once the element is saved successfully. Let us define it in the beginning of the file /bitrix/php_interface/include/before_iblock_element_edit.php:

        <?
        function BXIBlockAfterSave($arFields)
        {
                $dbr = CIBlockElement::GetByID($arFields['ID']);
                if(($ar = $dbr->Fetch()) && $ar['DETAIL_PICTURE']>0)
                {
                    $img_path = $_SERVER['DOCUMENT_ROOT'].CFile::GetPath($ar['DETAIL_PICTURE']);
                    $width = 200;
                    $height = 200;
                    list($width_orig, $height_orig) = getimagesize($img_path);
                    if($width && ($width_orig < $height_orig))
                       $width = ($height / $height_orig) * $width_orig;
                    else
                       $height = ($width / $width_orig) * $height_orig;
                    $image_p = imagecreatetruecolor($width, $height);
                    $image = imagecreatefromjpeg($img_path);
                    imagecopyresized($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
                    $new_img_path = tempnam("/tmp", "FOO").".jpg";
                    imagejpeg($image_p, $new_img_path);
                    $be = new CIBlockElement();
                    $be->Update($arFields['ID'], Array('PREVIEW_PICTURE' => CFile::MakeFileArray($new_img_path)), false);
                    @unlink($new_img_path);
                }
        }
        ?>

        Note: In the aforementioned script, a preview picture will be created based on the Detailed picture, and thus the created picture will be substituted in the field Preview picture. The example works only with JPG images.

        Let us look at the complete code of the page /bitrix/php_interface/include/before_block_element_edit.php:

        <?
        if($REQUEST_METHOD=="POST" && strlen($Update)>0 && $view!="Y" && (!$error) && empty($dontsave) && strlen($_POST['DETAIL_TEXT'])<=0)
           $error = new _CIBlockError(2, "DESCRIPTION_REQUIRED", "Add article text");
        
        function BXIBlockAfterSave($arFields)
        {
                $dbr = CIBlockElement::GetByID($arFields['ID']);
                if(($ar = $dbr->Fetch()) && $ar['DETAIL_PICTURE']>0)
                {
                    $img_path = $_SERVER['DOCUMENT_ROOT'].CFile::GetPath($ar['DETAIL_PICTURE']);
                    $width = 200;
                    $height = 200;
                    list($width_orig, $height_orig) = getimagesize($img_path);
                    if($width && ($width_orig < $height_orig))
                       $width = ($height / $height_orig) * $width_orig;
                    else
                       $height = ($width / $width_orig) * $height_orig;
                    $image_p = imagecreatetruecolor($width, $height);
                    $image = imagecreatefromjpeg($img_path);
                    imagecopyresized($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
                    $new_img_path = tempnam("/tmp", "FOO").".jpg";
                    imagejpeg($image_p, $new_img_path);
                    $be = new CIBlockElement();
                    $be->Update($arFields['ID'], Array('PREVIEW_PICTURE' => CFile::MakeFileArray($new_img_path)), false);
                    @unlink($new_img_path);
                }
        }
        ?>

        Important! Do not forget to specify the path to this file in the infoblock settings.


        Sometimes completely different changes must be made, for example the form of entry and change of several pictures simultaneously. In this case, we just have to create a new page and add it to the administrative menu.


        Errors When Working with Infoblocks

        Error of the Type:

        Fatal error: Class 'CIBlockElement' not found in /hosting/site.com/www/index.php on line XX

        The script uses the method of the Information Blocks module, but the module itself is not connected. The module should be connected:

        CModule::IncludeModule("iblock");


        Error of the Type:

        Fatal error: Call to a member function GetNextElement() on a non-object in /hosting/site.com/www/index.php on line XX

        You are likely to have submitted wrong parameters to a method. For example:

        $res = CIBlockElement::GetList(array(), $arFilter, array(), array(), $arSelect);

        While the third parameter must be true/false, and not array.

        Examples

        This chapter provides for examples on using the Information Blocks module.

        Handling custom iblock properties

        Examples for solving objectives, occurring when handling iblock elements, sections and properties.

      • Getting all property values for an element with known ID
      • Getting properties for elements, knowing the method CIBlockElement::GetList
      • Adding TEXT/html property for an element
      • Completing a File multiple property
      • Completing a List multiple property
      • Getting a section custom field
      • Example for creating a custom data type for custom property
      • How to delete a file in an iblock element property
      • Objective 1:
        Get all property values for an element with a known ID.

        1 <? $db_props = CIBlockElement::GetProperty(IBLOCK_ID, ELEMENT_ID, "sort", "asc", array());
        2 $PROPS = array();
        3 while($ar_props = $db_props->GetNext())
        4 $PROPS[$ar_props['CODE']] = $ar_props['VALUE'];?>

        Now character ID for a property is the key for an associative array $PROPS, if you need property value with price code, it will be stored in $PROPS['price'].

        Objective 2:
        Getting property values via the method CIBlockElement::GetList

        1	<? $arSelect = array("ID", "NAME", "PROPERTY_prop_code_1", "PROPERTY_prop_code_2");
        2 $res = CIBlockElement::GetList(array(), array(), false, array(), $arSelect);?>

        Next, use the cycle and get properties with codes prop_code_1 and prop_code_2.

        Objective 3:
        Add property type TEXT/html for the element.

        In case the property is not a multiple type:

        01 <? $element = new CIBlockElement;
        02 $PROP = array();
        03 $PROP['property character code']['VALUE']['TYPE'] = 'text'; // or html
        04 $PROP['property character code']['VALUE']['TEXT'] = 'value, to enter';
        05 $arLoadArray = array(
        06 	"IBLOCK_ID"      => IBLOCK_ID,
        07 	"PROPERTY_VALUES"=> $PROP,
        08 	"NAME"           => "Element name"
        09 	);
        10 	$element->Add($arLoadArray);?>

        In case the property is a multiple type:

        01	<? // В $ITEMS stores multiple properties to enter
        02 foreach($ITEMS as $item)
        03 {
        04 	$VALUES[]['VALUE'] = array(
        05 	'TYPE' => 'text', // ir html
        06 	'TEXT' => $item,
        07 	);
        08	}
        09	$element = new CIBlockElement;
        10	$PROPS = array();
        11	$PROPS['property character code'] = $VALUES;
        12	$arLoadArray = array(
        13	  "IBLOCK_ID"      => IBLOCK_ID,
        14	  "PROPERTY_VALUES"=> $PROPS,
        15	  "NAME"           => "Element name"
        16	  );
        17 $element->Add($arLoadArray);?>
        Objective 4:

        Complete File multiple property. Quite frequently, when adding an element to the iblock you may need to associate several files to it. For this purpose, it's useful to create a File multiple property and store files within it. Example for completing the property:

        01 <?
        02 $arFiles = array();
        03 for($i = 1; $i < 10; $i++)
        04 {
        05 	if(file_exists($_SERVER['DOCUMENT_ROOT'].'/images/image_'.$i.'.jpg'))
        06 	{
        07 		$arFiles[] = array('VALUE' => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"].'/images/image_'.$i.'.jpg'), 'DESCRIPTION' => '');
        08 	}
        09 }
        10 ?>

        After this, the array $arFiles passes as property value when adding an element.

        Objective 5:

        Complete the List multiple property with checkmark flags display. In this case, each element of the value list has its own ID. You can browse them, by entering into property detailed edit. The property is completed as follows:

        1 <?
        2 if($first_condition == true) $values[] = array('VALUE' => 1);
        3 if($second_condition == true) $values[] = array('VALUE' => 2);
        4 CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array('property_code' => $values));
        5 ?>

        In this case, when executing first and second conditions we are marking flags the items from the list with ID =1 and ID=2 accordingly. You need to replace $ELEMENT_ID, $IBLOCK_ID and property_code with desired values.

        Objective 6:
        Get a custom section property

        1 <? $section_props = CIBlockSection::GetList(array(), array('IBLOCK_ID' => IBLOCK_ID, 'ID' => SECTION_ID), true, array("UF_ADDITIONAL_PRICE"));
        2 $props_array = $section_props->GetNext(); ?>

        Now the $props_array['UF_ADDITIONAL_PRICE'] contains the property UF_ADDITIONAL_PRICE for iblock section.

        Advice from developers.
        When handling iblocks, its more convenient for all property ID to be named with capital letters. In this case you can avoid small inconsistencies in your work.

        For example, property with the foto code when handling components is most frequently accessible via [PROPERTIES][foto][VALUE]?, and you can get PROPERTY_FOTO_VALUE when using the method GetList.

        Example of creating your own custom data type for a custom property

        Let's add am image with preview as the property value. For example, it can be photos of a hotel at the tourist website or something similar. This objective will be achieved within this context.

        One of variant for implementation: store images in a an individual iblock and show as association to an element. Example of code:

        AddEventHandler("iblock", "OnIBlockPropertyBuildList", array("CIBlockPropertyPicture", "GetUserTypeDescription"));
        AddEventHandler("iblock", "OnBeforeIBlockElementDelete", array("CIBlockPropertyPicture", "OnBeforeIBlockElementDelete"));
        class CIBlockPropertyPicture
        {
        	function GetUserTypeDescription()
        	{
        		return array(
        			"PROPERTY_TYPE"      =>"E",
        			"USER_TYPE"      =>"Picture",
        			"DESCRIPTION"      =>"Hotel photo",
        			"GetPropertyFieldHtml" =>array("CIBlockPropertyPicture", "GetPropertyFieldHtml"),
        			"GetPublicViewHTML" =>array("CIBlockPropertyPicture", "GetPublicViewHTML"),
        			"ConvertToDB" =>array("CIBlockPropertyPicture", "ConvertToDB"),
        			//"GetPublicEditHTML" =>array("CIBlockPropertyPicture","GetPublicEditHTML"),
        			//"GetAdminListViewHTML" =>array("CIBlockPropertyPicture","GetAdminListViewHTML"),
        			//"CheckFields" =>array("CIBlockPropertyPicture","CheckFields"),
        			//"ConvertFromDB" =>array("CIBlockPropertyPicture","ConvertFromDB"),
        			//"GetLength" =>array("CIBlockPropertyPicture","GetLength"),
        			);
        	}
        	function GetPropertyFieldHtml($arProperty, $value, $strHTMLControlName)
        	{
        		$LINK_IBLOCK_ID = intval($arProperty["LINK_IBLOCK_ID"]);
        		if($LINK_IBLOCK_ID)
        		{
        			$ELEMENT_ID = intval($value["VALUE"]);
        			if($ELEMENT_ID)
        			{
        				$rsElement = CIBlockElement::GetList(array(), array("IBLOCK_ID" => $arProperty["LINK_IBLOCK_ID"], "ID" => $value["VALUE"]), false, false, array("ID", "PREVIEW_PICTURE", "DETAIL_PICTURE"));
        			$arElement = $rsElement->Fetch();
        			if(is_array($arElement))
        				$file_id = $arElement["DETAIL_PICTURE"];
        			else
        				$file_id = 0;
        			}
        			else
        			{
        				$file_id = 0;
        			}
        			if($file_id)
        			{
        				$db_img = CFile::GetByID($file_id);
        				$db_img_arr = $db_img->Fetch();
        			if($db_img_arr)
        			{
        				$strImageStorePath = COption::GetOptionString("main", "upload_dir", "upload");
        				$sImagePath = "/".$strImageStorePath."/".$db_img_arr["SUBDIR"]."/".$db_img_arr["FILE_NAME"];
        				return '<label><input name="'.$strHTMLControlName["VALUE"].'[del]" value="Y" type="checkbox">Удалить файл '.$sImagePath.'</label>.'<input name="'.$strHTMLControlName["VALUE"].'[old]" value="'.$ELEMENT_ID.'" type="hidden">';
        			}
        			}
        			return '<input type="file" size="'.$arProperty["COL_COUNT"].'" name="'.$strHTMLControlName["VALUE"].'"/>';
        		}
        		else
        		{
                 return "Property settings error. Specify iblock to store images.";
        		}
        	}
        	function GetPublicViewHTML($arProperty, $value, $strHTMLControlName)
        	{
        		$LINK_IBLOCK_ID = intval($arProperty["LINK_IBLOCK_ID"]);
        		if($LINK_IBLOCK_ID)
        		{
        			$ELEMENT_ID = intval($value["VALUE"]);
        			if($ELEMENT_ID)
        			{
        				$rsElement = CIBlockElement::GetList(array(), array("IBLOCK_ID" => $arProperty["LINK_IBLOCK_ID"], "ID" => $value["VALUE"]), false, false, array("ID", "PREVIEW_PICTURE", "DETAIL_PICTURE"));
        				$arElement = $rsElement->Fetch();
        				if(is_array($arElement))
        					return CFile::Show2Images($arElement["PREVIEW_PICTURE"], $arElement["DETAIL_PICTURE"]);
        			}
        		}
        		return "";
        	}
        	function ConvertToDB($arProperty, $value)
        	{
        		$arResult = array("VALUE" => "", "DESCRIPTION" => "");
        		$LINK_IBLOCK_ID = intval($arProperty["LINK_IBLOCK_ID"]);
        		if($LINK_IBLOCK_ID)
        		{
        			if(
        				is_array($value["VALUE"])
        				&& is_array($value["VALUE"]["error"])
        				&& $value["VALUE"]["error"]["VALUE"] == 0
        				&& $value["VALUE"]["size"]["VALUE"] > 0
        			)
        			{
        				$arDetailPicture =  array(
        					"name" => $value["VALUE"]["name"]["VALUE"],
        					"type" => $value["VALUE"]["type"]["VALUE"],
        					"tmp_name" => $value["VALUE"]["tmp_name"]["VALUE"],
        					"error" => $value["VALUE"]["error"]["VALUE"],
        					"size" => $value["VALUE"]["size"]["VALUE"],
        				);
        				$obElement = new CIBlockElement;
        				$arResult["VALUE"] = $obElement->Add(array(
        					"IBLOCK_ID" => $LINK_IBLOCK_ID,
        					"NAME" => $arDetailPicture["name"],
        					"DETAIL_PICTURE" => $arDetailPicture,
        				), false, false, true);
        			}
        			elseif(
        				is_array($value["VALUE"])
        				&& isset($value["VALUE"]["size"])
        				&& !is_array($value["VALUE"]["size"])
        				&& $value["VALUE"]["size"] > 0
        			)
        			{
        				$arDetailPicture =  array(
        					"name" => $value["VALUE"]["name"],
        					"type" => $value["VALUE"]["type"],
        					"tmp_name" => $value["VALUE"]["tmp_name"],
        					"error" => intval($value["VALUE"]["error"]),
        					"size" => $value["VALUE"]["size"],
        					);
        					$obElement = new CIBlockElement;
        					$arResult["VALUE"] = $obElement->Add(array(
        					"IBLOCK_ID" => $LINK_IBLOCK_ID,
        					"NAME" => $arDetailPicture["name"],
        					"DETAIL_PICTURE" => $arDetailPicture,
        				), false, false, true);
        			}
        			elseif($value["VALUE"]["del"])
        			{
        				$obElement = new CIBlockElement;
        				$obElement->Delete($value["VALUE"]["old"]);
        			}
        			elseif($value["VALUE"]["old"])
        			{
        				$arResult["VALUE"] = $value["VALUE"]["old"];
        			}
        				elseif(!is_array($value["VALUE"]) && intval($value["VALUE"]))
        			{
        				$arResult["VALUE"] = $value["VALUE"];
        			}
        		}
        		return $arResult;
        	}
        	function OnBeforeIBlockElementDelete($ELEMENT_ID)
        	{
        		$arProperties = array();
        		$rsElement = CIBlockElement::GetList(array(), array("ID" => $ELEMENT_ID), false, false, array("ID", "IBLOCK_ID"));
        		$arElement = $rsElement->Fetch();
        		if($arElement)
        		{
        			$rsProperties = CIBlockProperty::GetList(array(), array("IBLOCK_ID" => $arElement["IBLOCK_ID"], "USER_TYPE" => "Picture"));
        			while($arProperty = $rsProperties->Fetch())
                    $arProperties[] = $arProperty;
        		}
        		$arElements = array();
        		foreach($arProperties as $arProperty)
        		{
                 $rsPropValues = CIBlockElement::GetProperty($arElement["IBLOCK_ID"], $arElement["ID"], array(), array(
                    "EMPTY" => "N",
                    "ID" => $arProperty["ID"],
        				));
        				while($arPropValue = $rsPropValues->Fetch())
        			{
        					$ID = intval($arPropValue["VALUE"]);
        					if($ID > 0)
        						$arElements[$ID] = $ID;
        			}
        		}
        		foreach($arElements as $to_delete)
        		{
                 CIBlockElement::Delete($to_delete);
        		}
        	}
        }

        As the result we have the following:

        • Element edit interface with an option to add and delete images.
        • When deleting an element, information associated with it, is deleted.
        • Component support in the public section.

        Manual:

        • Place this code in the file /bitrix/php_interface/init.php.
        • Create an information block for image storage and indicate in its settings the parameters for preview image generation from details (in the Fields tab).
        • Add a Picture proeprty in the Hotels iblock and indicate an iblock, created at the first step, in the additional settings for this property. Do not forget to specify a character ID code for the property.
        • Create an element and try to change up different values for this property.
        • For example, select this property in the list config parameters inside the public section's news component.

        How to delete a file inside iblock property

        Update any property using the following methods:

        When using any method, the update array key receives property ID and a new value. You need to pass the following simple array to delete the file:

        array('MY_FILE' => array('XXX' => array('del' => 'Y')));

        This method is universal and suits for both iblocks and iblocks 2.0 and document processing as well. MY_FILE - is the code for your File property. What is ХХХ? It contains property value ID. It means specifically value ID, not the property ID.

        CModule::IncludeModule('iblock');
        $IB = 24;
        $ID = 220304;
        $CODE = 'ONE_FL';
        if ($arProp = CIBlockElement::GetProperty($IB, $ID, 'ID', 'DESC', array('CODE' => $CODE))->fetch()) {
        	$XXX = $arProp['PROPERTY_VALUE_ID'];
        	CIBlockElement::SetPropertyValueCode($ID, $CODE, array($XXX => array('del' => 'Y')));
        }

        This way, you will have a universal XXX, and it must be passed for each file targeted for deletion.

        What can be done in case of a multiple file? How to delete a file inside a list? It's simple - use the while instead of if in the example above and, additional filter which file is to be deleted.


        Examples of working with multiple properties

        Task 1: Delete one of the values of a multiple property of the infoblock element.

        Solution:

        $el = new CIBlockElement;
        $PROP = array();
        $PROP[property_id][id] = "4";  
        $PROP[property_id][id] = "5";
        $PROP[property_id][id] = "6";
        
        $arLoadProductArray = Array(
          "IBLOCK_ID" => $B_ID,
          "PROPERTY_VALUES" => $PROP,
          "NAME" => "Element",
          );
        
        $PRODUCT_ID = $E_ID;
        $res = $el->Update($PRODUCT_ID, $arLoadProductArray);
        

        In this case, in order to delete the value, it is sufficient to exclude the pair: key and value of the property to be deleted from the array $PROP. This solution is appropriate when the property value id must be left unchanged.

        $PROP[property_id ][id ]

        Alternatively, the method SetPropertyValues may be used as a solution:

        CIBlockElement::SetPropertyValues($ELEMENT_ID, $IBLOCK_ID, $PROPERTY_VALUE, $PROPERTY_CODE);

        False shall be submitted to the fourth parameter of the function, and the "property code"=>"value" array – to the third parameter.

        In this case, all of the values will be deleted except for those indicated in the array submitted to the third parameter.


        Task 2: Adding a specific value for a multiple property of the file type:

        Solution:

        //68 – property id;
        //FILES – symbol code of a multiple property of the file type;
        
        $ELEMENT_ID = 392;
        $arFile = CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif");
        $arFile["MODULE_ID"] = "iblock";
        
        $PROPERTY_VALUE["68"][n0] = $arFile;  
        CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array("VALUE"=>$arFile) ) );
        

        Task 3: Adding several values for a multiple property of the file type:

        Solution:

        $arFile = array(
        0 => array("VALUE" => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/01.gif"),"DESCRIPTION"=>""),
        1 => array("VALUE" => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif"),"DESCRIPTION"=>"")
        );
        CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array($PROPERTY_CODE => $arFile));
        

        Task 4: Deleting a specific value of a multiple property of the file type:

        Solution:

        //68 – property id;
        //FILES – symbol code of a multiple property of the file type;
        //2033 – property value id;
        
        $ELEMENT_ID = 392;
        $arFile["MODULE_ID"] = "iblock";
        $arFile["del"] = "Y";
        
        $PROPERTY_VALUE["68"]["2033"] = $arFile; 
        CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array ("2033" => Array("VALUE"=>$arFile) ) );
        

        Task 5: Updating a specific value of a multiple property of the file type file:

        Solution:

        //68 – property id;
        //FILES – symbol code of a multiple property of the file type;
        //2033 – property value id;
        
        $ELEMENT_ID = 392;
        $arFile = CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif");
        $arFile["MODULE_ID"] = "iblock";
        $arFile["del"] = "Y";
        
        $PROPERTY_VALUE["68"]["2033"] = $arFile;  
        CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array ("2033" => Array("VALUE"=>$arFile) ) );
        

        Task 6: Setting a multiple property of the string type with a value description field:

        Solution using SetPropertyValueCode:

        $arValues = array(
          0 => array("VALUE"=>"value","DESCRIPTION"=>"value description"),
          1 => array("VALUE"=>"value2","DESCRIPTION"=>"value description2") 
        );  
        CIBlockElement::SetPropertyValueCode($IBLOCK_ID, $PROP_CODE, $arValues);  
        

        Solution using SetPropertyValuesEx:

        $PROPERTY_VALUE = array(
          0 => array("VALUE"=>"value","DESCRIPTION"=>"value description"),
          1 => array("VALUE"=>"value2","DESCRIPTION"=>"value description2") 
        );
        CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array($PROPERTY_CODE => $PROPERTY_VALUE));
        

        Task 7: Updating a multiple property of the text type without changing the DESCRIPTION:

        Solution:

        CIBlockElement::SetPropertyValues($nProductID, $nIblockID, array(
                     array(
                         "VALUE" => array(
                             "TEXT"=>time(),
                             "TYPE"=>"HTML"
                          ),
                         "DESCRIPTION"=>"111"),
                     array(
                         "VALUE" => array(
                             "TEXT"=>time(),
                             "TYPE"=>"HTML"
                          ),
                         "DESCRIPTION"=>"222"),
                    ), $prop['ID']);
        

        Copy element field values to properties

        Let us consider an example of the function which copies the field values of infoblock elements ($_FROM_FIELD_NAMES) to the properties of the infoblock elements ($TO_PROPERTY_NAMES).

        The fields Date (DATE_ACTIVE_FROM) and Activity End (DATE_ACTIVE_TO) will be copied to the properties DATE_BEGIN and DATE_END of infoblock elements with ID = 22:

         function copy_from_fields_to_propertys_values( $IBLOCK_ID, $_FROM_FIELD_NAMES, $TO_PROPERTY_NAMES ){
            /* *
             * $_FROM_FIELD_NAMES = array(DATE_ACTIVE_FROM, DATE_ACTIVE_TO);
             * $TO_PROPERTY_NAMES = array(DATE_BEGIN, DATE_END);
             * copy_from_fields_to_propertys_values(22, array("DATE_ACTIVE_FROM","DATE_ACTIVE_TO"), array("DATE_BEGIN","DATE_END"));
             * */
                if ( CModule::IncludeModule ( "iblock" ) ){
                    $arOrder = array(
                        "sort" => "ASC",
                    );
        
                    $arFilter = array(
                        "IBLOCK_ID" => $IBLOCK_ID,
                    );
        
                    foreach ( $TO_PROPERTY_NAMES as $property_name ) {
                        $TO_PROPERTY_NAMES_with_prop[] = 'PROPERTY_' . $property_name;
                    }
        
                    $arSelect = array(
                        "NAME",
                        "ID"
                    );
        
                    $arSelect = array_merge ( $arSelect, $_FROM_FIELD_NAMES, $TO_PROPERTY_NAMES_with_prop );
        
                    $db_elemens = CIBlockElement::GetList ( $arOrder, $arFilter, false, false, $arSelect );
        
                    while ( $arElement = $db_elemens->Fetch () ) {
                        $PRODUCT_ID = $arElement["ID"];
        
                        foreach ( $TO_PROPERTY_NAMES as $key => $property_name ) {
                            CIBlockElement::SetPropertyValues ( $PRODUCT_ID, $IBLOCK_ID, $arElement[$_FROM_FIELD_NAMES[$key]], $property_name );
                        }
                    }
        
                } else {
                    die( "The iblock module is not set" );
                }
            }
        

        Additional Information



        Obtaining the sum of field values of related iblocks

        Let us assume that we have related infoblocks and we face the following task: to obtain a sum of field values of related elements in a specific field of an infoblock element.

        Solution is possible both with using the module e-Store or without it. If the e-Store module is used, all of the infoblocks must have properties of a trade catalog with an indication of the price in the appropriate fields. If the version without the e-Store module is used, all of the fields of the source infoblocks must have the number type and (as a part of this solution) have the PRICE code.

        Let us assume that the resulting infoblock is called COST. It must have the number type. The following expression must be entered in the field “Default values” of the COST parameter:

        • {PROP_1_PRICE}+{PROP_2_PRICE}+... - for versions without e-Store.
        • {PROP_1} + {PROP_2}+... - for versions with e-Store.

        The code below is provided to obtain results in the component catalog.section. Code modification is required in order to display results in another component. The code provided shall be entered in the file result_modifer.php of the indicated component:

        <?
        //This string can be uncommented and see the contents arResult
        //of the component for which this modifier is to be adapted
        
        //echo "<pre>",htmlspecialchars(print_r($arResult, 1)),"</pre>";
        
        
        //The symbol code of a property with a default value containing expressions
        //the calculation result will be displayed by the component template
        //The expression itself represents a PHP code executable by eval
        //in which specific values will be substituted to the templates of the type {}
        //These properties must be selected in the component settings and available through arResult
        //otherwise, the function CIBlockElement::GetProperty must be used to access the database
        //These properties must NOT be multiple
        //Example of the expression: "({PROP_1_PRICE} + {PROP_2_PRICE}) * {PROP_COUNTER}"
        //Please pay attention to the _PRICE – it is an indication that the price of a related element must be selected! 
        //The property itself must have a symbol code PROP_1 and PROP_2, accordingly
        
        
        $CALCULATOR_CODE="COST";
        //ID of the price that will be retrieved for calculations
        $PRICE_ID = 1;
        //Infoblock identifier (for different components different fields of arResult must be chosen)
        $IBLOCK_ID = $arResult["IBLOCK_ID"];
        //We obtain metadata of the “Calculator” property (COST)
        $arProperty = CIBlockProperty::GetPropertyArray($CALCULATOR_CODE, $IBLOCK_ID);
        //If there is such property and the expression for calculation is set:
        if($arProperty && strlen($arProperty["DEFAULT_VALUE"]) > 0)
        {
           //The cycle for all the elements of the catalog
           foreach($arResult["ITEMS"] as $i => $arElement)
           {
              //We take the “Calculator”’s expression
              $EQUATION = $arProperty["DEFAULT_VALUE"];
              //Check if template substitution is necessary
              if(preg_match_all("/(\\{.*?\\})/", $EQUATION, $arMatch))
              {
                 //Cycle for all of the properties used in the expression
                 $arPropCodes = array();
                 foreach($arMatch[0] as $equation_code)
                 {
                    //This is the "price"
                    $bPrice = substr($equation_code, -7)=="_PRICE}";
                    //Symbol code of the property which value will be substituted in the expression
                    $property_code = ($bPrice? substr($equation_code, 1, -7): substr($equation_code, 1, -1));
        
                    if($bPrice)
                    {
                       //Let us find a related element
                       $rsLinkedElement = CIBlockElement::GetList(
                          array(),
                          array(
                             "=ID" => $arElement["PROPERTIES"][$property_code]["~VALUE"],
                             "IBLOCK_ID" => $arElement["PROPERTIES"][$property_code]["~LINK_IBLOCK_ID"]
                          ),
                          false, false,
                          array(
                             "ID", "IBLOCK_ID", "CATALOG_GROUP_".$PRICE_ID, "PROPERTY_PRICE"               )
                       );
                       $arLinkedElement = $rsLinkedElement->Fetch();
                       //We borrow its price
                       if($arLinkedElement["CATALOG_PRICE_".$PRICE_ID])
                          $value = doubleval($arLinkedElement["CATALOG_PRICE_".$PRICE_ID]);
                       else
                          $value = doubleval($arLinkedElement["PROPERTY_PRICE_VALUE"]);
                    }
                    else
                    {
                       //If we require not only numbers but also strings
                       //get rid of doubleval and add screening of string symbols
                       $value = doubleval($arElement["PROPERTIES"][$property_code]["~VALUE"]);
                    }
                    //Value substitution
                    $EQUATION = str_replace($equation_code, $value, $EQUATION);
                 }
        
              }
              //Calculation proper
              $VALUE = @eval("return ".$EQUATION.";");
              //and saving its result to be displayed in the template
              $arResult["ITEMS"][$i]["DISPLAY_PROPERTIES"][$CALCULATOR_CODE] = $arResult["ITEMS"][$i]["PROPERTIES"][$CALCULATOR_CODE];
              $arResult["ITEMS"][$i]["DISPLAY_PROPERTIES"][$CALCULATOR_CODE]["DISPLAY_VALUE"] = htmlspecialchars($VALUE);
           }
        }
        ?>
        


        Printing iblock element properties

        Objective

        Select an element property/properties and display in on the screen.

        Solution

        First stage of the objective is simple: method GetProperty of class CIBlockElement is detailed in the documentation.

        Second stage. Take the property HTML\text. Its value cannot be just printed (key VALUE), because its an array, containing "raw" value and its type (HTML or text). Requires a single method call: GetDisplayValue of class CIBlockFormatProperties:

        $arResult['DISPLAY_PROPERTIES'][$pid] = CIBlockFormatProperties::GetDisplayValue($arResult, $prop);

        Now, we can write as follows in the template:

        echo $arResult['DISPLAY_PROPERTIES'][$pid]['DISPLAY_VALUE'];

        And any property, which type presupposes a value formatting before display - will be converted accordingly.

        ORM

        ORM (Object-relational mapping) is a programming technique that connects databases with the concepts of object-oriented programming languages creating a "virtual object database" (Wikipedia).

        Introduction

        In the old core, its own GetList, Update, Add, and Delete are programmed for each entity.

        Deficiencies of such method:

        • Different set of parameters;
        • Different syntax of the filter fields;
        • Events may be existent or non-existent;
        • Sometimes different code for different databases (Add).
        New Core D7:

        To make database selecting and saving operations uniform with the same parameters and filters. If possible, entity tables must be serviced with a minimum of a new code. Standard events of adding/changing/deleting must be available automatically.

        The following notions were introduced to implement these purposes:

        1. Entities (Bitrix\Main\Entity\Base).
        2. Entity fields (Bitrix\Main\Entity\Field and its successors).
        3. Data manager (Bitrix\Main\Entity\DataManager).

        An entity describes a table in the database, and also contains entity fields. The DataManager performs selection and entity change operations. In practice, the work is mostly done at the level of the DataManager.

        Attention! Before starting development, ensure that the module you selected has classes methods from core D7. It can be checked by available description in the documentation D7.

        Note: This chapter provides abstract examples for purposes of clarification. Using the provided examples via copy\paste may not render the desired result for your specific project. Examples must be adapted for your specific code and database structure.

        Entity concept and description

        When creating Internet-based projects using the Bitrix Framework platform, vast functional capabilities are available out of the box for use through API calls for relevant modules. In this case, each module in the framework concept is a separate working unit which ensures a solution for a certain range of tasks.

        As a rule, the API of each module is designed based on specific tasks, and quite often call formats are different for each module. The basic functionality existing in almost every module is standardized in order to minimize these differences. These are CRUD operations: Create, Read, Update, and Delete (creating, reading, updating, and deleting data).

      • Entity concept
      • Field typing
      • Primary & autoincrement & required
      • Column name mapping
      • ExpressionField expressions
      • User fields
      • Example
      • Concept of Entities

        An entity is an aggregate of a set of objects with their intrinsic basic (low level) business logic. An entity has a set of properties which values are subject to specific processing rules.

        For example, the entity of the User means numerous users with a set of fields:

        • ID
        • First name
        • Last name
        • Password
        • Login
        • etc.

        At the same time, the database issues an ID automatically, First Name and Last Name are limited to 50 characters, Login must contain only Latin letters, numbers, and underscore sign, etc.

        Instead of programming each entity, we would rather describe it in a specific format. Such a description would be processed by the system core and would represent some sort of configuration for it:

        Book
          ID int [autoincrement, primary]
          ISBN str [match: /[0-9-]+/]
          TITLE str [max_length: 50]
          PUBLISH_DATE date

        For example, a similar description can be used for a book catalog where the system itself will monitor the correctness and integrity of the data checking of the format and the correct range of acceptable values.

        Field Typing

        Tag tools (xml, yml, and similar) are not used for the configuration of entities, php is used instead. This option provides for better development opportunities and flexibility.

        The determination of data types from the example above is as follows:

        namespace SomePartner\MyBooksCatalog;
        
        use Bitrix\Main\Entity;
        
        class BookTable extends Entity\DataManager
        {
        	public static function getTableName()
        	{
        		return 'my_book';
        	}
        	
        	public static function getMap()
        	{
        		return array(
        			new Entity\IntegerField('ID'),
        			new Entity\StringField('ISBN'),
        			new Entity\StringField('TITLE'),
        			new Entity\DateField('PUBLISH_DATE')
        		);
        	}
        }
        Attention! Notwithstanding the fact that the example means a Book as an entity, the class name is followed by a postfix: BookTable. It is intended, because the name of a descriptive class of an entity must always be completed with the word Table. The main name Book in the same namespace is considered reserved, and in the future the main name is expected to be used (in this case, the Book class) to represent entity elements as objects (at present, these entities are represented by arrays as in the old getList methods).

        The method getMap() is responsible for the description of the entity structure. It returns an array to field instances.

        Each type of the field is represented as an inheritor class Entity\ScalarField. These fields operate simple scalar values which are saved in the database 'as is'. By default, 8 of such types are available:

        • Integral number
        • Number
        • Line
        • Text
        • Date
        • Date/Time
        • Yes/No
        • Value from the list

        For the sake of consistency with code writing standards, field names should be uppercase. The names must be unique within the same entity.

        As a rule, the first parameter in the field designer is the field name, and the second parameter contains additional settings. General settings will be dealt with later on in the same chapter, but there is a specific setting for BooleanField and EnumField:

        new Entity\BooleanField('NAME', array(
        	'values' => array('N', 'Y')
        ))
        
        new Entity\EnumField('NAME', array(
        	'values' => array('VALUE1', 'VALUE2', 'VALUE3')
        ))

        Since true and false cannot be stored as such in the database, a mapping of values is created in the form of an array for BooleanField, where the first element replaces false and the second replaces true at the time of saving.

        Note: when describing the entity the table name can be set in the method getTableName. In our example, it is my_book. If this method is not defined, the table name will be generated automatically from the namespace and class name, and for this entity it will result in b_somepartner_mybookscatalog_book.

        Primary & autoincrement & required

        In the majority of cases, the entity has a primary key for one field. The same key is, as a rule, autoincremental. To inform an entity about it, we have to use parameters in the field designer:

        new Entity\IntegerField('ID', array(
        	'primary' => true
        ))

        Note: a composite primary key is also possible. For example, in the relations of two entities, the IDs of both entities will form a composite key. To learn more about this and see an example, please refer to the section N:M relations.

        This is the way to announce the ownership of a field to the primary key. Thanks to this option, the entity will control data entering and will not permit adding an entry without specifying the value for the primary key. During the update and deletion of entries it will be possible to identify them using the primary key only.

        The ID value is often not indicated but rather obtained from the database after successfully adding an entry. In this case, the entity must be informed about that:

        new Entity\IntegerField('ID', array(
        	'primary' => true,
        	'autocomplete' => true
        ))

        The flag autocomplete for an entity means that when adding a new entry the developer is not required to set values for this field. By default, this requirement is applicable only to the primary key fields, but it is possible to ask the system to also insist on setting any other field:

        new Entity\StringField('ISBN', array(
        	'required' => true
        ))

        Now, it will not be possible to add a new book without specifying its ISBN code.

        Column Name Mapping

        When describing entities for an already existing table you may wish to rename the column. For example, initially the ISBN field in the table my_book is named as ISBNCODE, and the old code uses this column name in SQL-query. If you wish to optimize the name, making it a more readable ISBN in the new API, you may use the parameter column_name:

        new Entity\StringField('ISBN', array(
        	'required' => true,
        	'column_name' => 'ISBNCODE'
        ))

        There are other instances when the same physical column of the table stores values that differ in meaning. In this case, we can create several entity fields with the same column_name.

        ExpressionField Expressions

        The system provides for the storage of data as is as well as its transformation during sampling. Let us assume that we need to obtain the age of a book in days at the same time when we obtain the issue date. This number is difficult to store in the database since, in this case, we would have to recalculate and update data daily. The age can be calculated on the side of the database:

        SELECT DATEDIFF(NOW(), PUBLISH_DATE) AS AGE_DAYS FROM my_book

        For this, it is necessary to describe a virtual field in the entity whose value is based on a SQL expression with other field(s):

        new Entity\ExpressionField('AGE_DAYS',
        	'DATEDIFF(NOW(), %s)', array('PUBLISH_DATE')
        )

        As with the rest of fields, the first parameter to set is the name. The second parameter is the text of an SQL expression, but in this case other entity fields must be replaced with placeholders according to the format sprintf. The third parameter should transmit the array with entity field names in a certain order set in the expression.

        Note: %s or %1$s, %2$s, etc. are recommended as placeholders. For example, when several fields (FIELD_X + FIELD_Y) * FIELD_X participate in the expression EXPR, the expression can be described as follows: '(%s + %s) * %s', [FIELD_X, FIELD_Y, FIELD_X]; or: '(%1$s + %2$s) * %1$s', [FIELD_X, FIELD_Y].

        Very often, expressions can be used to aggregate data (for example, COUNT(*) or SUM(FIELD)). These examples will be reviewed in the chapter Data Retrieval.

        Note: expression fields can be used only in data sampling to select, filter, group, and sort according to them. Since such columns do not exist physically in the database table, there is no place to write the field value, and the system will generate an exception.

        User-Defined (Custom) Fields

        In addition to the fields ScalarField and ExpressionField, the entity may contain User-Defined Fields. They are configured through the Administrative interface and require no additional description on the entity side. Only a selected Object of the user-defined field must be indicated in the entity:/p>

        class BookTable extends Entity\DataManager
        {
        	...
        	
        	public static function getUfId()
        	{
        		return 'MY_BOOK';
        	}
        	
        	...
        }

        Note: Support for SqlExpression values for user fields in ORM is added starting from the Main module version 20.5.200 (main).

        Later on, it is this identifier that must be indicated when attaching user-defined fields to an entity:

        Thus, it is possible to select and update values of user-defined fields on the same basis as values of standard fields of an entity.


        Example

        The following entity was obtained following this chapter:

        namespace SomePartner\MyBooksCatalog;
        
        use Bitrix\Main\Entity;
        
        class BookTable extends Entity\DataManager
        {
        	public static function getTableName()
        	{
        		return 'my_book';
        	}
        	
        	public static function getUfId()
        	{
        		return 'MY_BOOK';
        	}
        
        	public static function getMap()
        	{
        		return array(
        			new Entity\IntegerField('ID', array(
        				'primary' => true,
        				'autocomplete' => true
        			)),
        			new Entity\StringField('ISBN', array(
        				'required' => true,
        				'column_name' => 'ISBNCODE'
        			)),
        			new Entity\StringField('TITLE'),
        			new Entity\DateField('PUBLISH_DATE')
        		);
        	}
        }
        
        // code to create a table in MySQL
        // (obtained by calling BookTable::getEntity()->compileDbTableStructureDump())
        CREATE TABLE `my_book` (
        	`ID` int NOT NULL AUTO_INCREMENT,
        	`ISBN` varchar(255) NOT NULL,
        	`TITLE` varchar(255) NOT NULL,
        	`PUBLISH_DATE` date NOT NULL,
        	PRIMARY KEY(`ID`)
        );

        Therefore, it is possible to describe regular scalar fields, single out a primary key from among them, and indicate the autoincrementive fields and the required fields. If there is a discrepancy between the column name in the table and a desired name in the entity, it can be fixed.

        Attention! The getMap method is used only as a means to obtain the primary configuration of an entity. If you want to obtain the actual list of entity fields, please use the method BookTable::getEntity()->getFields().

        The only thing left is to record the entity code in the project. According to the general file naming rules in D7, the entity code must be stored in the file: local/modules/somepartner.mybookscatalog/book.php

        After that, the system will automatically connect the file upon finding calls of the BookTable class.

        Note: the example above uses the recommended data entry. The legacy entry form as an array:
        'ID' => array(
        'data_type' => 'integer',
        'primary' => true,
        'autocomplete' => true,
        ),
        has been kept for compatibility. Upon initialization, objects of classes \Bitrix\Main\Entity\* are still created. You can use both variants, but recommended and correct variant is via objects.


        Operations with Entities

        There are three methods available for writing inside the described class: BookTable::add, BookTable::update, BookTable:delete.

      • BookTable::add
      • BookTable::update
      • BookTable::delete
      • Validators
      • Events
      • Value formatting
      • Calculated values
      • Error warnings
      • BookTable::add

        The add-entry method admits as an input parameter an array with values containing entity field names as the keys:

        namespace SomePartner\MyBooksCatalog;
        
        use Bitrix\Main\Type;
        
        $result = BookTable::add(array(
        	'ISBN' => '978-0321127426',
        	'TITLE' => 'Patterns of Enterprise Application Architecture',
        	'PUBLISH_DATE' => new Type\Date('2002-11-16', 'Y-m-d')
        ));
        
        if ($result->isSuccess())
        {
        	$id = $result->getId();
        }

        The method returns the result object Entity\AddResult, and the example above shows how to check the successful adding of an entry and obtain the ID of the added entry.

        Note. The objects of the class Bitrix\Main\Type\Date and Bitrix\Main\Type\DateTime must be used as values of the fields DateField and DateTimeField and also for user-defined fields Date and Date with Time. By default, the designer receives a line date in the website format but the format of the date to be submitted can also be indicated explicitly.

        Attention! The fields must be used in upper case: FIELDS. This field in lower case is reserved for needs of the system. In similar fashion, the field auth_context is reserved by the system as well.

        BookTable::update

        Entry update follows a similar procedure; only the value of the primary key is added to the array of values in the parameters:

        $result = BookTable::update($id, array(
        	'PUBLISH_DATE' => new Type\Date('2002-11-15', 'Y-m-d')
        ));

        In the example, the date indicated in the new entry is corrected. As a result, the object Entity\UpdateResult is returned, and it also has a test method isSuccess() (to make sure that there was no errors in the query) and, additionally, it is possible to learn whether the entry was actually updated: getAffectedRowsCount().

        BookTable::delete

        Only the primary key is needed to delete the record:

        $result = BookTable::delete($id);

        Operation Results

        If one or more errors occur during the operation, their text can be obtained from the result:

        $result = BookTable::update(...);
        
        if (!$result->isSuccess())
        {
        	$errors = $result->getErrorMessages();
        }

        Default Values

        Sometimes the majority of new entries always contain the same value for a certain field or it is calculated automatically. Let us assume that the book catalog has today’s date as the issue/publishing date by default (it is logical to add a book to the catalog on the day of its issue). Let us return to the description of the field in the entity and use the parameter default_value:

        new Entity\DateField('PUBLISH_DATE', array(
        	'default_value' => new Type\Date
        ))

        Now, when adding an entry with no expressly indicated issue date, its value will be the current day:

        $result = BookTable::add(array(
        	'ISBN' => '978-0321127426',
        	'TITLE' => 'Some new book'
        ));

        Let us consider a more complicated task: there is no possibility to promptly add books on the day of their issue but it is known that, as a rule, new books are issued on Fridays. Accordingly, they will be added only in the course of the following week:

        new Entity\DateField('PUBLISH_DATE', array(
        	'default_value' => function () {
        		// figure out last friday date
        		$lastFriday = date('Y-m-d', strtotime('last friday'));
        		return new Type\Date($lastFriday, 'Y-m-d');
        	}
        ))

        Any callable value can be the value of the parameter default_value: a function name, an array from class/object and a name of the method, or an anonymous function.

        Validators

        Before writing new data to the database their correctness must be checked without fail. This can be done with the help of validators:

        new Entity\StringField('ISBN', array(
        	'required' => true,
        	'column_name' => 'ISBNCODE',
        	'validation' => function() {
        		return array(
        			new Entity\Validator\RegExp('/[\d-]{13,}/')
        		);
        	}
        ))

        Now each time you add or edit an entry, the ISBN will be checked using the template [\d-]{13,} – the code must contain only numbers and a hyphen, with a minimum of 13 digits.

        Validation is set using the parameter validation in the field designer and is a callback that returns an array of validators.

        Note: Why validation – callback, and not just an array of validators? It is a kind of deferred load: validators will be instantiated only when data validation is really needed. Generally no validation is needed for data sampling from the database.

        An inheritor Entity\Validator\Base or any callable that is to return a true or a text of an error, or an object of Entity\FieldError (if you want to use own code of the error) is accepted as a validator.

        It is known for sure that the ISBN code must contain 13 digits, and these digits can be separated by several hyphens:

        978-0321127426
        978-1-449-31428-6
        9780201485677

        To make sure that there are exactly 13 digits, let us write our own validator:

        new Entity\StringField('ISBN', array(
        	'required' => true,
        	'column_name' => 'ISBNCODE',
        	'validation' => function() {
        		return array(
        			function ($value) {
        				$clean = str_replace('-', '', $value);
        				
        				if (preg_match('/^\d{13}$/', $clean))
        				{
        					return true;
        				}
        				else
        				{
        					return ‘The ISBN code must contain 13 digits.’;
        				}
        			}
        		);
        	}
        ))

        The value of this field is submitted to the validator as the first parameter, but more optional information is also available:

        new Entity\StringField('ISBN', array(
        	'required' => true,
        	'column_name' => 'ISBNCODE',
        	'validation' => function() {
        		return array(
        			function ($value, $primary, $row, $field) {
        				// value – field value
        				// primary – an array with the primary key, in this case [ID => 1]
        				// row – all arrays of data submitted to ::add or ::update
        				// field – an object of the field under validation – Entity\StringField('ISBN', ...)
        			}
        		);
        	}
        ))

        This set of data allows for a much wider range of complex checks.

        If several validators are attached to a field and there is a need to find out on the program level which of them exactly has worked, the error code can be used. For example, the last digit of the ISBN code is a control digit that serves to check the correctness of the numerical part of ISBN. You have to add a validator for checking it and to process its result in a specific way:

        // describing validator in the entity field
        new Entity\StringField('ISBN', array(
        	'required' => true,
        	'column_name' => 'ISBNCODE',
        	'validation' => function() {
        		return array(
        			function ($value) {
        				$clean = str_replace('-', '', $value);
        
        				if (preg_match('/^\d{13}$/', $clean))
        				{
        					return true;
        				}
        				else
        				{
        					return ‘ISBN code must contain 13 digits.’;
        				}
        			},
        			function ($value, $primary, $row, $field) {
        				// checking the last digit
        				// ...
        				// if the number is wrong, a special error is returned
        				return new Entity\FieldError(
        					// if the number is wrong, a special error is returned
        				);
        			}
        		);
        	}
        ))
        // performing the operation
        $result = BookTable::update(...);
        
        if (!$result->isSuccess())
        {
        	// checking which errors have been revealed
        	$errors = $result->getErrors();
        	
        	foreach ($errors as $error)
        	{
        		if ($error->getCode() == 'MY_ISBN_CHECKSUM')
        		{
        			// our validator has worked
        		}
        	}
        }

        2 standard error codes are available by default: BX_INVALID_VALUE if the validator has worked, and BX_EMPTY_REQUIRED if no required field is indicated when adding an entry.

        Validators work both when adding new entries and when updating the existing entries. This behavior is based on the general purpose of validators consisting in guaranteeing correct and integral data in the database. The event mechanism is available in order to check data only upon their addition or updating and also for other manipulations.

        We recommend that you use standard validators in standard situations:

        • Entity\Validator\RegExp – check by regular expression,
        • Entity\Validator\Length – check the minimum/maximum line length,
        • Entity\Validator\Range – check the minimum/maximum number value,
        • Entity\Validator\Unique – check the uniqueness of a value.

        The validators described above cannot apply to the User-defined fields. Their values shall be configured in field settings through the administrative interface.

        Events

        In the example with validators, one of the checks for the ISBN field consisted in checking the availability of 13 digits. In addition to numbers, ISBN code may include hyphens, but technically speaking they have no value. In order to store only “clean” data in the database (13 digits only, without hyphens), we can use an internal event handler:

        class BookTable extends Entity\DataManager
        {
        	...
        	
        	public static function onBeforeAdd(Entity\Event $event)
        	{
        		$result = new Entity\EventResult;
        		$data = $event->getParameter("fields");
        
        		if (isset($data['ISBN']))
        		{
        			$cleanIsbn = str_replace('-', '', $data['ISBN']);
        			$result->modifyFields(array('ISBN' => $cleanIsbn));
        		}
        
        		return $result;
        	}
        }

        The method onBeforeAdd set up in the entity is automatically recognized by the system as a handler for the event “before addition” thus allowing change of data or additional checks to be done in it. In the example, we have changed the ISBN code using the method modifyFields.

        // before transformation
        978-0321127426
        978-1-449-31428-6
        9780201485677
        
        // after transformation
        9780321127426
        9781449314286
        9780201485677

        After such a transformation, we can return again to the neat validator RegExp instead of using an anonymous function (because we already know that the value will contain no acceptable hyphens and only numbers must remain):

        'validation' => function() {
        	return array(
        		//function ($value) {
        		//	$clean = str_replace('-', '', $value);
        		//
        		//	if (preg_match('/^\d{13}$/', $clean))
        		//	{
        		//		return true;
        		//	}
        		//	else
        		//	{
        		//		return 'The ISBN code must contain 13 digits.';
        		//	}
        		//},
        		new Entity\Validator\RegExp('/\d{13}/'),
        		...
        	);
        }

        In addition to data change, the event handler makes it possible to delete data or even abort the operation. For example, let us assume that the updating of the ISBN code for the books that already exist in the catalog must be prohibited. It can be done in the event handler onBeforeUpdate using one of two ways:

        public static function onBeforeUpdate(Entity\Event $event)
        {
           $result = new Entity\EventResult;
           $data = $event->getParameter("fields");
        
           if (isset($data['ISBN']))
           {
              $result->unsetFields(array('ISBN'));
           }
        
           return $result;
        }

        In this option, the ISBN will be deleted “with no fuss” as if it were not submitted. The second option consists in prohibiting its update and generating an error:

        public static function onBeforeUpdate(Entity\Event $event)
        {
        	$result = new Entity\EventResult;
        	$data = $event->getParameter("fields");
        
        	if (isset($data['ISBN']))
        	{
        		$result->addError(new Entity\FieldError(
        			$event->getEntity()->getField('ISBN'),
        			'Changing the ISBN code for the existing books is prohibited'
        		));
        	}
        
        	return $result;
        }

        If an error is returned, we have formed the object Entity\FieldError in order for us to learn during subsequent error processing in which field, exactly, the check was activated. If the error applies to more than one field or to the entire entry, the use of the object Entity\EntityError will be more appropriate:

        public static function onBeforeUpdate(Entity\Event $event)
        {
        	$result = new Entity\EventResult;
        	$data = $event->getParameter("fields");
        
        	if (...) // comprehensive data check
        	{
        		$result->addError(new Entity\EntityError(
        			'Impossible to update an entry'
        		));
        	}
        
        	return $result;
        }

        Two events were used in the examples: onBeforeAdd and onBeforeUpdate, there are nine such events in total:

        • onBeforeAdd (parameters: fields)
        • onAdd (parameters: fields)
        • onAfterAdd (parameters: fields, primary)

        • onBeforeUpdate (parameters: primary, fields)
        • onUpdate (parameters: primary, fields)
        • onAfterUpdate (parameters: primary, fields)

        • onBeforeDelete (parameters: primary)
        • onDelete (parameters: primary)
        • onAfterDelete (parameters: primary)

        The following diagram shows the sequence in which the event handlers are called, and the actions a handler may carry out.

        It goes without saying that these events can be handled in the entity itself as well as in the methods with the same name. In order to subscribe to an event in an arbitrary point of script execution, call for the event manager:

        $em = \Bitrix\Main\ORM\EventManager::getInstance();
                
        $em->addEventHandler(
        	BookTable::class, // entity class
            	DataManager::EVENT_ON_BEFORE_ADD, // event code
        		function () { // your callback
        			var_dump('handle entity event');
        		}
        );

        Value formatting

        Sometimes it may become necessary to store data in one format and work with them in the program in another. The most common example: work with an array and its serialization before saving into the database. For this, the field parameters save_data_modification and fetch_data_modification are available. They are set up similarly to the validators through callback.

        Let us use the example of a book catalog in order to describe the text field EDITIONS_ISBN: it will store the ISBN codes of other editions of the book, if any:

        new Entity\TextField('EDITIONS_ISBN', array(
        	'save_data_modification' => function () {
        		return array(
        			function ($value) {
        				return serialize($value);
        			}
        		);
        	},
        	'fetch_data_modification' => function () {
        		return array(
        			function ($value) {
        				return unserialize($value);
        			}
        		);
        	}
        ))

        We have indicated the serialization of the value before saving into the database in the parameter save_data_modification, and we have set up de-serialization during sampling from the database in the parameter fetch_data_modification. Now, when writing business logic you can simply work with the array without having to look into conversion issues.

        Attention! Before creating a serialized field, make sure the serialization will not interfere during filtering or linking tables. Search by a single value in WHERE among serialized lines is highly inefficient. You may want to opt for a normalized data storage scheme.

        Since serialization is the most typical example for conversion of values it is singled out into a separate parameter serialized:

        new Entity\TextField('EDITIONS_ISBN', array(
        	'serialized' => true
        ))

        However, you can still describe your callables for other data modification options.

        Value calculating

        More often than not, developers have to implement counters where a new value is to be calculated on the database side for the sake of data integrity instead of selecting the old value and recalculating it on the application side. In other words, the queries of the following type must be executed:

        UPDATE my_book SET READERS_COUNT = READERS_COUNT + 1 WHERE ID = 1

        If the numeric field, READERS_COUNT is described in the entity, the counter increment can be launched as follows:

        BookTable::update($id, array(
        	'READERS_COUNT' => new DB\SqlExpression('?# + 1', 'READERS_COUNT')
        ));

        The placeholder ?# means that the following argument in the designer is the database ID – the name of the database, table, or column, and this value will be masked appropriately. For all variable parameters, the use of placeholders is highly recommended. This approach will help to avoid problems with SQL injections.

        For example, if an increment number of readers is variable, it would be better to describe the expression as follows:

        // correct
        BookTable::update($id, array(
        	'READERS_COUNT' => new DB\SqlExpression('?# + ?i', 'READERS_COUNT', $readersCount)
        ));
        
        // incorrect
        BookTable::update($id, array(
        	'READERS_COUNT' => new DB\SqlExpression('?# + '.$readersCount, 'READERS_COUNT')
        ));

        The list of placeholders currently available:

        • ? or ?s – the value is masked in put between single quotes '
        • ?# – the value is masked as an identifier
        • ?i – the value is reduced to integer
        • ?f – the value is reduced to float

        Error warnings

        The examples above have a peculiarity that the data update query is called without checking the result.

        // call without checking successful query execution
        BookTable::update(...);
        
        // with check
        $result = BookTable::update(...);
        if (!$result->isSuccess())
        {
        	    // error processing
        }

        The second option is undoubtedly preferable from the point of view of control. However, if the code is executed only in the agent mode, we have no use for the list of errors occurred during validation. In this case, if the query has not gone through due to a “failed” validation and isSuccess() check was not called, the system will generate E_USER_WARNING with a list of errors which may be seen in the website log (provided that .settings.php is set up properly).

        In view of the results of this chapter, some changes have occurred in the entity description. It looks as follows now:

        namespace SomePartner\MyBooksCatalog;
        
        use Bitrix\Main\Entity;
        use Bitrix\Main\Type;
        
        class BookTable extends Entity\DataManager
        {
        	public static function getTableName()
        	{
        		return 'my_book';
        	}
        	
        	public static function getUfId()
        	{
        		return 'MY_BOOK';
        	}
        
        	public static function getMap()
        	{
        		return array(
        			new Entity\IntegerField('ID', array(
        				'primary' => true,
        				'autocomplete' => true
        			)),
        			new Entity\StringField('ISBN', array(
        				'required' => true,
        				'column_name' => 'ISBNCODE',
        				'validation' => function() {
        					return array(
        						new Entity\Validator\RegExp('/\d{13}/'),
        						function ($value, $primary, $row, $field) {
        							// check the last digit
        							// ...
        							// if the digit is incorrect we will return a special error
        							return new Entity\FieldError(
        								$field, 'ISBN control digit does not match', 'MY_ISBN_CHECKSUM'
        							);
        						}
        					);
        				}
        			)),
        			new Entity\StringField('TITLE'),
        			new Entity\DateField('PUBLISH_DATE', array(
        				'default_value' => function () {
        					// figure out last friday date
        					$lastFriday = date('Y-m-d', strtotime('last friday'));
        					return new Type\Date($lastFriday, 'Y-m-d');
        				}
        			)),
        			new Entity\TextField('EDITIONS_ISBN', array(
        				'serialized' => true
        			)),
        			new Entity\IntegerField('READERS_COUNT')
        		);
        	}
        
        	public static function onBeforeAdd(Entity\Event $event)
        	{
        		$result = new Entity\EventResult;
        		$data = $event->getParameter("fields");
        
        		if (isset($data['ISBN']))
        		{
        			$cleanIsbn = str_replace('-', '', $data['ISBN']);
        			$result->modifyFields(array('ISBN' => $cleanIsbn));
        		}
        
        		return $result;
        	}
        }

        Copy this code and play with all the options described above.


        Objects

        To start using the objects you need a described entity only. Just replace fetch with fetchObject in your code:

        $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
        	->fetchObject();
        

        Now $book - is a full-scale object of Book entity, having multiple methods for manipulating its data and establishing relations with other entities.



        Object class

        All entity objects are descendants of class Bitrix\Main\ORM\Objectify\EntityObject, with each entity having its own class for objects. By default, such class is created automatically. When you have generated ORM class annotation, with IDE highlighting this moment:

        As seen above, EO_Book object class is located in the same namespace as the Table class, with the same name, but with instead of suffix Table has the prefix EO_ (abbreviated EntityObject). Such prefix is added for backward compatibility: existing projects already can have the class Book or structure

        use Some\Another\Book;
        resulting in conflict of repeated use of Book word. We deemed the prefix EO_ as sufficiently unique. Particularly, when in a standard case use of _ character contradicts to code naming standards: no conflicts should be present with manually described classes.

        At the same time, we have allowed to conveniently customize your own, suitable class name and even place such class in another namespace, if required:

        //File bitrix/modules/main/lib/test/typography/book.php
        
        namespace Bitrix\Main\Test\Typography;
        
        class Book extends EO_Book
        {
        }
        

        Key moment - inheriting from object's base virtual class EO_Book.

        Entity must be notified about a new class as follows:

        //File bitrix/modules/main/lib/test/typography/booktable.php
        
        namespace Bitrix\Main\Test\Typography;
        
        class BookTable extends Bitrix\Main\ORM\Data\DataManager
        {
        	public static function getObjectClass()
        	{
        		return Book::class;
        	}
        	//...
        }

        Now, the method fetchObject will return objects of class Bitrix\Main\Test\Typography\Book. And after re-generating annotations, the new class start showing IDE:

        Please be advised: defining your own custom classes is recommended only when you need to directly use the class name or for expanding the class with your extra features. It's not recommended to use names for standard classes in your code, for example instanceof EO_Book, new EO_Book or EO_Book::class. In such cases, it's preferable to describe your class with more "authentic" name that corresponds to naming standards, or to use impersonal methods BookTable::getObjectClass(), BookTable::createObject(), BookTable::wakeUpObject() and etc.

        You may not only add your own functionality in your class, but also re-define standard named methods. It's not recommended to use descriptions for class properties with such names as primary, entity and dataClass because such names have been already used by the base class.



        Named methods

        Majority of methods are designated as named methods. This means that each field has the available set of personal methods:

        $book->getTitle();
        $book->setTitle($value);
        $book->remindActualTitle();
        $book->resetTitle();
        // and etc.
        

        You can overview full list of methods in other articles under this section.

        The approach mentioned above was selected due to several reasons:

        • incapsulation - you can control access individually to each field;
        • convenient use - no need to memorize field names for each entity by heart, IDE will remind you and will expedite input by autocompletion;
        • code readability - such entries look coherently and exhaustive.

        With this approach, all methods have an alternative in the form of universal methods, getting field name as one of arguments:

        $fieldName = 'TITLE';
        
        $book->get($fieldName);
        $book->set($fieldName, $value);
        $book->remindActual($fieldName);
        $book->reset($fieldName);
        // and etc.
        

        Such approach is convenient when field names are stored in memory and you can handle them in anonymized mode.

        The named method you have specified directly is called when describing your class for object and re-defining any named method via a universal method:

        namespace Bitrix\Main\Test\Typography;
        
        class Book extends EO_Book
        {
        	public function getTitle()
        	{
        		return 'custom title';
        	}
        }
        
        $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
        	->fetchObject();
        
        echo $book->getTitle(); // prints 'custom title'
        echo $book->get('TITLE'); // also prints 'custom title'
        

        Exception is the method fill: it doesn't call the named method. Design-wise, this method is aimed at optimizing database handling. In case of simultaneously calling several fields - simultaneous calls for individual methods will create an excessive load.

        Internal implementation for named methods is based on magic-method __call. Code generation can act as an alternative: compiling of classes with all methods and their subsequent caching. Bitrix24 has selected the magic method due the following reasons:

        • lower memory consumption compared to code generation, when system requires handling of excessively cumbersome classes;
        • no longer required task of caching the generated classes and monitoring of entity updates.

        Disadvantage of magic-methods is an increased consumption of processor computing resources, which can be resolved in individual cases by directly specifying frequently used methods (as it's done with the method getId in the base class). At the same time, such issue is the most easy to scale out, allowing to add new computing resources instead of a perpetual upgrade of a single operational computing power.

        Unavailable magic-methods and code generation would prevent automatic coverage of all fields from the class Table: you would need to describe all necessary fields and methods manually.



        Value types

        Objects convert values to field type. It means that values will have the value formats and string - the string formats:

        $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
        	->fetchObject();
        
        var_dump($book->getId());
        // prints int 1
        
        var_dump($book->getTitle());
        // prints string 'Title 1' (length=7)
        

        Please, be advised regarding to BooleanField: expects true or false, despite the fact that database could store other values:

        //(new BooleanField('IS_ARCHIVED'))
        //	->configureValues('N', 'Y'),
        
        $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
        	->fetchObject();
        
        var_dump($book->getIsArchived());
        // prints boolean true
        
        // set values are boolean as well
        $book->setIsArchived(false);
        


        Data read (get, require, remindActual, primary, collectValues, runtime)

        • get

          Data reading is implemented using several methods. The most simple returns a field value or null when value is missing (for example, when field is not specified in select when retrieved):

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          $title = $book->getTitle();
          
        • require

          When you are sure that field must be completed by a value and the scenario is not viable without this value, you can set this value as mandatory/required by the method require:

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          $title = $book->requireTitle();
          

          In this case, the result requireTitle() won't be different from the abovementioned getTitle(). And the next example will finish with thrown exception, because field won't be containing a value:

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, ['select' => ['ID', 'PUBLISHER_ID', 'ISBN']])
          	->fetchObject();
          
          $title = $book->requireTitle();
          // SystemException: "TITLE value is required for further operations"
          
        • remindActual

          One more getter remindActual will be useful when re-setting value to differentiate the original value from the previously set value during the session and not yet saved in the database:

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          echo $book->getTitle();
          // prints "Title 1"
          
          $book->setTitle("New title");
          
          echo $book->getTitle();
          // prints "New title"
          
          echo $book->remindActualTitle();
          // prints "Title 1"
          

          As an alternative, you can use universal unnamed methods:

          $fieldName = 'TITLE';
          
          $title = $book->get($fieldName);
          $title = $book->require($fieldName);
          $title = $book->remindActual($fieldName);
          
        • primary

          System "getter" primary is implemented as a virtual read-only property, to avoid using the method getPrimary(), thus reserving the PRIMARY field name with corresponding named getter method. Property returns primary key values in the array's format independently from whether the primary key is or composite or singular:

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          $primary = $book->primary;
          // returns ['ID' => 1]
          
          $id = $book->getId();
          // returns 1
          
        • collectValues

          Method collectValues is used to get all object values as an array.

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          $values = $book->collectValues();
          

          This example returns all available values. When some field values are re-set using the "setter", but not yet saved, returns these specific values. Unmodified fields source actual values.

          You can use optional filters to specify set of fields and data type:

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          $values = $book->collectValues(\Bitrix\Main\ORM\Objectify\Values::ACTUAL);
          // returns only current values, without not yet saved values
          
          $values = $book->collectValues(\Bitrix\Main\ORM\Objectify\Values::CURRENT);
          // returns only current values, not yet saved in the database
          
          $values = $book->collectValues(\Bitrix\Main\ORM\Objectify\Values::ALL);
          // equals to calling collectValues() without parameters - first CURRENT, then ACTUAL
          

          Second argument passes the mask, similar to the used in fill, defining field types:

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          $values = $book->collectValues(
          	\Bitrix\Main\ORM\Objectify\Values::CURRENT,
          	\Bitrix\Main\ORM\Fields\FieldTypeMask::SCALAR
          );
          // returns only updated scalar field values
          
          $values = $book->collectValues(
          	\Bitrix\Main\ORM\Objectify\Values::ALL,
          	\Bitrix\Main\ORM\Fields\FieldTypeMask::ALL & ~\Bitrix\Main\ORM\Fields\FieldTypeMask::USERTYPE
          );
          // returns values for all fields, except for user fields
          
        • runtime

          Only universal get is provided for runtime fields created within individual queries:

          $author = \Bitrix\Main\Test\Typography\AuthorTable::query()
          	->registerRuntimeField(
          		new \Bitrix\Main\Entity\ExpressionField(
          			'FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME']
          		)
          	)
          	->addSelect('ID')
          	->addSelect('FULL_NAME')
          	->where('ID', 17)
          	->fetchObject();
          
          echo $author->get('FULL_NAME');
          // prints 'Name 17 Last name 17'
          

          Storing of such values is isolated from standard field values inside the object and, correspondingly, the rest of data handling methods are not applicable.



        Write (set, reset, unset)

        • set

          Setting value is performed in the familiar manner:

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          $book->setTitle("New title");
          

          The object memorizes its original values. From this moment, the current value can be accessed via the main "getter" get and the database original/actual value can be accessed via auxiliary "getter" method remindActual:

          $book->getTitle(); // current value
          $book->remindActualTitle(); // actual database value
          

          Primary key can be set only in new objects, you cannot update it in existing objects. When it's needed, you have to create a new object and delete the old one. Also, Bitrix\Main\ORM\Fields\ExpressionField fields cannot be set due to values being calculated automatically and cannot be modified from outside.

          When setting a value different from the current value, such value won't be modified and won't be included into SQL query when saving the object.
        • reset

          To cancel the new value and rollback to the old one, you can use the auxiliary "setter" method reset:

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          echo $book->getTitle();
          // prints "Title 1"
          
          $book->setTitle("New title");
          
          echo $book->getTitle();
          // prints "New title"
          
          $book->resetTitle();
          
          echo $book->getTitle();
          // prints "Title 1"
          
        • unset

          One more auxiliary "setter" method unset deletes object as if it wasn't retrieved from the database and wasn't set at all:

          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
          	->fetchObject();
          
          echo $book->getTitle();
          // prints "Title 1"
          
          $book->unsetTitle();
          
          echo $book->getTitle();
          // null
          

          "Setter" methods also have universal variants for calling with field title as an argument:

          $fieldName = 'TITLE';
          
          $book->set($fieldName, "New title");
          $book->reset($fieldName);
          $book->unset($fieldName);
          
          All actions to update the value result in updates during session only. Read more about how to save the object to register modifications in the database here: Create and edit (save, new).


        Checks (isFilled, isChanged, has)

        • isFilled

          The method isFilled is used to verify, if object contains a current actual value from the database:

          use \Bitrix\Main\Test\Typography\Book;
          
          // Values from fetch* and wakeUp methods are deemed as actual 
          // the example demonstrates how only primary key is passed when initializing an object
          $book = Book::wakeUp(1);
          
          var_dump($book->isTitleFilled());
          // false
          
          $book->fillTitle();
          
          var_dump($book->isTitleFilled());
          // true
        • isChanged

          The method isChanged responds to question if a new value was set during the session:

          use \Bitrix\Main\Test\Typography\Book;
          
          // object may have source value, but may not 
          // this doesn't affect the further behaviour
          $book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
          
          var_dump($book->isTitleChanged());
          // false
          
          $book->setTitle('New title 1');
          
          var_dump($book->isTitleChanged());
          // true

          Such behaviour is applicable for new objects as well, until their values are stored in the database.

        • has

          The method has checks, if an object has a field value - an actual value from the database or value, specified during the session. In fact, its an abbreviation from isFilled() || isChanged():

          use \Bitrix\Main\Test\Typography\Book;
          
          $book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
          $book->setIsArchived(true);
          
          var_dump($book->hasTitle());
          // true
          
          var_dump($book->hasIsArchived());
          // true
          
          var_dump($book->hasIsbn());
          // false


        Object status

        Object can receive 3 statuses:

        • new, with data never before saved in the database;
        • actual, with data matching to the data stored in the database;
        • modified, with data different from the data stored in the database.

        You can check object status using the public read-only property state and class constants \Bitrix\Main\ORM\Objectify\State:

        use \Bitrix\Main\Test\Typography\Book;
        use \Bitrix\Main\ORM\Objectify\State;
        
        $book = new Book;
        $book->setTitle('New title');
        
        var_dump($book->state === State::RAW);
        
        $book->save();
        
        var_dump($book->state === State::ACTUAL);
        
        $book->setTitle('Another one title');
        
        var_dump($book->state === State::CHANGED);
        
        $book->delete();
        
        var_dump($book->state === State::RAW);
        
        // true


        Create and edit (save, new)

        The method save is used for registering object updates in the database:

        $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
        	->fetchObject();
        
        $book->setTitle("New title");
        
        $book->save();
        

        Note: if you copy this example and attempt to execute it with a test entity from the namespace Bitrix\Main\Test\Typography, due to specifics of test data you will get an SQL error. You will see, however, that a portion of the query with test data is built correctly.

        From the moment of saving, all current object values are converted into actual values:

        $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
        	->fetchObject();
        
        echo $book->remindActualTitle();
        // prints "Title 1"
        
        $book->setTitle("New title");
        
        echo $book->remindActualTitle();
        // prints "Title 1"
        
        $book->save();
        
        echo $book->remindActualTitle();
        // prints "New title"
        

        Regarding the new objects, there are two aprroaches for creating them. The most readable approach - directly via instantiation:

        $newBook = new \Bitrix\Main\Test\Typography\Book;
        $newBook->setTitle('New title');
        $newBook->save();
        
        $newAuthor = new \Bitrix\Main\Test\Typography\EO_Author;
        $newAuthor->setName('Some name');
        $newAuthor->save();
        

        The method operates with both standard EO_ classes and re-defined classes. Even if you initially used EO_ class, and then decided to create your own class, you won't have to re-write an existing code - backward compatibility will be saved automatically. System class with prefix EO_ becomes an "alias" to your class.

        More universal and applicable method to create new object, is to use entity's factory:

        $newBook = \Bitrix\Main\Test\Typography\BookTable::createObject();
        $newBook->setTitle('New title');
        $newBook->save();
        

        By design, new object sets all default values, described in the getMap "mapping". You can get a completely clean object by passing a corresponding argument in constructor:

        $newBook = new \Bitrix\Main\Test\Typography\Book(false);
        $newBook = \Bitrix\Main\Test\Typography\BookTable::createObject(false);
        

        Value status changes similarly as during editing. Before saving the object value is deemed as current, after saving in the database, it's status becomes as the actual.



        Deleting action

        Use the delete() method for deletion:

        // delete record
        $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
        	->fetchObject();
        
        $book->delete();
        
        // deleting by primary key
        $book = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
        
        $book->delete();

        First only the record for this object is deleted from the database. When you need to delete or attempt to perform other actions with bindings, you will need direct actions.

        Deleting using the self-titled method of Table-class, triggers all the required Events. That's why additional action can be described in the event handler onDelete.



        Restoring (wakeUp)

        When you already have available records, you don't have to retrieve them from database again. Object can be restored having primary key values as a minimum:

        $book = \Bitrix\Main\Test\Typography\Book::wakeUp(1);

        You may indicate not only the primary key, but also partial or full set of data:

        $book = \Bitrix\Main\Test\Typography\Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1', 'PUBLISHER_ID' => 253]);

        Similar to creating objects, the method is applicable for EO_ classes as well, for calling directly from entity:

        // your class
        $book = \Bitrix\Main\Test\Typography\Book::wakeUp(
        	['ID' => 1, 'TITLE' => 'Title 1']
        );
        
        // system class
        $book = \Bitrix\Main\Test\Typography\EO_Book::wakeUp(
        	['ID' => 1, 'TITLE' => 'Title 1']
        );
        
        // using the entity factory
        $book = \Bitrix\Main\Test\Typography\BookTable::wakeUpObject(
        	['ID' => 1, 'TITLE' => 'Title 1']
        );
        

        You can pass not only scalar values in wakeUp, but also values of Relations:

        $book = \Bitrix\Main\Test\Typography\Book::wakeUp([
        	'ID' => 2,
        	'TITLE' => 'Title 2',
        	'PUBLISHER' => ['ID' => 253, 'TITLE' => 'Publisher Title 253'],
        	'AUTHORS' => [
        		['ID' => 17, 'NAME' => 'Name 17'],
        		['ID' => 18, 'NAME' => 'Name 18']
        	]
        ]);
        


        Filling action

        When not all object fields are completed with data and you need to fill in the missing data, do not use the following approach:

        // initially only ID and NAME are available
        $author = \Bitrix\Main\Test\Typography\EO_Author::wakeUp(
        	['ID' => 17, 'NAME' => 'Name 17']
        );
        
        // we need to write LAST_NAME, retrieving it from database
        $row = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary($author->getId(),
        	['select' => ['LAST_NAME']]
        )->fetch();
        
        // adding value to the object
        $author->setLastName($row['LAST_NAME']);
        

        In this case, the value will be deemed as a newly set, but not an actual value (which, theoretically can lead to unforeseen collisions in further object handling).

        The correct way to use the name method for object fill:

        // initially only ID and NAME are available
        $author = \Bitrix\Main\Test\Typography\EO_Author::wakeUp(
        	['ID' => 17, 'NAME' => 'Name 17']
        );
        
        // add LAST_NAME from the database
        $author->fillLastName();
        

        In addition to name methods, a generic method is available as well. It provides a significantly more options then other generic methods:

        $author = \Bitrix\Main\Test\Typography\EO_Author::wakeUp(17);
        
        // filling several fields
        $author->fill(['NAME', 'LAST_NAME']);
        
        // filling all presently unfilled fields
        $author->fill();
        
        // filling fields by mask, for example, all unfilled scalar fields
        $author->fill(\Bitrix\Main\ORM\Fields\FieldTypeMask::SCALAR);
        
        // unfilled scalar and user fields
        $author->fill(
        	\Bitrix\Main\ORM\Fields\FieldTypeMask::SCALAR
        	| \Bitrix\Main\ORM\Fields\FieldTypeMask::USERTYPE
        );
        
        /*
         * Masks are available as follows:
         *
         * SCALAR - scalar fields (ORM\ScalarField)
         * EXPRESSION - expressions (ORM\ExpressionField)
         * USERTYPE - user fields
         * REFERENCE - relations 1:1 and N:1 (ORM\Fields\Relations\Reference)
         * ONE_TO_MANY - relations 1:N (ORM\Fields\Relations\OneToMany)
         * MANY_TO_MANY - relations N:M (ORM\Fields\Relations\ManyToMany)
         *
         * FLAT - scalar fields and expressions
         * RELATION - all relations
         *
         * ALL - absolutely all available fields
         */
        

        If you need to additionally fill in several objects, it's strongly not recommended to perform this command in a loop: this will result to a significant number of queries to the database. Handling several objects of the same type requires a similar Collection method.



        Relations (addTo, removeFrom, removeAll)

        Detailed description can be found in the next article for Relations. However, you can find specifications for managing method relations below.

        Relations fields can be handled by the already described methods get, require, fill, reset, unset.

        Important! Despite the fact that Collections object is used as a relations value, relations can be modified only via the methods addTo, removeFrom, removeAll for partner objects. Modifying a collection directly (add, remove) doesn't lead to a desirable result.

        • addTo

          The addTo method adds a new relations between objects:

          // publisher initialization
          $publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
          	->fetchObject();
          
          // book initialization
          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2)
          	->fetchObject();
          
          // adding book to a relation collection
          $publisher->addToBooks($book);
          
          // saving
          $publisher->save();
          

          Calling the method binds the object only in the system memory, you need to register the changes using the save method.

        • removeFrom

          Deleting relation associations - removeFrom - operates in a similar manner:

          // publisher initialization
          $publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
          	->fetchObject();
          
          // book initialization
          $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2)
          	->fetchObject();
          
          // deleting a single specific publisher book
          $publisher->removeFromBooks($book); 
          
          // saving
          $publisher->save();
          
        • removeAll Deleting all records can be done via a single call:
          // publisher initialization
          $publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
          	->fetchObject();
          	
          // deleting all publisher books
          $publisher->removeAllBooks();
          
          // saving
          $publisher->save();
          

          This operation requires knowledge of the source value: which Books are available at the Publisher presently. That's why, if BOOKS field is not initially accessed, it will be accessed automatically before deleting.

        As an alternative, you can use generic unnamed methods:

        $fieldName = 'BOOKS';
        
        $publisher->addTo($fieldName, $book);
        $publisher->removeFrom($fieldName, $book);
        $publisher->removeAll($fieldName);
        

        ArrayAccess

        Interface for accessing object as an array can ensure backward compatibility when switching from arrays to objects:

        $author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(17)->fetchObject();
        		
        echo $author['NAME'];
        // call is similar to the method $author->getName()
        
        $author['NAME'] = 'New name';
        // call is similar to the method $author->setName('New name')
        

        As to runtime fields, you may only read their values in this case, but not set them:

        $author = \Bitrix\Main\Test\Typography\AuthorTable::query()
        	->registerRuntimeField(
        		new \Bitrix\Main\Entity\ExpressionField('FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME'])
        	)
        	->addSelect('ID')
        	->addSelect('FULL_NAME')
        	->where('ID', 17)
        	->fetchObject();
        
        echo $author['FULL_NAME'];
        // call is similar to the method $author->get('FULL_NAME');
        
        $author['FULL_NAME'] = 'New name';
        // throws exception
        


        Collections

        Collections class

        Operation is based on the same logic as for EO_, and for Objects. Each entity has its own Collection class, inherited from Bitrix\Main\ORM\Objectify\Collection. The Book entity will have the EO_Book_Collection view by default. To set your own class, create a descendants for this class and designate it in the Table entity class:

        //File bitrix/modules/main/lib/test/typography/books.php
        
        namespace Bitrix\Main\Test\Typography;
        
        class Books extends EO_Book_Collection
        {
        }
        
        //File bitrix/modules/main/lib/test/typography/booktable.php
        
        namespace Bitrix\Main\Test\Typography;
        
        class BookTable extends Bitrix\Main\ORM\Data\DataManager
        {
        	public static function getCollectionClass()
        	{
        		return Books::class;
        	}
        	//...
        }

        Now the method fetchCollection will return the collection Bitrix\Main\Test\Typography\Books of class objects Bitrix\Main\Test\Typography\Book. Annotations allow for IDE to create hints, simplifying the developer's work.



        Access to Collection items

        • foreach

          Base collection class implements the \Iterator interface, allowing to get items:

          $books = \Bitrix\Main\Test\Typography\BookTable::getList()
          	->fetchCollection();
          
          foreach ($books as $book)
          {
          	// ...
          }
        • getAll, getByPrimary

          Collection items can also be fetched directly. The method getAll returns all the contained objects as an array:

          $books = \Bitrix\Main\Test\Typography\BookTable::getList()
          	->fetchCollection();
          
          $bookObjects = $books->getAll();
          
          echo $bookObjects[0]->getId();
          // prints ID value for the first object
          

          The method getByPrimary gets specific objects, contained in the collection:

          // 1. example with standard primary key
          $books = \Bitrix\Main\Test\Typography\BookTable::getList()
          		->fetchCollection();
          	
          $book = $books->getByPrimary(1);
          // book with ID=1
          
          // 2. example with composite primary key
          $booksToAuthor = \Bitrix\Main\Test\Typography\BookAuthorTable::getList()
          	->fetchCollection();
          
          $bookToAuthor = $booksToAuthor->getByPrimary(
          	['BOOK_ID' => 2, 'AUTHOR_ID' => 18]
          );
          // assigns relations for book object with ID=2 and author with ID=18
          
        • has, hasByPrimary

          You can check availability of specific object in the collection using the method has:

          $book1 = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
          $book2 = \Bitrix\Main\Test\Typography\Book::wakeUp(2);
          
          $books = \Bitrix\Main\Test\Typography\BookTable::query()
          	->addSelect('*')
          	->whereIn('ID', [2, 3, 4])
          	->fetchCollection();
          
          var_dump($books->has($book1));
          // prints false
          
          var_dump($books->has($book2));
          // prints true
          

          Similarly, the method hasByPrimary is convenient for checking by the primary key:

          $books = \Bitrix\Main\Test\Typography\BookTable::query()
          	->addSelect('*')
          	->whereIn('ID', [2, 3, 4])
          	->fetchCollection();
          
          var_dump($books->hasByPrimary(1));
          // prints false
          
          var_dump($books->hasByPrimary(2));
          // prints true
          
        • add, []

          Objects are added by the method add and interface ArrayAccess, allowing to use the structure []:

          $book1 = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
          
          $books = \Bitrix\Main\Test\Typography\BookTable::query()
          	->addSelect('*')
          	->whereIn('ID', [2, 3, 4])
          	->fetchCollection();
          
          $books->add($book1);
          // or
          $books[] = $book1;
        • remove, removeByPrimary

          You can delete object from collection either directly or by specifying the primary key:

          $book1 = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
          
          $books = \Bitrix\Main\Test\Typography\BookTable::getList()
          	->fetchCollection();
          
          $books->remove($book1);
          // book with ID=1 is deleted from collection
          
          $books->removeByPrimary(2);
          // book with ID=2 is deleted from collection
          


        Group actions

        Collections allow performing group actions for items contained inside such collections.

        • save (adding)

          The method save() performs primary saving in case of new objects, by generating a single group query:

          use \Bitrix\Main\Test\Typography\Books;
          use \Bitrix\Main\Test\Typography\Book;
          
          $books = new Books;
          
          $books[] = (new Book)->setTitle('Title 112');
          $books[] = (new Book)->setTitle('Title 113');
          $books[] = (new Book)->setTitle('Title 114');
          
          $books->save(true);
          
          // INSERT INTO ...  (`TITLE`, `ISBN`) VALUES
          	('Title 112', DEFAULT),
          	('Title 113', DEFAULT),
          	('Title 114', '114-000')

          The method receives the parameter $ignoreEvents = true, cancelling the ORM events when adding entries. In case of multiple inserting with autoincremental field (ID) you cannot get multiple values of this field which is possible when inserting a single entry using the function similar to mysqli_insert_id().

          In the rest of cases, when entity doesn't have autoincremental fields, events are left to the discretion of a developer. Events are executed by default.

        • save (editing)

          The method save() saves already existing, but modified objects using a single query UPDATE:

          use \Bitrix\Main\Test\Typography\PublisherTable;
          use \Bitrix\Main\Test\Typography\BookTable;
          
          $books = BookTable::getList()->fetchCollection();
          $publisher = PublisherTable::wakeUpObject(254);
          
          foreach ($books as $book)
          {
          	$book->setPublisher($publisher);
          }
          
          $books->save();
          
          // UPDATE ... SET `PUBLISHER_ID` = '254'
          	WHERE `ID` IN ('1', '2')

          Group update works only in case, when set of updated data is the same for all objects. When at least a single object has different data, all entries/records will be saved individually.

          As in the case with the adding action, the update action can disable event using the parameter $ignoreEvents in the method save(). By default, events are executed for each collection item individually.

        • fill

          Collection operation fill is a great alternative to the similar operation in Object, executed in a loop. In case of a loop, number of database queries will be equal to the number of objects:

          /** @var \Bitrix\Main\Test\Typography\Book[] $books */
          $books = [
          	\Bitrix\Main\Test\Typography\Book::wakeUp(1),
          	\Bitrix\Main\Test\Typography\Book::wakeUp(2)
          ];
          
          foreach ($books as $book)
          {
          	$book->fill();
          	// SELECT ... WHERE ID = ...
          	// do not do this!
          }

          In case of collection the query will be an only single one:

          $books = new \Bitrix\Main\Test\Typography\Books;
          // or $books = \Bitrix\Main\Test\Typography\BookTable::createCollection();
          
          $books[] = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
          $books[] = \Bitrix\Main\Test\Typography\Book::wakeUp(2);
          
          $books->fill();
          // SELECT ... WHERE ID IN(1,2)
          

          As in the case with objects, the parameter fill can pass the array with names of fields for completing or a mask type as follows:

          $books->fill(['TITLE', 'PUBLISHER_ID']);
          $books->fill(\Bitrix\Main\ORM\Fields\FieldTypeMask::FLAT);

          You can find more details on possible parameter values in this article for Objects fill.

        • get*List

          Not the most rare case: getting list of values for dedicated field from the query result. In standard case this can look as follows:

          $books = \Bitrix\Main\Test\Typography\BookTable::getList()
          	->fetchCollection();
          
          $titles = [];
          
          foreach ($books as $book)
          {
          	$titles[] = $book->getTitle();
          }

          Named group "getter" method allows to reduce such loop to a single code line:

          $books = \Bitrix\Main\Test\Typography\BookTable::getList()
          	->fetchCollection();
          
          $titles = $books->getTitleList();

          Such "getters" are available for all entity fields and are described in annotations for IDE.



        Restoring collection

        Restoring collection from completed data operates the same as described in the Objects:

        // restoring by primary key
        $books = \Bitrix\Main\Test\Typography\Books::wakeUp([1, 2]);
        
        // restoring by set of fields
        $books = \Bitrix\Main\Test\Typography\Books::wakeUp([
        	['ID' => 1, 'TITLE' => 'Title 1'],
        	['ID' => 2, 'TITLE' => 'Title 2']
        ]);

        With the difference that array with object data is passed to be placed in the collection.



        Relations

        The diagram below highlights the relations for test entities: Book, Author andPublisher with all combinations and direction for relations: 1:N,

        Starting point is the assumption that a book belongs to a single publisher, but can have several authors and it's sold in several stores.



        1:N

        In our presented test environment, a book can belong to and published strictly by a single publishing house. We'll get the relation "1 publisher - N books".

        Book and a Publisher

        In such cases, add the field PUBLISHER_ID to the table Books with value designating the Publisher.

        namespace Bitrix\Main\Test\Typography;
        
        use Bitrix\Main\ORM\Fields\IntegerField;
        
        class BookTable extends \Bitrix\Main\ORM\Data\DataManager
        {
        	public static function getMap()
        	{
        		return [
        			// ...
        			(new IntegerField('PUBLISHER_ID'))
        		];
        	}
        }
        

        However, a single field is not enough for ORM to recognize relations between Book and Publisher entities. For ORM to recognize such relations, use the multifields Bitrix\Main\ORM\Fields\Relations. In this case, the Reference type field is needed for indicating "multiple to one" relation:

        namespace Bitrix\Main\Test\Typography;
        
        use Bitrix\Main\ORM\Fields\IntegerField;
        use Bitrix\Main\ORM\Fields\Relations\Reference;
        use Bitrix\Main\ORM\Query\Join;
        
        class BookTable extends \Bitrix\Main\ORM\Data\DataManager
        {
        	public static function getMap()
        	{
        		return [
        			// ...
        			(new IntegerField('PUBLISHER_ID')),
        
        			(new Reference(
        					'PUBLISHER',
        					PublisherTable::class,
        					Join::on('this.PUBLISHER_ID', 'ref.ID')
        				))
        				->configureJoinType('inner')
        		];
        	}
        }
        

        Reference constructor parameters:

        /support/training/course/index.php?COURSE_ID=68&LESSON_ID=24526
        ParameterDescription
        $name Field name.
        $referenceEntity Class for bound entity.
        $referenceFilter "Join" conditions. Expects a filter object. In difference to regular filter use, add prefixes "this." and "ref." to column names here to designate association to current and related bounded entities accordingly.

        For readability, a created class Bitrix\Main\ORM\Query\Join is available, with a only method on that returns filter object Bitrix\Main\ORM\Query\Filter\ConditionTree, indicating the most popular condition whereColumn.

        Additionally, you can configure "join" type. By default, it's left join; example above sets inner join.

        Now you can use the described relations during data fetch:

        $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
        	'select' => ['*', 'PUBLISHER']
        ])->fetchObject();
        
        echo $book->getPublisher()->getTitle();
        // prints Publisher Title 253
        

        Access to Publisher entity object is implemented via "getter" getPublisher(). This way, you can connect more deeply nested relations chains, and use the "getter" chains to reach terminal objects.

        To set a relation, it's sufficient to pass Publisher entity object in a corresponding "settter":

        // publisher initialization
        $publisher = \Bitrix\Main\Test\Typography\PublisherTable::wakeUpObject(253);
        
        // book initialization
        $book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
        	->fetchObject();
        
        // setting object value
        $book->setPublisher($publisher);
        
        // saving
        $book->save();
        

        PUBLISHER_ID field value will be completed automatically from the passed object.

        The array results will not look so compact:

        $result = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
        	'select' => ['*', 'PUBLISHER']
        ]);
        
        print_r($result->fetch());
        /* prints
        Array (
        	[ID] => 1
        	[TITLE] => Title 1
        	[PUBLISHER_ID] => 253
        	[ISBN] => 978-3-16-148410-0
        	[IS_ARCHIVED] => Y
        	[MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_ID] => 253
        	[MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_TITLE] => Publisher Title 253
        )
        */
        

        Unique names, based at the class name and namespace, are assigned to related entity fields. You can use the "alias" mechanism to get more brief and practical names:

        $result = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
        	'select' => ['*', 'PUB_' => 'PUBLISHER']
        ]);
        
        print_r($result->fetch());
        /* prints
        Array (
        	[ID] => 1
        	[TITLE] => Title 1
        	[PUBLISHER_ID] => 253
        	[ISBN] => 978-3-16-148410-0
        	[IS_ARCHIVED] => Y
        	[PUB_ID] => 253
        	[PUB_TITLE] => Publisher Title 253
        )
        */
        

        Publisher and Books

        At the moment, access to