Table of Contents

What is Bitrix Framework?

Bitrix Framework - is a PHP-based platform for the development of web applications. By using this platform, Bitrix, Inc. has created two popular products – Bitrix Site Manager and Bitrix24.

The product Bitrix Site Manager is a software core for the comprehensive management of web projects of any complexity.

The product 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

Each platform uses its own terms. 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 for Oracle, 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; in Oracle the exp utility permits downloading in its own internal format.
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

  • Structure
  • Templates
  • Properties
  • Parameters
  • Execution Procedure
  • Structure

    A Page is a PHP file consisting of a prologue, page body (main working area), and epilogue:
    • 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:

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

    Thanks to the technology of deferred functions a part of visual elements displayed in the prologue 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 epilogue, 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 Prologue with values that in the previous example would be set in the page body (for example, page properties). In this case, the prologue 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 prologue
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/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 epilogue
    
    // connection of a visual part of the prologue
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_after.php");
    ?>
    Page contents
    <?
    // connection of the epilogue
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
    ?>

    The following occurs in the service part of the prologue:

    • 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 prologue has a particularity that it does not display any data (does not send the header to the browser).

    In the visual part of the prologue 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 epilogue may also be divided into a visual and a service part:

    <?
    // connection of the service part of the prologue
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
    
    // connection of the visual part of the prologue
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_after.php");
    
    ?>
    Page contents
    <?
    
    // connection of the visual part of the epilogue
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_before.php");
    
    // connection of the service part of the epilogue
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_after.php");
    ?>

    In the visual part of the epilogue 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 epilogue:

    • 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 prologue and epilogue. In this case, the connection of service parts of the prologue and epilogue will suffice.

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

    For the correct operating of the system, the service parts of the prologue and epilogue 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
    (
        [èìÿ ôàéëà] => 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 prologue.

    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 prologue;
    • Visual part of the prologue;
    • Page body;
    • Visual part of the epilogue;
    • Service part of the epilogue.

    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.

    Site Template

    Pages in the site public section are displayed based on site design templates.

    Design template is a visual appearance of the site where the layout of various elements on the site is determined along with the artistic style and page display mode. It includes program html code, graphic elements, style tables, and additional files for content display. It also may include templates of components, templates of ready-made pages, and snippets.

    Generally, the site template sets the “framing” of a page, and the visual components take care of the display of dynamic information.


    The number of design templates used on the site is unlimited. A condition is set for each template determining when this template should be applied to site pages:

    Condissions of assigning templates to site pages are set for each site individually on the "Edit site" page: Settings -> System settings -> Websites -> Websites:

    This form allows to define a set of conditions that will regulate the site design selection, in the site settings.

    Site template includes the following:

    • A set of files in the catalog /bitrix/templates/site template ID/, where site template ID is the ID field in the form of the editing of a site template. The structure of this catalog is provided below:
      • File header.php - the prologue of this template;
      • File footer.php - the epilogue of this template;
      • File styles.css contains CSS styles of the template;
      • /components/ contains a catalog with component templates that belong to a certain module;
      • /lang/ - language files belonging both to this template in general and to specific components;
      • /images/ - catalog with images of this site template;
      • /page_templates/ - catalog with page templates and their description stored in the file .content.php. When a user creates a new page, they can choose which template to use.
      • /include_areas/ - catalog with files that include the contents of the areas to be included. A random (i.e. no rules apply to its name) folder for files of the component areas to be included (main.include). Normally, a component is invoked from the site template. Files of the areas to be included shall be grouped in this file for convenience, because they may have a specific layout for the particular site template. There may be a name, logo, and contact information so that a user could edit only these data without interfering with site template.
      • And also a number of other auxiliary random files included in this template.

    Site template styles should be connected last, as it is the last possibility to redetermine, for example, standard component styles. Otherwise, a large-scale customization of component templates is necessary. If there is a need to connect own styles last, it can be done using the function of CMain::AddHeadString.

    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 system – 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 – process technique that permits you to launch arbitrary PHP functions (agents) with a preset frequency.

    As each page starts loading (immediately before the event of OnPageStart), the system automatically checks if there is an agent that must be launched and, if necessary, executes it.

    Note: Time accuracy of the agent launching directly depends on the steadiness and density of the site traffic. If you need to arrange for launching 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 does not 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 a return value of the function that will be used during the next execution of this function.

    Limits of Processing Techniques

    Using the processing technique, please take into account the following:

    • Agents have no USER variable. To be more precise, it can be created on a hosting server, but there is no guarantee that it will be a CUser class object.
    • Agents do not allow authorization using the Authorize method.
    • There is no pure data SITE_ID because the agent can also be executed on the page of the administrative section.
    • It is impossible to know which language it will be on multi-language sites.

    Caching

    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.

    Examples

    An example of caching with the CPHPCache class:

    $cntIBLOCK_List = 10;
    $cache = new CPHPCache();
    $cache_time = 3600;
    $cache_id = 'arIBlockListID'.$cntIBLOCK_List;
    $cache_path = '/arIBlockListID/';
    if ($cache_time > 0 && $cache->InitCache($cache_time, $cache_id, $cache_path))
    {
       $res = $cache->GetVars();
       if (is_array($res["arIBlockListID"]) && (count($res["arIBlockListID"]) > 0))
          $arIBlockListID = $res["arIBlockListID"];
    }
    if (!is_array($arIBlockListID))
    {
       $res = CIBlock::GetList(
          Array(), 
          Array(
             'TYPE' => 'catalog', 
             'SITE_ID' => SITE_ID, 
             'ACTIVE' => 'Y', 
             "CNT_ACTIVE" => "Y", 
             "!CODE" => 'test%'
          ), true
       );
       while($ar_res = $res->Fetch())
       {
          if($ar_res['ELEMENT_CNT'] > 0)
          $arIBlockListID[] = $ar_res['ID'];
       }
       //////////// end cache /////////
       if ($cache_time > 0)
       {
             $cache->StartDataCache($cache_time, $cache_id, $cache_path);
             $cache->EndDataCache(array("arIBlockListID"=>$arIBlockListID));
       }
    }

    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.

    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.

    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 14.0 (with core D7):

    • PHP version 5.3.
    • MySQL version 5.

    Main Differences from the Old Core

    • Database
      • The following databases are supported: MySQL, MS SQL, Oracle, and NoSQL.
      • o For MySQL only the driver MySQL Improved is supported. This driver supports object-oriented programming and has a number of other advantages.
      • 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.
      • o 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).
      • Repository of all the component connections will permit to find where necessary pages are located.

    Modules

    System modules are located in the bitrix/modules system folder. User modules may be located in the folder /local/modules. Such a division will permit third party developers to easily organize control of versions of their implementations maintaining a standard update system for product updates.

    Module API (classes, logic) in a new core is located in subfolder /lib of the module folder.

    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.

    Core Parameter Setup

    Settings in a new core are made in the file /bitrix/.settings.php. Let us remember that in the old core 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.

    Parameters may be edited using the Configuration class (Bitrix\Main\Config\Configuration).

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

    Description of Parameters

    Parameters that may be changed are described below:


    Cache Section

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

      'cache' => array (
        'value' => array (
          'type' => 'files',
        ),
        'readonly' => false,
      ),
    Parameter Value
    type The following can be set as values:
    • memcache
    • eaccelerator
    • apc
    • xcache
    • files
    • 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),

    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.

    File example /bitrix/.settings_extra.php



    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,
          '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 and its size will be limited.
    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'
          ),
    ),


    Section connections

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

      '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,
          ),
        ),
        'readonly' => true,
      ),
    );
    Parameter Value
    options Checkboxes of persistent connection and deferred connection with the database.

    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

    There exists a notion of exception hierarchy. Its purpose is to handle exceptions, look up which of them tripped, and take the appropriate actions depending on it. 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\Config\ConfigurationException   - configuration error
          Bitrix\Main\Security\SecurityException      - security error
          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\LoaderException      - exception in loader
       Bitrix\Main\CustomException      - basic type of all custom exceptions.

    Exception sent must have the most appropriate type possible.

    Events

    Event system is somewhat changed in core D7. Requirements for data belonging to a code triggering the event are leveled down. An example of an event sending:

    $event = new Bitrix\Main\Event("main", "OnPageStart");
    $event->send();

    If necessary, for a sending party there is a possibility to receive the result of an event handled by accepting parties.

    foreach ($event->getResults() as $eventResult)
    {
       if ($eventResult->getResultType() == \Bitrix\Main\EventResult::ERROR)
       {
          . . .   
       }
    }

    In order to reduce the code quantity Bitrix\Main\Event class successors may be created for specific event types. For example, Bitrix\Main\Entity\Event makes it more convenient to send events connected with the modification of entities.


    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

    Attention! The functionality is not supported so far. The commencement of support services will be additionally announced.

    Application is an object responsible for core initialization.

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

    Any specific class of application is a successor of the abstract class Bitrix\Main\Application. Two specific classes of the application are supported – Bitrix\Main\HttpApplication and Bitrix\Main\CliApplication that are the successors of the class Bitrix\Main\Application, but implement a slightly different execution logic.

    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"];

    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 guess for a user/developer, overlook 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. 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.

    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 database (Add).
    Purposes in the New Core:

    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.

    Concept and Description of an Entity

    When creating Internet-based projects using the Bitrix Framework platform, vast functional capabilities are available out of the box and they can be used through API that 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).

    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 Sampling.

    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';
    	}
    	
    	...
    }

    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.


    Operations with Entities

    For writing entities three methods of already described class are used: BookTable::add, BookTable::update, BookTable:delete.

    BookTable::add

    The add-entry method admits as an input parameter an array with values where entity field names are 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.

    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:

    $eventManager = Main\EventManager::getInstance();
    $eventManager->addEventHandler(
    	"main",
    	"\SomePartner\MyBooksCatalog\Book::OnBeforeUpdate",
    	
    );

    Formatting of Values

    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.

    Calculating Values

    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.


    Data Sampling

    Data sampling with various conditions for filtering, grouping, and sorting is the most common task.

    getList

    In order to make a new API look less frightening and more familiar for the developer, the name of the most popular method getList has been left unchanged. However, contrary to the getList of earlier days with its set of parameters and closed nontransparent behavior, now this method is common for all entities and follows the same rules. The entity developer will not be able to alter getList even if s/he wants to.

    The entity BookTable taken as an example is not an exception. Which parameters are acceptable for the method BookTable::getList?

    BookTable::getList(array(
    	'select'  => ... // field names to be obtained as a result
    	'filter'  => ... // filter description for WHERE and HAVING
            'group'   => ... // explicit indication of the field to be used in order to group the result
            'order'   => ... // sorting parameters
            'limit'   => ... // number of entries
            'offset'  => ... // limit shifting
            'runtime' => ... // dynamically defined fields
    
    ));

    getList always returns the object DB\Result, from which the data can be obtained:

    $rows = array();
    result = BookTable::getList(array(
    	...
    ));
    while ($row = $result->fetch())
    {
    	$rows[] = $row;
    }

    In order to obtain all entries at once the method fetchAll() can be used:

    result = BookTable::getList($parameters);
    $rows = $result->fetchAll();
    
    // or even shorter:
    $rows = BookTable::getList($parameters)->fetchAll();

    Now, let us consider all the parameters in more detail.

    select

    The select parameter is set up as an array with the names of entity fields:

    BookTable::getList(array(
    	'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
    ));
    
    // SELECT ISBN, TITLE, PUBLISH_DATE FROM my_book

    The following aliases can be used if you are not satisfied with the original field names in the result due to any reasons:

    BookTable::getList(array(
    	'select' => array('ISBN', 'TITLE', 'PUBLICATION' => 'PUBLISH_DATE')
    ));
    
    // SELECT ISBN, TITLE, PUBLISH_DATE AS PUBLICATION FROM my_book

    In this example, the field name PUBLISH_DATE is replaced with PUBLICATION, and it is this name that will be used in the resulting array.

    If all the fields must be selected, the symbol *can be used:

    BookTable::getList(array(
    	'select' => array('*')
    ));

    In this case, only the fields ScalarField will be selected, and the ExpressionField fields and relations with other entities will not be affected since they always have to be indicated explicitly.

    filter

    The parameter filter has inherited the infoblock filter format:

    // WHERE ID = 1
    BookTable::getList(array(
    	'filter' => array('=ID' => 1)
    ));
    
    // WHERE TITLE LIKE 'Patterns%'
    BookTable::getList(array(
    	'filter' => array('%=TITLE' => 'Patterns%')
    ));

    The filter can be a multilevel array where AND/OR are merged:

    // WHERE ID = 1 AND ISBN = '9780321127426'
    BookTable::getList(array(
    	'filter' => array(
    		'=ID' => 1,
    		'ISBN' => '9780321127426'
    	)
    ));
    
    // WHERE (ID=1 AND ISBN='9780321127426') OR (ID=2 AND ISBN='9781449314286')
    BookTable::getList(array(
    	'filter' => array(
    		'LOGIC' => 'OR',
    		array(
    			// 'LOGIC' => 'AND', // by default the elements are merged through AND
    			'=ID' => 1,
    			'ISBN' => '9780321127426'
    		),
    		array(
    			'=ID' => 2,
    			'ISBN' => '9781449314286'
    		)
    	)
    ));

    The complete list of comparison operators which can be used in a filter:

    • = - equal
    • % - substring
    • > - more
    • < - less
    • @ IN (EXPR) - the object DB\SqlExpression is submitted as a value
    • != - not equal
    • !% - not substring
    • >< - between, the array(MIN, MAX) is submitted as a value
    • >= - more or equal
    • <= - less or equal
    • =% - LIKE
    • %= - LIKE
    • >!< - not between, the array(MIN, MAX) is submitted as a value
    • !=% - NOT LIKE
    • !%= - NOT LIKE

    group

    The parameter group contains the list of fields for grouping:

    BookTable::getList(array(
    	'group' => array('PUBLISH_DATE')
    ));

    More often than not there is no need to indicate the grouping explicitly, the system will do it automatically. For more details, please refer to the section below regarding dynamically defined fields.

    order

    The parameter order permits to indicate the sorting order:

    BookTable::getList(array(
    	'order' => array('PUBLISH_DATE' => 'DESC', 'TITLE' => 'ASC')
    ));
    
    BookTable::getList(array(
    	'order' => array('ID') // direction by default - ASC
    ));

    offset/limit

    The parameters offset and limit will help to limit the number of selected records or implement a page by page selection:

    // 10 last entries
    BookTable::getList(array(
    	'order' => array('ID' => 'DESC')
    	'limit' => 10
    ));
    
    // 5th page with entries, 20 per page
    BookTable::getList(array(
    	'order' => array('ID')
    	'limit' => 20,
    	'offset' => 80
    ));

    runtime

    The calculated fields (ExpressionField) mentioned in the first part are often more necessary during selection for various calculations with grouping rather than in the description of an entity.

    The simplest example, counting the number of entries in an entity, can be performed as follows:

    BookTable::getList(array(
    	'select' => array('CNT'),
    	'runtime' => array(
    		new Entity\ExpressionField('CNT', 'COUNT(*)')
    	)
    ));
    // SELECT COUNT(*) AS CNT FROM my_book

    In this example, the calculated field not just simply transforms the value of a field but implements an arbitrary SQL expression with the COUNT function.

    After the registration of a field in the section runtime it can be referred to in the select section as well as in other sections:

    BookTable::getList(array(
    	'select' => array('PUBLISH_DATE'),
    	'filter' => array('>CNT' => 5)
    	'runtime' => array(
    		new Entity\ExpressionField('CNT', 'COUNT(*)')
    	)
    ));
    // choose the days on which more than 5 books were issued
    // SELECT PUBLISH_DATE, COUNT(*) AS CNT FROM my_book GROUP BY PUBLISH_DATE HAVING COUNT(*) > 5

    Note. This example shows the automatic grouping mentioned above. The system itself has recognized that grouping should be performed by the field PUBLISH_DATE.

    If a calculated field is needed only in the section select (as often is the case) the use of the ‘runtime’ section is not required. You can save time by putting the expression directly into select.

    BookTable::getList(array(
    	'select' => array(
    		new Entity\ExpressionField('MAX_AGE', 'MAX(%s)', array('AGE_DAYS'))
    	)
    ));
    // SELECT MAX(DATEDIFF(NOW(), PUBLISH_DATE)) AS MAX_AGE FROM my_book

    Please note that inside the new Expression field MAX_AGE the already existing other Expression field AGE_DAYS was used. Thus, the system permits using nested expressions which will be successively unfolded in the final SQL code.

    The runtime section permits registering Expression fields as well as the fields of any other types. The runtime mechanism works in such a way that a new field is added to the entity as if it were described in it in the first place in the method getMap. However, such field is located in the field of vision only within the limits of a single query. It will not be available in the next getList call and will have to be registered anew.

    Short calls

    There is a form of a short call getById and getByPrimary for rather popular sampling:

    BookTable::getById(1);
    BookTable::getByPrimary(array('ID' => 1));
    
    // such calls will be similar to the following getList call:
    BookTable::getList(array(
    	'filter' => array('=ID' => 1)
    ));

    There exists an even shorter version of sampling by ID: getRowById which returns an array with data at once instead of the object DB\Result:

    $row = BookTable::getRowById($id);
    
    // the same result can be obtained as follows:
    $result = BookTable::getById($id);
    $row = $result->fetch();
    
    // or
    $result = BookTable::getList(array(
    	'filter' => array('=ID' => 1)
    ));
    
    $row = $result->fetch();

    If sampling is not performed by the primary key but rather by any other parameters but still only one entry needs to be obtained, the method getRow can be used:

    $row = BookTable:getRow(array(
    	'filter' => array('%=TITLE' => 'Patterns%'),
    	'order' => array('ID')
    ));
    
    // a similar result can be obtained as follows:
    $result = BookTable::getList(array(
    	'filter' => array('%=TITLE' => 'Patterns%'),
    	'order' => array('ID')
    	'limit' => 1
    ));
    
    $row = $result->fetch();

    Query Object

    All the parameters of getList, getRow, and others are submitted at once. The query is executed and the result is returned within the same call. However, there is an alternative configuration option for a query and control over its execution. It is the Entity\Query object:

    // obtaining data through getList
    $result = BookTable::getList(array(
    	'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
    	'filter' => array('=ID' => 1)
    ));
    
    // similarly through Entity\Query
    $q = new Entity\Query(BookTable::getEntity());
    $q->setSelect(array('ISBN', 'TITLE', 'PUBLISH_DATE'));
    $q->setFilter(array('=ID' => 1));
    $result = $q->exec();

    Such an approach may come handy if flexibility is needed for query building. For example, if query parameters are not known in advance and are generated by the program, you can use one Query object instead of variety of arguments and store query parameters in it:

    $q = new Entity\Query(BookTable::getEntity());
    attachSelect($query);
    attachOthers($query);
    $result = $query->exec();
    
    function attachSelect(Entity\Query $query)
    {
    	$query->addSelect('ID');
    	
    	if (...)
    	{
    		$query->addSelect('ISBN');
    	}
    }
    
    function attachOthers(Entity\Query $query)
    {
    	if (...)
    	{
    		$query->setFilter(...);
    	}
    	
    	if (...)
    	{
    		$query->setOrder(...);
    	}
    }

    Also the object Entity\Query permits building a query without executing it. It can be useful in order to execute subqueries or just to obtain query text for its subsequent use:

    $q = new Entity\Query(BookTable::getEntity());
    $q->setSelect('ID');
    $q->setFilter('=PUBLISH_DATE' => new Type\Date);
    $sql = $q->getQuery();
    
    file_put_contents('/tmp/today_books.sql', $sql);
    
    // thus, the query "SELECT ID FROM my_book WHERE PUBLISH_DATE='2014-12-31'" will be saved in the file but will not be executed.

    The complete list of the methods Entity\Query for implementation of the options described above:

    select, group:

    • setSelect, setGroup – establishes an array with field names
    • addSelect, addGroup – adds a field name
    • getSelect, getGroup – returns an array with field names

    filter:

    • setFilter – sets up single- or multi-dimensional array with filter description
    • addFilter – adds one filter parameter with a value
    • getFilter – returns current filter description

    order:

    • setOrder – sets up an array with field names and sorting order
    • addOrder – adds one field with sorting order
    • getOrder – returns current sorting description

    limit/offset:

    • setLimit, setOffset – sets up a value
    • getLimit, getOffset – returns the current value

    runtime fields:

    • registerRuntimeField – registers a new temporary field for the original entity

    The Query object is the basic element for data sampling. It is also used inside the standard getList. That is why the efficiency of hacks of getList comes to nothing: a hack may work in case a relevant method is called, but it will not work if a similar query is submitted directly through a Query.


    Relations among Entities

    Create, update, delete, and select data makes quite sizeable functionality sufficient for the organization of work with unrelated data. However, web projects often imply relations among entities. That is why various ways of relating the entities are provided for:

  • One-to-one relations 1:1
  • One-to-many relations 1:N
  • Many-to-many M:N
  • 1:1 Relations

    The simplest type of relation is when an entity element refers to an element of another or the same entity. The examples described deal with a book catalog, but until now book entries contained no indication of their authors. Let us implement that. First, let us describe the entity The author of the book:

    <?php
    
    namespace SomePartner\MyBooksCatalog;
    
    use Bitrix\Main\Entity;
    
    class AuthorTable extends Entity\DataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book_author';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new Entity\IntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new Entity\StringField('NAME'),
    			new Entity\StringField('LAST_NAME')
    		);
    	}
    }

    This example is based on the assumption that a book has only one author which is a person with a first name and a last name, and an entry about such author is contained in the entity Author. At the same time, one author may have many books. Thus, we obtain the relation 1 author : N books.

    This relation can be described as follows:

    class BookTable extends Entity\DataManager
    {
    	...
    	public static function getMap()
    	{
    		return array(
    			...
    			new Entity\IntegerField('AUTHOR_ID'),
    			new Entity\ReferenceField(
    				'AUTHOR',
    				'SomePartner\MyBooksCatalog\Author',
    				array('=this.AUTHOR_ID' => 'ref.ID')
    			)
    		);
    	}
    	...
    }

    First, we should add a numeric field AUTHOR_ID where the author’s ID will be stored. Based on this field, the relation between entities will be configured through a new type of field – ReferenceField. This is a virtual field that has no actual reflection in the database:

    new Entity\ReferenceField(
    	'AUTHOR',
    	'SomePartner\MyBooksCatalog\Author',
    	array('=this.AUTHOR_ID' => 'ref.ID')
    	array('join_type' => 'LEFT')
    )
    // LEFT JOIN my_book_author ON my_book.AUTHOR_ID = my_book_author.ID
    ParametersDescription
    First a field name is set up
    Second name of a partner entity with which the relation is being established
    Third describes which fields are used to connect the entities, and is set up in a format similar to the filter for the section select in getList. Keys and values are field names with prefixes:
    • this. – field of the current entity,
    • ref. – field of the partner entity.
    Fourth, additional table connection type join_type can be specified – LEFT (by default), RIGHT, or INNER.

    Now the described relation can be used during data sampling:

    BookTable::getList(array(
    	'select' => array('TITLE', 'AUTHOR.NAME', 'AUTHOR.LAST_NAME')
    ));
    
    SELECT 
    	`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
    	`somepartner_mybookscatalog_author`.`NAME` AS `SOMEPARTNER_MYBOOKSCATALOG_AUTHOR_NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `SOMEPARTNER_MYBOOKSCATALOG_AUTHOR_LAST_NAME`
    FROM `my_book`
    LEFT JOIN `my_book_author` `somepartner_mybookscatalog_author` ON `somepartner_mybookscatalog_book`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`

    The switch to the Author entity is carried out by the entry AUTHOR: reference field name is specified, and the context switches to this entity after the dot. After that, the field name from this entity, including the Reference, can be specified, thus going even further and generating a new table connection:

    'select' => array('AUTHOR.CITY.COUNTRY.NAME')

    This could be a query to select a book author’s residence country if there was the structure Countries -> Cities -> Book authors living in the cities..

    In order to make sure that the field names of different entities do not overlap, the system generates unique aliases for the entities connected. Sometimes they are not very readable. In these cases, we can use the reassignment of aliases you already know:

    BookTable::getList(array(
    	'select' => array(
    		'TITLE',
    		'AUTHOR_NAME' => 'AUTHOR.NAME',
    		'AUTHOR_LAST_NAME' => 'AUTHOR.LAST_NAME'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
    	`somepartner_mybookscatalog_author`.`NAME` AS `AUTHOR_NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `AUTHOR_LAST_NAME`
    FROM ...

    Similarly to the source entity, the symbol * can be used to select all the scalar fields of an entity. Short aliases can also be used:

    BookTable::getList(array(
    	'select' => array(
    		'TITLE',
    		'AR_' => 'AUTHOR.*'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_author`.`ID` AS `AR_ID`,
    	`somepartner_mybookscatalog_author`.`NAME` AS `AR_NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `AR_LAST_NAME`
    FROM `my_book` `somepartner_mybookscatalog_book` 
    LEFT JOIN `my_book_author` `somepartner_mybookscatalog_author` ON `somepartner_mybookscatalog_book`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`

    As mentioned above, the conditions for the entity relation are described similarly to a filter. This means that tables can be connected by several fields, and SqlExpression can also be used:

    $author_type = 5;
    
    new Entity\ReferenceField(
    	'AUTHOR',
    	'SomePartner\MyBooksCatalog\Author',
    	array(
    		'=this.AUTHOR_ID' => 'ref.ID',
    		'=ref.TYPE' => new DB\SqlExpression('?i', $author_type)
    	)
    )
    // LEFT JOIN my_book_author ON my_book.AUTHOR_ID = my_book_author.ID AND my_book_author.TYPE = 5

    The field ReferenceField, like other fields, can be described during data selection in the ‘runtime’ section and be used for the connection of other entities with which the relations were not initially described.

    If a field of an adjacent entity is used frequently, ExpressionField can be used, and the remote field can be determined as local.

    new Entity\ExpressionField('AUTHOR_NAME', '%', 'AUTHOR.NAME')

    In this example, the difference is not evident, but if you have a longer chain of junctions instead of AUTHOR.NAME, the use of one short name may come in handy.


    Relation 1:N, or back Reference

    The ReferenceField concept means that this field must be located in the entity N of the relation 1:N. Thus, the Reference must indicate just one entry: in the example above, it is assumed that a book may have only 1 author, and thus the ReferenceField in the Book entity indicates one entry of the Author entity.

    It is easy to select a book author because the Book entity has an indication of the relation with the author entity. But how can we select all of the books of an author if the Author entity contains no explicit indication to the Books?

    The point is that the Reference described in the book entity is sufficient for two-way selection; we only have to use a special syntax:

    \SomePartner\MyBooksCatalog\AuthorTable::getList(array(
    	'select' => array(
    		'NAME',
    		'LAST_NAME',
    		'BOOK_TITLE' => '\SomePartner\MyBooksCatalog\BookTable:AUTHOR.TITLE'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_author`.`NAME` AS `NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `LAST_NAME`,
    	`somepartner_mybookscatalog_author_book_author`.`TITLE` AS `BOOK_TITLE`
    FROM `my_book_author` `somepartner_mybookscatalog_author` 
    LEFT JOIN `my_book` `somepartner_mybookscatalog_author_book_author` ON `somepartner_mybookscatalog_author_book_author`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`

    Instead of the Reference name, we have to indicate the Name of an entity which has a Reference to the current entity:Name of the reference to the current entity. Following such construction, the context switches to the Book entity, and the TITLE field and other fields can be selected in it.


    Relations M:N

    Any book can be characterized from the point of view of genre/category, whether business or fiction literature, history or training books, thrillers, comedies, dramas, books on marketing, development, sales, etc. For simplicity sake, let us call all of this as tags, and assign several tags to each book.

    Let us describe the entity of tags:

    <?php
    
    namespace SomePartner\MyBooksCatalog;
    
    use Bitrix\Main\Entity;
    
    class TagTable extends Entity\DataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book_tag';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new Entity\IntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new Entity\StringField('NAME')
    		);
    	}
    }

    In order to connect this entity with the Books using the principle N:M (one book may have several tags, one tag may be connected with several books), it is necessary to create in the database an interim entity with a table to store data about the connections of books with tags.

    < true
    			)),
    			new Entity\ReferenceField(
    				'BOOK',
    				'SomePartner\MyBooksCatalog\Book',
    				array('=this.BOOK_ID' => 'ref.ID')
    			),
    			new Entity\IntegerField('TAG_ID', array(
    				'primary' => true
    			)),
    			new Entity\ReferenceField(
    				'TAG',
    				'SomePartner\MyBooksCatalog\Tag',
    				array('=this.TAG_ID' => 'ref.ID')
    			)
    		);
    	}
    }

    In this case, the entity is just required to store the connection Book ID – tag ID, and relevant ReferenceFields will help to describe this connection in software.

    The interim entity itself may be of little interest. Standard tasks in this case involve obtaining a list of tags for a book or a list of books according to a tag. These tasks are solved using the methods of work with the Reference described above:

    // tags for the book with ID = 5
    \SomePartner\MyBooksCatalog\BookTable::getList(array(
    	'filter' => array('=ID' => 5),
    	'select' => array(
    		'ID',
    		'TITLE',
    		'TAG_NAME' => 'SomePartner\MyBooksCatalog\BookTag:BOOK.TAG.NAME'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_book`.`ID` AS `ID`,
    	`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
    	`somepartner_mybookscatalog_book_book_tag_book_tag`.`NAME` AS `TAG_NAME`
    FROM `my_book` `somepartner_mybookscatalog_book` 
    LEFT JOIN `my_book_to_tag` `somepartner_mybookscatalog_book_book_tag_book` ON `somepartner_mybookscatalog_book_book_tag_book`.`BOOK_ID` = `somepartner_mybookscatalog_book`.`ID`
    LEFT JOIN `my_book_tag` `somepartner_mybookscatalog_book_book_tag_book_tag` ON `somepartner_mybookscatalog_book_book_tag_book`.`TAG_ID` = `somepartner_mybookscatalog_book_book_tag_book_tag`.`ID`
    WHERE `somepartner_mybookscatalog_book`.`ID` = 5

    The entry SomePartner\MyBooksCatalog\BookTag:BOOK.TAG.NAME may seem complicated, but it is actually pretty simple when considered by parts:

    ParametersDescription
    BookTable::getList source entity – BookTable
    SomePartner\MyBooksCatalog\BookTag:BOOK switch to the entity BookTag through its reference BOOK, current entity – BookTag
    TAG switch following the reference TAG from BookTag, current entity – Tag
    NAME a field from the current entity Tag

    The call chain will be very similar in order to obtain books with a specific tag:

    // books for the tag with ID = 11
    \SomePartner\MyBooksCatalog\TagTable::getList(array(
    	'filter' => array('=ID' => 11),
    	'select' => array(
    		'ID',
    		'NAME',
    		'BOOK_TITLE' => 'SomePartner\MyBooksCatalog\BookTag:TAG.BOOK.TITLE'
    	)
    ));

    Thus, using two types of switch between entities, REFERENCE and Entity:REFERENCE, the necessary related information can be easily accessed.


    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.


    Some Classes

    Configuration

    Located in the namespace \Bitrix\Main\Config. Consists of 2 classes: \Bitrix\Main\Config\Configuration and \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 part: 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 – work with paths, static.
    • Directory – work with folders.
    • File – work with files.

    Apart from them, there are other classes, including abstract ones, to organize hierarchy.


    Other Classes

    The

    bitrix/main/lib
    folder contains a class library intended for the performance of various frequent actions located in Main and not spread across various modules. Among other things, files and API for work are located in the relevant spaces:

    • Bitrix\Main\Data - with cache, including controlled cache.
    • Bitrix\Main\Text - with text: classes for text conversion and others.
    • Bitrix\Main\Type - with data types: date, file, etc.
    • Bitrix\Main\Web - with web: work with URL, web queries, etc.

    PHPdoc

    The new kernel is documented in PHPdoc format. Any programs that read this format can be used to display documentation to the code. For example, free doxywizard.

    The necessary settings are introduced in the first tab of the software:

    The Expert mode can be used to customize the display according to your preferences.

    The documentation that is obtained can be represented in any convenient form:

    A detailed description of the methods is provided below:

    Highloadblock

    Since the Information Blocks module is deemed very “heavy” to create light references or store a large amount of data (when the module behavior is not very efficient) there appeared the need to create a substitute to infoblocks (Highloadblock module) that was much simpler and “lighter”. The new module is available from product version 14.0 and is written on the D7 new core. The data structure of this module makes it possible to use the module for loaded projects.

    Note: Highloadblock and traditional infoblocks are different things. That is why there is no possibility to convert traditional infoblocks to Highloadblock. It is similar to the conversion of a forum in wiki. Technically, a similar structure can be created, and then data may be transferred. But it makes sense only for a specific project under certain circumstances.

    Module Architecture

    Unlike the Information Blocks module the module Highloadblock provides for the storage of each entity in its table. Entity fields are user-defined properties of the core. Entities are built in the administrative interface, i.e. without additional programming.

    Since the module works on a new core, access to data will be given based on ORM.

    Data may be allocated not only in the tables of the same database, but also in different databases. Thus, project scale out becomes possible when certain objects are located on one server and others on another.

    NoSQL support is added to Highloadblock based on the MySQL database. Database connection is effected through the extension handlersocket for MySQL.

    Let us assume that a project has NoSQL support configured, all the necessary MySQL drivers are installed, and kernel configuration provides for the connection through these drivers. As a result, the Highloadblock module permits you to create entities and build the project to access the database very quickly through the driver using a primary key.

    In order to use an ORM entity for Highloadblock the following actions must be performed:

    • Initialize an entity in the system:
      //first select information about it from the database
      $hldata = Bitrix\Highloadblock\HighloadBlockTable::getById($ID)->fetch();
      
      //then initialize an entity class
      $hlentity = Bitrix\Highloadblock\HighloadBlockTable::compileEntity($hldata);
      
    • After that, queries may be created with this entity:
      $query = new Entity\Query($hlentity);
      
      Or use it through the interface of DataManager:
      $hlDataClass = $hldata['NAME'].'Table';
      $hlDataClass::getList();
      

    All further work with Highloadblock is subject to ORM laws, because the entity created in Highloadblock is an ORM entity.

    The module comprises two components – the list of entity entries (highloadblock.list) and a detailed view of an entity (highloadblock.view).

    Example of the component that adds an entry to an entity

    Let us see an example of the creation of a proper component that will add a new entry to an entity. In order to write a component we use constructions of a new core D7. The Highloadblock namespace module must be connected without fail:

    use Bitrix\Highloadblock as HL;
    

    First, let us obtain data about the entity using its identifier:

    $hlblock_id = $arParams['BLOCK_ID'];
    
    $hlblock = HL\HighloadBlockTable::getById($hlblock_id)->fetch();
    

    After that we initialize the entity class to be able to access the entity through API:

    HL\HighloadBlockTable::compileEntity($hlblock);
    

    The component to be created will receive values from a form, work with user-defined core properties, and call the add method from ORM:

    if($_SERVER["REQUEST_METHOD"] == "POST" && $_POST["add_resume"] <> '' && check_bitrix_sessid())
    {
    	$data = array();
    
    	$USER_FIELD_MANAGER->EditFormAddFields('HLBLOCK_'.$hlblock['ID'], $data);
    
    	if (!$GLOBALS["USER_FIELD_MANAGER"]->CheckFields('HLBLOCK_'.$hlblock['ID'], 0, $data))
    	{
    		if(is_object($APPLICATION) && $APPLICATION->GetException())
    		{
    			$e = $APPLICATION->GetException();
    			$errors[] = $e->GetString();
    			$APPLICATION->ResetException();
    		}
    		else
    		{
    			$errors[] = "Unknown error.";
    		}
    	}
    
    	if (empty($errors))
    	{
    		/** @param Bitrix\Main\Entity\AddResult $result */
    		$result = ResumeTable::add($data);    //ìåòîä ORM
    
    		if($result->isSuccess())
    		{
    			$ID = $result->getId();
    			LocalRedirect("resume.php?ID=".intval($ID));
    		}
    		else
    		{
    			$errors = array_merge($errors, $result->getErrorMessages());
    		}
    	}
    	
    	if(!empty($errors))
    	{
    		$arResult['ERROR'] = implode("<br>", $errors);
    		$arResult["bVarsFromForm"] = true; 
    	}
    }
    

    Please note the way the operation result is processed in our example:

    • If add result is successful, we obtain an identifier of a new entity entry and get redirected to the page resume.php;
    • If there are errors, we obtain them using the method getErrorMessages and show them in the form.
    Full Code of the Component

    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.

     


    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 button of the context panel Load template 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.



    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:

    • 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.

    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

    • In a site template directory is created the /lang/ folder:

      /bitrix/templates/<>/lang/
    • In the /lang/ folder are placed folders with the utilized languages identifiers: /en/, /de/, /fr/, and etc. For example:

      /bitrix/templates/<>/lang/fr/
    • In the folders with languages identifiers are stored corresponding message files. 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 is 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 the 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 not translated 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 at the bottom of the page will be displayed list of message files used on 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.

    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.

    Creating and Editing the Include Area

    Include areas may be created:

    • From the administrative section in Ste 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. /.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.

    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

  • 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).

    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

    Advertising 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.

    Advertising 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 Russian, English, and German languages is provided below:

    LanguageEncoding
    Russian (ru)windows-1251, koi8-r, iso-8859-5
    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 (for MySql and Oracle database) 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.

    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.

    Sorting

    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.

    Sorting Types

    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 percent symbol "%"
    $res = CIBlockElement::GetList(Array(), Array("%NAME"=>"%"));
    ?>
    • "><" - 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");


    Work with Infoblocks through 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.


    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.

    Sorting of infoblock elements without using the 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 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:

    Examples of work 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 ôàéë:

    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 Infoblocks

    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);
       }
    }
    ?>
    


    Components

    Components are the main tool for developers when it comes to working with the projects created with Bitrix Framework. Developers’ professionalism largely depends on their ability to use this tool.

    Component - is a logically completed code intended for the retrieval of information from infoblocks and other sources and its conversion to HTML code for displaying as fragments of web pages. It consists of a component proper (controller) and a template (form). The component manipulates data using API of one or more modules. The component template displays data on the page.

    Classical workflow diagram of a component:


    Carrier Rider Mapper

    Components implement the design pattern Carrier Rider Mapper to the full extent.

    • Carrier. Carrier of any information that can be accessed by several clients simultaneously.
    • Rider (Reader or Writer) - objects through which the Carrier provides acces to the information stored in it. The clients read and write information stored in the Carrier exclusively using only the objects of the Reader and Writer type. Thus, Reader and Writer are information access interfaces.
    • Mapper (Scanneror Formatter) – objects that cover Reader or Writer, accordingly. Mappers are responsible for the conversion of data formats to the formats convenient for the clients.

    Information flow from carrier to client (read): Carrier -> Reader -> Scanner -> Client.

    Information flow from client to carrier (write): Carrier <- Writer <- Formatter <- Client.

    Introducing mappers between Carrier-Rider and clients permits connecting one and the same Carrier-Rider with different types of clients by using appropriate (different) mappers.


    The Use of Components

    Components are used for the following purposes:

    • Creating fully functionining sections on a website, e.g. news section, photo gallery, goods catalog, etc. These sections are created using composite components;
    • Creating frequently used areas in the template or on website pages (e.g., authorization forms and subscription forms);
    • Representing dynamically updated information (e.g., news feed and an occasional photo);
    • Performing any other operations with data.

    When placing a component on a page the user sets parameters that are used to retrieve the page program module on this particular page. The set of parameters (including their types) is to be established in the component parameter file as a special hash array.

    Several components may be placed on a website page. One component may be in charge of displaying the text of the article, the second may display banners, the third may display news related to the subject of this article, etc. One and the same component may be used on different pages of the website and on any website within the same product installation.

    Components may be simple and composite.


    Main Specifics of Component Technique

    • Logic and visual form are separated in components. Logic is the component itself, and form is the component display template. Several forms may be created for the same logic, including forms which depend on the template of the current website. Visual form (display template) may be written using any template language that can be connected from PHP. For example, templates may be written using PHP, Smarty, XSL etc.
    • There is no need to change the component logic in order to change the specifics of its display. That is why it is quite easy to manage the external appearance of the information displayed by the component. Display template is by far simpler than the component as a whole.
    • Components are stored together in one folder. It ensures the integrity and transparency of a website structure. The folder is available for queries. This means the component and its templates can easily connect their additional resources.

    Note: The evolution of the development of Bitrix Framework using the components of regular standards has led to the components that are currently used in 2.0 standard. The regular standard (version 1.0 components) is not supported any longer. However, sometimes outdated components can be found in the projects operating on older versions of Bitrix Site Manager.

    Simple and composite components

    Components are subdivided into simple (single page) and composite (compound, multi-page). A simple component displays on one physical page available under a specific URL. A composite component replaces a set of simple components. For example, a news section can be implemented using several simple components located on a separate physical page each, or using one composite component located on one physical page.

    In terms of the structure and means of connection, simple and composite components are very similar. However, in terms of functioning, they are very different.

    Simple components

    Simple (single page) components create an area on a page surface. They come at hand when you need to render data from different modules on a single page (for example: blogs and information blocks) or data from different information blocks (news and commercial catalog). Evidently, simple components are not convenient to create a whole news section or a catalog because this requires the creation of a considerable number of pages and controlling their linkage.

    Composite components

    Composite (compound, multi-page) components create the site sections. For example, the commercial catalog component creates the whole catalog section including the catalogs page, the groups page and the product pages. Essentially, a composite component is a set of pages from the visitor's point of view, but represented by a single page physically. The foundation of composite components are simple components and the use of the single-page logic.

    The advantage of composite components is the automatic preparation of parameters of the underlying simple components: you do not have to care about the interrelationships of internal parameters.

    Composite components resolve the following problems:

    • Avoid the creation of numerous pages to add all required sub-components to them. There is no need to configure common parameters of each individual component.
    • Avoid the need to establish composite associations between sub-components. For example, you do not have to configure the link creation rules etc.
    • Components (even those driven by custom view templates) can be enriched with new pages. For example, if you add page (sub-component) showing 10 most rated forum visitors, this page becomes momentarily available in the public section.
    • The component view template can be changed at one stroke instead of configuring each of the underlying sub-components.

    A typical composite component workflow is as follows:

    1. the component takes user actions on input and defines the exact page to show to a visitor and attaches an appropriate template to the page;
    2. the page template attaches simple components and configures their parameters as required;
    3. the simple components perform the requested operations: request data from the system kernel, format and display data to the visitor; show various controls (links, forms, buttons etc.);
    4. the visitor sends a new request to the composite component using the controls.

    Example

    Let us consider the difference between composite and simple components using the creation of a news section as an example.

    A news section may be organized, for example, by placing a news list component on the page index.php and a detailed news component on the page news.php. In this case, the news list component must have entry parameters set in such a manner so that it could generate links to the page containing the detailed news (with news code). The detailed news component must have entry parameters set in such a manner so that it could generate a link to the page containing the news list.

    ×To set a link to the page with detailed news the path to this page must be specified along with the name of the parameter in which a news code will be submitted for display. The same parameter name must also be set in the entry parameters of the detailed news component to indicate where it shall retrieve the code of the news to be displayed. Even in this oversimplified case, the settings are not so simple. What if it is a set of tens of components of a forum?

    The use of a composite news component is a better option for website developers. For example, this component can be simply installed on the page index.php. The composite component itself will take care of link and parameter alignment. No further actions are required on the part of the website developer.

    Template pages of the composite component will contain a connection of the relevant regular components with the correct setting of their entry parameters. Regular components will perform their regular functions: they do no care who retrieves them or why. For regular components, only the correct setting of their entry paramenters is important.

    MVC pattern is implemented as follows:

    • News composite component (controller) receives an HTTP query (user’s actions);
    • News composite component (controller) checks if a news code is set through the HTTP query and connects from its template the page containing the news list or the page containing the detailed news (view);
    • The connected page, in its turn, connects the relevant regular component at the same time installing entry paramenters accordingly;
    • Regular component performs its work: requests data from the kernel (model), formats them, and displays to the visitor, and also displays control elements (links, forms, buttons, etc.);
    • The user sends a new HTTP query to the news composite component (controller) using control elements;
    • The procedure is repeated as necessary.

    Location of a Component in the System and Its Connection

    All components are stored in the /bitrix/components/ folder. The system components are in the /bitrix/components/bitrix/ folder. The content of this folder is updated by the update system and must not be modified by users.

    Note! Modifying the system component folder /bitrix/components/bitrix/ can have unpredictable effects.

    Users can store their own components in any subfolder of /bitrix/components/, or in the /bitrix/components/ root.

    Naming convention

    The name of a subfolder in /bitrix/components/ is the component namespace. For example, all system components belong to the bitrix namespace. When creating a custom component, you should create a namespace and put custom components there.

    For example, a system component news exists located in the bitrix namespace. If you need a custom news component implementing functions unavailable in the standard system component, you should create a namespace (subfolder) in /bitrix/components/ (for example, demo) and put the custom component news there. The two components news will be available in different namespaces: bitrix:news and demo:news; both of them will be shown in the component tree in the visual editor.

       

    Names of the components are as follows identifier1.identifier2…. For example, catalog, catalog.list, catalog.section.element, etc. The names should be built hierarchically starting from the general term and ending with specific purpose of the component. For example,the component showing the list of goods of this group may have the name catalog.section.elements. The full name of the component is the name of the component indicating the namespace. The full name looks like name_space:component_name. For example, bitrix:catalog.list. If the component is located outside the namespace, the namespace will not be specified. For example, catalog.section.

    Component Connection

    In the most general terms, the component connects as follows:

    <?$APPLICATION->IncludeComponent(
    componentName, // component name
    componentTemplate, // component template, empty line if default template is used
    arParams=array(), // parameters
    parentComponent=null,
    arFunctionParams=array()
    );?>

    The following preset variables are available inside the component (file component.php):

    $componentName – full name of a component (e.g.: bitrix:news.list).
    $componentTemplate – the template with which the component is retrieved.
    $arParams – entry parameters of the component (i.e. parameters with which the component is retrieved). Parameters are also available by their names.
    $componentPath – a path to the component from the website root (e.g.: /bitrix/components/bitrix/news.list).
    $parentComponentName – name of the parent component (empty if there is no parent).
    $parentComponentPath –path to the parent component from the website root (empty if there is no parent).
    $parentComponentTemplate – template of the parent component (empty if there is no parent).
    $arResult — result, read/change. Affects the member of the component class with the same name.
    $this — naturally a link to the current component retrieved (CBitrixComponent class object), all class methods can be used.
    In addition, the variables $APPLICATION, $USER, $DB are named as global in the component.

    Component Structure

    A component stores everything it needs for operation in its folder. That is why they can be easily moved from one project to another.

    Important: Component files cannot be used independently. A component is a single entity, and it is indivisible.

    The component folder may contain the following subfolders and files:

    • The help subfolder (outdated from version 11.0) containing component help files. This folder contains subfolders named by the language abbreviation. Each language folder must have a file index.php, which is the main help file for the corresponding language. For example, the path name of the english help file can be /help/en/index.php. The help files should describe the structure of an array in which the component returns data to the template. The subfolder help is optional.
    • subfolder install that contains installation and uninstallation scripts. The installation script name is install.php, and the uninstallation script is uninstall.php. The subfolder install is optional.
    • subfolder lang containing component messages. From version 11.0 and later it may also contain help folder files.
    • subfolder templates in which the component view templates are stored. The subfolder templates is optional if the component has no view templates.
    • The file component.php containing logics (code) of the component. This file is responsible for generating an array $arResult from the obtained parameters ($arParams) that will subsequently get to the component template. This file must always be located in the component folder.
    • The file .description.php containing the component name, description, and its position in the deduction tree (for WYSIWYG editor). This file must always be located in the component folder. Its absence will not affect the work of the component, but will make it impossible to place the component through a visual editor.
    • file .parameters.php, which contains the description of the component input parameters for the visual editor. This file must exist if the component uses input parameters.
    • The file .class.php to support OOP components.
    • any other folders and files containing the resources required by the component, for example: the folder images.

    General Structure of the Component

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?> 
    <? 
     //Check and initialize the incoming parameters of a component
     if(component retrieval is in a valid cache) 
     { 
     //Retrieval of the data from the cache
     } 
     else 
     { 
     //Data request and generation of $arResult array according to
     //the structure described in the component help file
     $this->IncludeComponentTemplate(); 
     //Cached retrieval
     } 
    ?>

    Localization Folders

    Components and their templates support the option of displaying a user’s messages in multiple languages. Therefore, if a component displays the contents of an infoblock, for example, these contents may be preceded by a string constant. E.g.: “Here you can see the contents of the infoblock.” If the user chooses to go to, for example, the English version of the website, this constant may be automatically displayed in English.

    In order to implement this function when displaying string constants, a special function shall be retrieved instead of the constant itself and the identifier of this constant shall be allocated to the special function.

    Example:

    In the file /template.php without localization:
    <?echo “Commercial catalog”?>
    In the file /template.php with localization:
    <?echo GetMessage(“CATALOG”)?>
    In this case we introduce the following in the file /lang/en/template.php:
    <?$MESS[“CATALOG”] = “Commercial catalog”;?>

    For each language version, a folder with the name of the language shall be created in the lang folder (e.g., de, en, etc.) to host the language message files. A language message file for a component file should have the same name as the file itself. These files contain arrays with constant identifiers as keys and the constants themselves translated into a relevant language as values.

    The files should be located according to the same hierarchy with respect to the /lang/anguage_code/ folder where the file is located with respect to a component folder. For example, a language file with English phrases for the /install/uninstall.php file should be located on the path /lang/en/install/uninstall.php. There may be no lang subfolder at all if the component has no language-dependent phrases.

    Localization folders are created separately for components and for each template.

    How to Replace Standard Texts in the Interface.

    Copy the component template to be customized (if it is not customized yet). And then either:

    • Using the module Localization: Settings > Localization.
    • Or manually edit language files /bitrix/templates/site_template_name/components/component_name/templates/template_name/lang/en/template­.php.

    Connection of a Component Language File (Translation File)

    Language files in a component and all its standard files (component.php, .description.php, .parameters.php) are connected automatically. In other component files language files can be connected using the following command:

    $this->IncludeComponentLang($relativePath = "", $lang = False)

    where:
    $relativePath - is a path to the file from the component folder,
    $lang - is the language. If False is submitted, the current language is used.

    Example: $this->IncludeComponentLang("myfile.php");


    Prompts to Components

    Bitrix Framework permits creating prompts to component parameters:

    There are two ways to add prompts. The first one you can see in the projects created on the versions earlier than 11.0, and the second one – in subsequent versions. In both cases a file named .tooltips.php must be created.

    Before version 11.0

    In this case, the file shall be created in the help folder located in the root of the component. In the file, the array $arTooltips shall be created where the component parameters are the keys, and GetMessage() calls are values. For example:

    $arTooltips = array(
       'IBLOCK_TYPE' => GetMessage('VWS_COMP_DVL_TIP_IBLOCK_TYPE'),
       'IBLOCK_ID' => GetMessage('VWS_COMP_DVL_TIP_IBLOCK_ID'),
       'SORT_BY1' => GetMessage('VWS_COMP_DVL_TIP_SORT_BY1'),
       'SORT_ORDER1' => GetMessage('VWS_COMP_DVL_TIP_SORT_ORDER1'),
    );

    Language files for the file .tooltips.php are located, accordingly, in the folder (from the root folder of the component) /lang/language_ID/help/.tooltips.php.

    From version 11.0 on

    This time, the file shall be created in the language folder named lang. In the file, the array $MESS shall be created, where the component parameters with _TIP suffix are the keys, and the prompts are values. For example:

    $MESS["IBLOCK_TYPE_TIP"] = "Select one of the existing information block types in the list";
    $MESS["IBLOCK_ID_TIP"] = "Select an information block of the selected type";
    $MESS["SORT_BY1_TIP"] = "Defines the sorting field";
    $MESS["SORT_ORDER1_TIP"] = "Defines the sorting order";
    

    In this version, there is no need to create a separate lang file with prompts. It is sufficient to save them in the language file .parameters.php (both for the component and for the component template). The prompt format remains the same.

    In both case

    The prompts for parameters of the component IBLOCK_TYPE, IBLOCK_ID, SORT_BY1 and SORT_ORDER1.

    A number of standard parameters (CACHE_TIME, AJAX_MODE, and others) have several prompts. For CACHE_TIME:

    • CACHE_TIME - caching time;
    • CACHE_TYPE - caching type.

    For pager:

    • DISPLAY_TOP_PAGER - show pager above the list;
    • DISPLAY_BOTTOM_PAGER - show pager below the list;
    • PAGER_TITLE - pager element title;
    • PAGER_SHOW_ALWAYS - always show pager;
    • PAGER_TEMPLATE - pager template name;
    • PAGER_DESC_NUMBERING - reverse addressing;
    • PAGER_DESC_NUMBERING_CACHE_TIME - caching time of reverse addressing.

    Attention! Do not create prompts in advance (creating them but leaving empty): component settings will not be displayed.

    Composite Component Structure

    Composite component serves to organize a website section as a whole (forum, catalog, etc.). It connects simple components to the display data. It actually operates as a controller (manager) of simple components. Composite component determines a page to be displayed to the visitor based on HTTP and connects the template of such a page.

    An example of a composite component:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?><?
    
    $arDefaultUrlTemplates404 = array(
       "list" => "index.php",
       "element" => "#ELEMENT_ID#.php"
    );
    
    $arDefaultVariableAliases404 = array();
    
    $arDefaultVariableAliases = array();
    
    $arComponentVariables = array("IBLOCK_ID", "ELEMENT_ID");
    
    
    $SEF_FOLDER = "";
    $arUrlTemplates = array();
    
    if ($arParams["SEF_MODE"] == "Y")
    {
       $arVariables = array();
    
        $arUrlTemplates = 
            CComponentEngine::MakeComponentUrlTemplates($arDefaultUrlTemplates404, 
                                                        $arParams["SEF_URL_TEMPLATES"]);
        $arVariableAliases = 
            CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases404, 
                                                           $arParams["VARIABLE_ALIASES"]);
    
       $componentPage = CComponentEngine::ParseComponentPath(
          $arParams["SEF_FOLDER"],
          $arUrlTemplates,
          $arVariables
       );
    
       if (StrLen($componentPage) <= 0)
          $componentPage = "list";
    
       CComponentEngine::InitComponentVariables($componentPage, 
                                                $arComponentVariables, 
                                                $arVariableAliases, 
                                                $arVariables);
    
       $SEF_FOLDER = $arParams["SEF_FOLDER"];
    }
    else
    {
       $arVariables = array();
    
        $arVariableAliases = 
           CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases, 
                                                          $arParams["VARIABLE_ALIASES"]);
        CComponentEngine::InitComponentVariables(false, 
                                                 $arComponentVariables, 
                                                 $arVariableAliases, $arVariables);
    
       $componentPage = "";
       if (IntVal($arVariables["ELEMENT_ID"]) > 0)
          $componentPage = "element";
       else
          $componentPage = "list";
    
    }
    
    $arResult = array(
       "FOLDER" => $SEF_FOLDER,
       "URL_TEMPLATES" => $arUrlTemplates,
       "VARIABLES" => $arVariables,
       "ALIASES" => $arVariableAliases
    );
    
    $this->IncludeComponentTemplate($componentPage);
    ?>

    The following arrays are defined in the beginning of the code:

    • $arDefaultUrlTemplates404 - is used to set up paths by default in order to work in a Search Engine Friendly URL (SEF) mode. Each element of the array is a path template and is to be set up as follows:
      "path template code" => "path template"
      Structure of the type "#word#" may be used in the path template. These structures are replaced with the values of the relevant variables when the actual path is being generated. For example, for a path template:
      "element" => "#ELEMENT_ID#.php"
      The actual path will look like 195.php or 7453.php. Path templates may have parameters, for example:
      "element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SECTION_ID=#SECTION_ID#"
      All path templates used by the component must be set up.
    • $arDefaultVariableAliases404 - is used to set up default aliases for variables in a SEF mode. As a rule, this array is empty (real names of variables are used). In case a variable must have a different name in an HTTP request (address), an alias can be set up for this variable and the variable value can be recovered from the alias during component operation. In case an alias must be set for one or more variables of any path templates, an element similar to that indicated below must appear in the array:
      "path template code" => array(
          "variable 1 name" => "variable 1 alias",
          "variable 2 name" => "variable 2 alias",
          * * *
          )
      For example, if a link to the page containing detailed information about an infoblock element (e.g., a product card) must be similar to the following:
      "/<infoblock mnemonic code>/.php?SID=<element group code>"
      then the path template can be set up as follows:
      "element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SID=#SECTION_ID#"
      and an alias for the variable SECTION_ID in the array $arDefaultVariableAliases404 can be set up as:
      "element" => array(
          "SECTION_ID" => "SID"
          )
      In this case, links (addresses) will be generated using the SID parameter and the variable SECTION_ID will be set up in the components.
    • $arDefaultVariableAliases - is used to set up default aliases for variables not in a CNC mode. As a rule, this array is empty, i.e. real names of variables are used. In case a variable must have a different name in an HTTP request (address), an alias can be set up for this variable and the variable value can be recovered from the alias during component operation. In case an alias must be set for any variable, an element similar to that indicated below must appear in the array:
      "variable name" => "variable alia"
      For example, if the variable name is defined in the component SECTION_ID, but the SID variable is to be used in the links, an alias for SECTION_ID can be set up as follows:
      "SECTION_ID" => "SID"
      In this case, links (addresses) will be generated using the SID parameter, and the components will contain the SECTION_ID variable. All of these arrays or their parts can be redefined using the input parameters of the component (upon component retrieval). For example, the following array can be set up in the input parameter SEF_URL_TEMPLATES in a SEF mode:
      "SEF_URL_TEMPLATES" => array(
          "element" => "#IBLOCK_CODE#/#ELEMENT_ID#.php?GID=#SECTION_ID#"
          )
      and the following parameter can be set up in the input parameter VARIABLE_ALIASES :
      "VARIABLE_ALIASES" => array(
          "element" => array(
          "SECTION_ID" => "GID",
          ),
      )
      Then, the paths in addresses (links) will have the type similar to /phone/3425.php?GID=28and the following variables will be recovered in the component out of these path: IBLOCK_CODE = phone, ELEMENT_ID = 3425 and SECTION_ID = 28.
    • $arComponentVariables - is used to set up a list of variables wherein the component can accept in an HTTP request and admit aliases. Each element of an array is the name of a variable.

    An input parameter with the preset name SEF_MODE can have two values: Y and N. If $arParams["SEF_MODE"] is equal to Y, the component works in a SEF mode; otherwise, it does not.

    An input parameter with the preset name SEF_FOLDER makes sense if the component works in a SEF mode. In this case, it contains the path along which the component works. The path may be virtual (i.e. it might not exist physically). For example, the component from the example can be located in the file /fld/n.php, at the same time, it works in a SEF mode, and the input parameter SEF_FOLDER is equal to /company/news/. In this case, the component will respond to queries at the addresses /company/news/index.php, /company/news/25.php etc.

    The following methods are used in order to determine the page to be shown by the composite component and also to recover component variables from the path and aliases:

    • CComponentEngine::MakeComponentUrlTemplates
      array
      CComponentEngine::MakeComponentUrlTemplates($arDefaultUrlTemplates404, 
                                                  $arParams["SEF_URL_TEMPLATES"]);
      The method combines into a single array the default array of path templates and path templates submitted in input parameters of a component. If a template of any path is defined in $arParams["SEF_URL_TEMPLATES"], it will redefine the default template for such path.
    • CComponentEngine::MakeComponentVariableAliases
      array
      CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases404, 
                                                     $arParams["VARIABLE_ALIASES"]);
      The method combines into a single array the default array of variable aliases and the variable aliases submitted in the input parameters of a component. If an alias of a variable is also defined both in the default array and in input parameters, the alias from the input parameters is returned.
    • CComponentEngine::ParseComponentPath
      string
      CComponentEngine::ParseComponentPath($arParams["SEF_FOLDER"],
                                           $arUrlTemplates, $arVariables);
      The method based on the parameter $arParams["SEF_FOLDER"] and path template array (returned by the method MakeComponentUrlTemplates) determines the path template to which the requested address corresponds. If the template was found, its code is returned. Otherwise, an empty line is returned. In addition, an array of component variables recovered from the path template without parameters is returned in the $arVariables variable. For example, if a path template array (resulted from the array $arDefaultUrlTemplates404 after the redefinition of all or a part of templates through input parameters of the component) looks like:
      $arUrlTemplates = array(
          "list" => "index.php",
          "element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SID=#SECTION_ID#"
      );
      If the input parameter SEF_FOLDER is equal to /company/news/, and the requested address is equal to /company/news/15/7653.php?SID=28, the method ParseComponentPath will return the line "element" (code of the relevant template), and the array $arVariables will look as follows:
      $arVariables = array(
          "IBLOCK_ID" => 15,
          "ELEMENT_ID" => 7653
      )
    • CComponentEngine::InitComponentVariables
      CComponentEngine::InitComponentVariables($componentPage, 
                                               $arComponentVariables, 
                                               $arVariableAliases, 
                                               $arVariables);
      where:
      • $componentPage - is the code of the template that returned the ParseComponentPath method and to which the requested address corresponds;
      • $arComponentVariables - is an array of variables that a component may admit in a HTTP request and that may have aliases;
      • $arVariableAliases - is an alias array (returned by the method MakeComponentVariableAliases).

      The method recovers variables from $_REQUEST taking into account their possible aliases and returns them in the variable $arVariables. For example, if for the code above the array $arVariableAliases contains an entry

      "element" => array(
          "SECTION_ID" => "SID",
      )
      the method InitComponentVariables will return an array in the $arVariables parameter
      $arVariables = array(
          "IBLOCK_ID" => 15,
          "ELEMENT_ID" => 7653,
          "SECTION_ID" => 28
      )
      Here, the method InitComponentVariables initialized the third element of the array. The first two were initialized by the method ParseComponentPath in the example above. In case the component works in a non-SEF mode, the first parameter submits the value False to the method InitComponentVariables.

    If the component works in a SEF mode based on the path template code and variables from HTTP request (and in case of non-SEF addresses only based on variables from HTTP request) the component determines which page from the template is to be connected and forwards its name to the method invocation:

    $this->IncludeComponentTemplate($componentPage);

    Simple components are connected and their input parameters are set up on pages of a composite component template based on the input parameters of the composite component and certain calculated values and constants. For example, the page "element" of the component template from the example (file of the type /templates/.default/list.php with respect of the component folder) may look as follows:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    <?$APPLICATION->IncludeComponent(
       "bitrix:news.detail",
       "",
       Array(
          "IBLOCK_ID" => $arParams["IBLOCK_ID"],
          "ELEMENT_ID" => $arResult["VARIABLES"]["ELEMENT_ID"],
          "SECTION_ID" => $arResult["VARIABLES"]["SECTION_ID"],
          "CACHE_TIME" => $arParams["CACHE_TIME"],
       ),
       $component
    );?>

    The last parameter $component in the component connection is an object representing the current component. It is forwarded to the component connection call. Thus, the component to be connected will “know” that it is connected from the composite component. Accordingly, it will be able to use the resources of the composite component, invoke its methods, etc.

    Component Description

    The file .description.php contains a description of the component. This description is used for work with the component (for example, in a visual editor) and also for work in the website editing mode. During the work of the component itself (when invoking the page where the component is located), the description is not used, and the file .description.php is not connected.

    The file .description.php must be located in the component folder. The language file connects automatically (it must be located in the folder /lang/<language>/.description.php of the component folder).

    Typically, the file .description.php has the following structure:

    <?
    $arComponentDescription = array(
       "NAME" => GetMessage("COMP_NAME"),
       "DESCRIPTION" => GetMessage("COMP_DESCR"),
       "ICON" => "/images/icon.gif",
       "PATH" => array(
          "ID" => "content",
          "CHILD" => array(
             "ID" => "catalog",
             "NAME" => "Catalog"
          )
       ),
       "AREA_BUTTONS" => array(
          array(
             'URL' => "javascript:alert('Button');",
             'SRC' => '/images/button.jpg',
             'TITLE' => "Button"
          ),
       ),
       "CACHE_PATH" => "Y",
       "COMPLEX" => "Y"
    );
    ?>

    As we can see, the file determines the array $arComponentDescription which describes the component. This array may have the following keys:

    • NAME - name of the component;
    • DESCRIPTION - description of the component;
    • ICON - path to the pictogram of the component from the component folder. The icon of the component is used in different parts of the system (e.g., in the visual editor);
    • PATH - component location in the virtual tree of the component in the visual editor. The value of this element must be an array with the following keys:
      • ID - code of the tree branch. Node ID must be unique within the entire component tree (including standard nodes). If the nodes have two equal ID, both of them will not open. E.g., for the proprietary component the ID node = “news” is selected, and such ID already exists for standard components.
      • NAME - name of the tree branch. It must be indicated. The NAME is taken from any component from the node. If no NAME is found or there is no language constant necessary, ID is used as a NAME.
      • CHILD - a child or subordinate branch. In the element with the CHILD key a subordinate branch of the tree with the same structure as the parent branch can be set up.
      The tree is limited to three tiers. As a rule, a two-tier tree is built, and the components are located on the second tier. The following service names of the first tier are reserved and cannot be used: content, service, communication, e-store and utility.

      If the PATH key is not established, the component will not be available in the visual editor.
    • AREA_BUTTONS - user buttons shown for the component in the website editor mode;
    • CACHE_PATH - if the value is equal to Y, the cache flush button of the component is shown in the website editor mode (the cache is supposed to be located at the standard path: /<website code>/<relative path to the component>). If equal to a non-empty line different from Y, the cache flush button of the component is shown in the website editor mode (the cache is located at the path equal to the value with the key CACHE_PATH - for non-standard paths);
    • COMPLEX - the element must have the value Y for a composite component and has no significance for simple components.

    Component Parameters

    The file .parameters.php contains the description of the input parameters of the component. The file data are needed exclusively to create the form to enter the properties of the component in Bitrix Framework environment (e.g., in a visual editor). This description is used for work with the component and also during work in the website editing mode. The description is not used and the said file is not connected during the operation of the component itself (when the page with the component is requested).

    The fil .parameters.php must be located in the component folder. The language file is connected automatically (it must be located in the folder /lang/<language>/.parameters.php, of the component folder).

    The file determines the array $arComponentParameters, which describes the input parameters of the component. If necessary, any additional data are selected. E.g., all active types must be selected in order to establish a drop-down list of information block types (input parameter IBLOCK_TYPE_ID).

    The structure of the typical file .parameters.php (based on the example of the components that operate with the module Information Block):

    <?
    CModule::IncludeModule("iblock");
    
    $dbIBlockType = CIBlockType::GetList(
       array("sort" => "asc"),
       array("ACTIVE" => "Y")
    );
    while ($arIBlockType = $dbIBlockType->Fetch())
    {
       if ($arIBlockTypeLang = CIBlockType::GetByIDLang($arIBlockType["ID"], LANGUAGE_ID))
          $arIblockType[$arIBlockType["ID"]] = "[".$arIBlockType["ID"]."] ".$arIBlockTypeLang["NAME"];
    }
    
    $arComponentParameters = array(
       "GROUPS" => array(
          "SETTINGS" => array(
             "NAME" => GetMessage("SETTINGS_PHR")
          ),
          "PARAMS" => array(
             "NAME" => GetMessage("PARAMS_PHR")
          ),
       ),
       "PARAMETERS" => array(
          "IBLOCK_TYPE_ID" => array(
             "PARENT" => "SETTINGS",
             "NAME" => GetMessage("INFOBLOCK_TYPE_PHR"),
             "TYPE" => "LIST",
             "ADDITIONAL_VALUES" => "Y",
             "VALUES" => $arIblockType,
             "REFRESH" => "Y"
          ),
          "BASKET_PAGE_TEMPLATE" => array(
             "PARENT" => "PARAMS",
             "NAME" => GetMessage("BASKET_LINK_PHR"),
             "TYPE" => "STRING",
             "MULTIPLE" => "N",
             "DEFAULT" => "/personal/basket.php",
             "COLS" => 25
          ),
          "SET_TITLE" => array(),
          "CACHE_TIME" => array(),
          "VARIABLE_ALIASES" => array(
             "IBLOCK_ID" => array(
                "NAME" => GetMessage("CATALOG_ID_VARIABLE_PHR"),
             ),
             "SECTION_ID" => array(
                "NAME" => GetMessage("SECTION_ID_VARIABLE_PHR"),
             ),
          ),
          "SEF_MODE" => array(
             "list" => array(
                "NAME" => GetMessage("CATALOG_LIST_PATH_TEMPLATE_PHR"),
                "DEFAULT" => "index.php",
                "VARIABLES" => array()
             ),
             "section1" => array(
                "NAME" => GetMessage("SECTION_LIST_PATH_TEMPLATE_PHR"),
                "DEFAULT" => "#IBLOCK_ID#",
                "VARIABLES" => array("IBLOCK_ID")
             ),
             "section2" => array(
                "NAME" => GetMessage("SUB_SECTION_LIST_PATH_TEMPLATE_PHR"),
                "DEFAULT" => "#IBLOCK_ID#/#SECTION_ID#",
                "VARIABLES" => array("IBLOCK_ID", "SECTION_ID")
             ),
          ),
       )
    );
    ?>

    Let us have a closer look at the keys of the array $arComponentParameters.

    GROUPS

    The value of this key is an array of component parameter groups. In the visual means of the Bitrix Framework (e.g., in a visual editor), the parameters are grouped. The groups in the Bitrix Framework environment are located according to the order set in the file. The array of the component parameter groups consists of the following types of elements:

    "group code" => array(
        "NAME" => "name of the group in the current language"
        "SORT" => "sorting",
        )

    List of standard groups:

    • ADDITIONAL_SETTINGS (sorting - 700). This group appears, for example, when indicating the parameter SET_TITLE.
    • CACHE_SETTINGS (sorting - 600). It appears when indicating the parameter CACHE_TIME.
    • SEF_MODE (sorting 500). The group for all parameters connected with the use of SEF.
    • URL_TEMPLATES (sorting 400). Link templates
    • VISUAL (sorting 300). This group is rarely used. These are parameters responsible for physical appearance.
    • DATA_SOURCE (sorting 200). Infoblock type and ID.
    • BASE (sorting 100). Main parameters.
    • AJAX_SETTINGS (sorting 550). Everything associated with AJAX.

    PARAMETERS

    The value of this key is an array of component parameters. In each parameter group, the parameters are located in the order set in the file. The array of regular parameters of the component consists of the following types of elements:

    "parameter code" => array(
        "PARENT" => "group code",  // if not - ADDITIONAL_SETTINGS shall be established
        "NAME" => "parameter name in the current language",
        "TYPE" => "type of the control element in which the parameter will be established",
        "REFRESH" => "refresh settings or not after selection (N/Y)",
        "MULTIPLE" => "single/multiple value (N/Y)",
        "VALUES" => "array of values for the list (TYPE = LIST)",
        "ADDITIONAL_VALUES" => "show the field for the values to be entered manually (Y/N)",
        "SIZE" => "number of lines for the list (if a list which is not a drop-down list is needed)",
        "DEFAULT" => "default value",
        "COLS" => "field width in characters",
    ),

    The following values are available for the TYPE control element type:

    • LIST - selection from the list of values. For the type LIST the key VALUES contains an array of values of the following type:
      VALUES => array(
         "ID or code to be saved in the component settings" => "language dependent description",
      ),
    • STRING - text entry field.
    • CHECKBOX - yes/no.
    • CUSTOM - permits creating customized control elements.

    The physical appearance of the list changes depending on whether the MULTIPLE and ADDITIONAL_VALUES keys are available/absent:

    • If there are no MULTIPLE and ADDITIONAL_VALUES or they are equal to “N”, a simple list is displayed, and no values are added to the list:

    • If ADDITIONAL_VALUES = "Y", MULTIPLE = "N", the value Other is added to the list with an additional field allowing manual entry of a value:

    • If ADDITIONAL_VALUES = "N", MULTIPLE = "Y", nothing will be added to the list, but it becomes possible to select several elements:

    • If ADDITIONAL_VALUES = "Y", MULTIPLE = "Y", the value Not selected is added to the list with a multiple additional field allowing manual entry of a value.

    The REFRESH parameter permits to refresh the entire form with parameters following value selection. This is done, for example, to select an infoblock of a specific type. I.e., we have two parameters: infoblock type and infoblock code. The starting point is as follows: the first parameter contains all types of infoblocks, the second parameter contains a list of all infoblocks of this website; after the selection of a certain type of infoblock, component parameters are refreshed, and we can only see the infoblocks of the selected type.

    Externally, for the LIST - type parameters, this key is represented as the OK button located close to the relevant parameter (see screenshots above).

    If a certain parameter must appear or remain hidden depending on another parameter, the procedure is as follows. For example, we need to display the list of properties of an infoblock. Assuming that the infoblock ID is contained in the IBLOCK_ID component parameter, let us name the parameter containing property list PROP_LIST. The parameter IBLOCK_ID must have the key REFRESH = 'Y' established. The code:

    if (0 < intval($arCurrentValues['IBLOCK_ID'])
    {
       $arPropList = array();
       $rsProps = CIBlockProperty::GetList(array(),array('IBLOCK_ID' => $arCurrentValues['IBLOCK_ID']));
       while ($arProp = $rsProps->Fetch())
       {
          $arPropList[$arProp['ID']] = $arProp['NAME'];
       }
       $arComponentParameters['PARAMETERS']['PROP_LIST'] => array(
          'NAME' => 'parameter title',
          'TYPE' => 'LIST'
          'VALUES' => $arPropList,
       );
    }

    There are special parameters that are standardized so that there is no need to describe them in detail. It is sufficient just to indicate that they exist. For example,

    "SET_TITLE" => array(),
    "CACHE_TIME" => array(),

    The first of the indicated parameters shows whether the component should set a page header and the second one shows all cache-related settings.


    Only composite components can work in a SEF mode or redefine the variables coming from an HTTP request. In this case, two special parameters must be indicated among the parameters:

    • VARIABLE_ALIASES - an array describing variables that the component may receive from an HTTP request. Each element of the array looks like the following:
      "internal name of the variable" => array(
        "NAME" => "name of the variable in the current language",
      )
    • SEF_MODE - an array describing path templates in a SEF mode. Each element of the array looks like the following:
      "path template code" => array(
          "NAME" => "name of the path template in the current language",
          "DEFAULT" => "default path template",
          "VARIABLES" => "an array of internal names of variables that may be used in the template"
      )

    From product version 12 on (new core D7) there is an option to add a control element into the component parameters that permits to indicate color (COLORPICKER).

    To do so, the following must be indicated in the component parameter file .parameters.php:

    $arParams["COLOR"] = Array(
        "PARENT" => "BASE",
        "NAME" => 'Colour selection',
        "TYPE" => "COLORPICKER",
        "DEFAULT" => 'FFFF00'
    );
    

    Component Templates

    Component template is a program code that converts data prepared by a component directly to HTML code.

  • Variables Available in the Component Template
  • Simple Component Template
  • Composite Component Template
  • How the System Searches for the Template
  • Template Connection
  • Template Example
  • Component templates are subdivided into system and user:

    • System templates come together with the component and are located in the subfolder templates of the component folder.
    • User templates of the component are the templates that have been changed according to the requirements of a specific website. They must be located in the folders of website templates (i.e. in /bitrix/templates/website_template/). When the component template is copied using the system it means that they will be located at the following path: /bitrix/templates/website_template/components/namespace/component_name/template_name.

    Component templates are defined by names. Default template is named .default. If no template name is indicated in the component parameter settings, the default template is retrieved. Other templates can have arbitrary names.

    Component templates can be folders or files. If the template does not require translation into other languages, own styles, and other resources, such template may be located in a file. Otherwise, the template should be located in a folder.

    Each component template is indivisible. If the system template of a component must be changed to fit for a specific website, such a component template must be copied to the website template folder in its integrity.

    For example, the component bitrix:catalog.site.selector has the system template dropdown located in the subfolder templates of the component folder. If this component template is to be changed to fit a specific website, the dropdown template folder should be copied into the folder /bitrix/templates/website_template/components/bitrix/catalog.site.selector/ and changed as one deems necessary.

    When including a component into a web page, the administrator shall establish which display template, exactly, will be used in this specific case.


    Variables Available in the Component Template

    The following variables are used in the template:

    • $templateFile – a path to the template from the website root, e.g.: /bitrix/components/bitrix/iblock.list/templates/.default/template.php).
    • $arResult – an array of component operation results.
    • $arParams – an array of input parameters of a component; it may be used to keep track of the set parameters of the template (e.g., displaying full pictures or links).
    • $arLangMessages – an array of language messages of the template (not required for php templates).
    • $templateFolder - a path to the folder with the template from DOCUMENT_ROOT (e.g., /bitrix/components/bitrix/iblock.list/templates/.default).
    • $parentTemplateFolder – a parent template folder. This variable is very convenient to connect additional images or scripts (resources). It should be introduced to form a full path from the template folder.
    • $component — a link to the currently invoked component (CBitrixComponent type).
    • $this - a link to the current template (object describing the template, CBitrixComponentTemplate type)
    • $templateName — the name of the component template (e.g., .default)
    • $componentPath - a path to the component from DOCUMENT_ROOT (e.g., /bitrix/components/bitrix/iblock.list)
    • $templateData — an array for entry; please note that here the data from template.php can be transferred to the file component_epilog.php, and these data will be sent to cache because the file component_epilog.php is executed with each hit.

    Also, within the PHP template, the variables $APPLICATION, $USER and $DB are declared global.


    Simple Component Template

    The file of a simple component template may contain the following subfolders and files:

    • Subfolder /lang where the files of language messages (translations) of the component template are located;
    • The file result_modifier.php that is to be connected directly before connecting the component template. This file receives as input the array of component operation results $arResult and the array of component invocation parameters $arParams. This way, it is possible, for example, to change the array of the component operation results to fit a specific template.
    • The file style.css determines the styles required for this template.
    • The file script.js determines and connects java scripts required for this template. This file may be absent.
    • The file .description.php containing the name and description of the template for the visual editor.
    • The file .parameters.php containing a description of additional input parameters of the template for the visual editor.
    • The file template.ext is the template proper. The extension ext depends on the type of the templating motor to be connected. The default extension is php. This file is mandatory.
    • Any other folders and files containing the resources required for the component template. For example, the image folder containing the images required by the template.

    Composite Component Template


    A composite component template contains the same folders as a simple component template, plus:

    • Templates of simple components that form part of the composite template. These templates are located in the folders of the type /namespace/simple_component_name/ in the composite component template folder.
    • Simple components that form part of the composite template are connected in the templates of pages of the composite component.

    How the System Searches for the Template

    The following search algorithm is used to find a suitable template for a component:

    • Everything begins with the folder /bitrix/templates/current_site_template/components/. In this folder, the file or folder with the template name is searched for along the path /component_namespace/component_name/. If there are none, then the availability of the file template_name.ext is searched for, where the ext extension includes all the available extensions of all templating motors established on the website that are checked one by one. If the template is found, the algorithm is ended.
    • If no template is found on step 1, the search is performed in the folder /bitrix/templates/.default/components/. The algorithm described in step 1 applies. If the template is found, the algorithm is ended.
    • If no template is found in step 2, the search among the system templates (i.e. those supplied with the component) is performed.

    Specifics of the search:

    • If no template name is set, the template with the .default name is searched for.
    • If the template is set as a folder name, then in case of a simple component in this folder the file template.extis searched for, and in case of a composite template - page_name.ext. The ext extension is assumed equal to php first, and then to extensions of other templating motors available on the website.

    For example, the component bitrix:catalog.list must be shown using the template table. Let us assume that on the website, in addition to the standard templating motor (php files), the Smarty motor (tpl files) is also available. The system will first check the folder /bitrix/templates/current_site_template/components/bitrix/catalog.list/ to find a file or a folder named table. If there are none, the system will check the aforementioned folder to find the files table.php and table.tpl. If nothing is found, the system will review the folders /bitrix/templates/.default/components/bitrix/catalog.list/ and /bitrix/components/bitrix/catalog.list/templates/.

    f the component folder is found, the file template.php is searched for first and if no such file is found, the file template.tpl. is searched for. If the template is set as table/template.php, the indicated file is retrieved at once.

    If a simple component is retrieved as a part of a composite component, the simple component template is first searched for within the composite component template, and after that (if not found) in own templates. To make sure this rule works, when calling for simple components within composite components do not forget to indicate the variable $component as the fourth parameter indicating the parent component. I.e. the code for invoking a simple component must look as follows:

    $APPLICATION->IncludeComponent("custom:catalog.element", "", array(...), $component);

    Note:

    The same folder (e.g., /bitrix/templates/current_site_template/components/) contains templates of two components, a composite and a simple:

    • catalog (composite component that contains the simple catalog.section)
    • catalog.section (simple)
    According to website operation conditions, a single template must be used for two catalog.section occurrences. In this case, this template must have a name other than .default; otherwise, it will not be caught.


    Template Connection

    Only <namespace>, component name, and template name (and parameters of the component itself) must be indicated in the connection code. When processing the code, the core first checks the availability of the component template in the current website template: /bitrix/templates/<site template name>/components/<namespace>/<component name>/<template name>/template.php.

    If <namespace> is bitrix it is the folder for templates of standard components. If <namespace> is selected by you <name> for your components is located in /bitrix/components/<name>, it is the folder for the templates of your components.

    If no template file is available, the default website template is checked: /bitrix/templates/.default/components/<namespace>/<component name>/<template name>/template.php.

    Only after that will the component template be connected from the component folder.

    The template is connected by the command:

    $this->IncludeComponentTemplate($templatePage = "");

    Where $templatePage is the name of the current page for a composite component, and an empty line for a simple component.

    Component template connection examples:

    1. Let us connect the template of the current composite component for the Section page:

      $this->IncludeComponentTemplate("section");

    2. Connect the template of the current simple component:

      $this->IncludeComponentTemplate();


    Template Example

    Menu Component Template
    Start test <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    Script start <?if (!empty($arResult)):?>
    <ul> tag opening — unnumbered list <ul class="…">
    Search cycle start <?foreach ($arResult as $arItem):?>
    Link display <?if($arItem["SELECTED"]):?>
    Active link <li><a href="<?=$arItem["LINK"]?>" class= "selected"><?=$arItem["TEXT"]?></a></li>
    Check for cycle continuation <?else:?>
    Inactive link <li><a href="<?=$arItem["LINK"]?>"> <?=$arItem["TEXT"]?></a></li>
    Link display ends <?endif?>
    Search cycle ends <?endforeach?>
    <ul> tag closes — unnumbered list </ul>
    Script ends <?endif?>

    Support of Component Classes

    From version 12.0.0 and later, component class support is available. It is implemented in the form of a file /component_name/class.php. Class.php is a reserved file name, and this file is automatically connected upon retrieval:

    $APPLICATION->IncludeComponent()

    In this case, the final call of the method initComponent is made, where class.php (if any) is connected from which the most recent inheritor from CBitrixComponent is taken.

    The actions like:

    class CDemoTest extends CBitrixComponent{}
    class CDemoTestDecorator1 extends CDemoTest {}
    class CDemoTestDecorator2 extends CDemoTest {}

    will not be successful. As a result, CDemoTestDecorator2 will be used.

    Please note that when changing the base class of the component, the behavior of all its descendants (other components) will have to be taken into account.

    Examples of Use

    Let us consider the simplest component taking the square of a parameter.

    File /bitrix/components/demo/sqr/component.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    $arParams["X"] = intval($arParams["X"]);
    if($this->startResultCache())
    {
        $arResult["Y"] = $arParams["X"] * $arParams["X"];
    }
    $this->includeComponentTemplate();
    ?>

    File /bitrix/components/demo/sqr/templates/.default/template.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    <div class="equation">
    <?echo $arParams["X"];?> squared is equal to <?echo $arResult["Y"];?>
    </div>

    In the real components, there may be three dozen lines and 5-6 similar operations instead of the multiplication operation. As a result, the file component.php becomes difficult to understand.

    Let us separate the component logic into a class.

    File /bitrix/components/demo/sqr/class.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    class CDemoSqr extends CBitrixComponent
    {
        //Parent method passes along all of the parameters transferred to $APPLICATION->IncludeComponent
        //and applies the function htmlspecialcharsex to them. In this case, such processing is excessive.
        //Redefine.
        public function onPrepareComponentParams($arParams)
        {
            $result = array(
                "CACHE_TYPE" => $arParams["CACHE_TYPE"],
                "CACHE_TIME" => isset($arParams["CACHE_TIME"]) ?$arParams["CACHE_TIME"]: 36000000,
                "X" => intval($arParams["X"]),
            );
            return $result;
        }
    
        public function sqr($x)
        {
            return $x * $x;
        }
    }?>

    File /bitrix/components/demo/sqr/component.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    if($this->startResultCache())
    {
        //$this - an instance of CDemoSqr
        $arResult["Y"] = $this->sqr($arParams["X"]);
    }
    $this->includeComponentTemplate();
    ?>

    Now the code in the fileå component.php has become controllable.

    Component Inheritance

    For example:

    The file /bitrix/components/demo/double_sqr/class.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    //Necessary for the correct search of the class CDemoSqr
    CBitrixComponent::includeComponentClass("demo:sqr");
    //Inheritor that expands functionality:
    class CDemoDoubleSqr extends CDemoSqr
    {
        public function sqr($x)
        {
            return parent::sqr($x)*2;
        }
    }?>

    The file /bitrix/components/demo/double_sqr/component.php is identical to the file /bitrix/components/demo/sqr/component.php, the contents can be copied.

    The file /bitrix/components/demo/double_sqr/templates/.default/template.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    <div class="equation">
    <?echo $arParams["X"];?> squared multiplied by 2 is equal to <?echo $arResult["Y"];?>
    </div>

    A Component without component.php

    A component may also be created without the file component.php

    To do so, it is sufficient to deactivate the method executeComponent. For example:

    class CDemoSqr extends CBitrixComponent
    {
    ...
        public function executeComponent()
        {
            if($this->startResultCache())
            {
                $this->arResult["Y"] = $this->sqr($this->arParams["X"]);
            }
    
            $this->includeComponentTemplate();
    
            return $this->arResult["Y"];
        }
    };

    Now, the files component.php can be deleted from both components.

    result_modifier.php

    The file result_modifier.php is a tool that is used to arbitrarily modify the data of the component operation. The developer shall create this file independently.

    If the file result_modifier.php is located in the template folder, it is retrieved prior to connecting the template.

    Sequence of Work of the Component with the File result_modifier.php:

    In this file, it is possible to request additional data and introduce them into the array of component operation results $arResult. It may prove to be useful if any additional data are to be displayed but component customization is undesirable since it means abandoning component support and updates.

    Note: The file result_modifier.php will run only if the template is NOT cached. And no dynamic properties of the: title, keywords and descriptioin type can be established through it.

    Language phrases of the component template and the following variables are available in the file:

    • $arParams - parameters, read, change. Does not affect the homonymous component member but changes made here affect $arParams in the file template.php.
    • $arResult — result, read/change. Affects the homonymous component class member.
    • $APPLICATION, $USER, $DB - there is no need to declare them global because they are available by default.
    • $this — link to the current template (object describing the template CBitrixComponentTemplate type)

    Note: Additional information about $arParams and $arResult.


    component_epilog.php

    The file component_epilog.phpis a tool to modify the component operation data when caching is activated. It should be created by the developer independently (available in version 9.0 and later).

    Sequence of Work of the Component with the Files result_modifier.php and component_epilog.php:

    This file is connected after the execution of the template. Similarly to style files, the parent component stores a list of epilogue files of all the templates of child components (possibly embedded) in its cache and (following cache hit) connects these files in the same order as they were executed without caching. Likewise, when invoking child components, the value $component must be submitted to the template.

    $arParams, $arResult are available in the file component_epilog.php, but these values are retrieved from the cache. A set of keys of the $arResult array to be cached is determined in the file component.php as follows:

                    $this->SetResultCacheKeys(array(
                            "ID",
                            "IBLOCK_TYPE_ID",
                            "LIST_PAGE_URL",
                            "NAV_CACHED_DATA",
                            "NAME",
                            "SECTION",
                            "ELEMENTS",
                    ));

    When developing your own components, always use this structure in order to limit the cache size to the data that are really necessary.

    Note: The file component_epilog.php may contain any code. You should only keep in mind that it will be executed with each hit whether cache is available or not. In versions of the main module earlier than 10.0 a copy of the arResult array was submitted to the component template. Due to this, the modification of this array in the file result_modifier.php did not give any results. The following code shall be located in the result_modifier.php to permit making changes in the results of cached data retrieval:
    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    global $APPLICATION;
    
    $cp = $this->__component; // component object
    
    if (is_object($cp))
    {
    	// we add two fields - MY_TITLE and IS_OBJECT to arResult of the component
    	$cp->arResult['MY_TITLE'] = 'My title';
    	$cp->arResult['IS_OBJECT'] = 'Y';
    	$cp->SetResultCacheKeys(array('MY_TITLE','IS_OBJECT'));
    	// we save them in copy of arResult used by the template
    	$arResult['MY_TITLE'] = $cp->arResult['MY_TITLE'];
    	$arResult['IS_OBJECT'] = $cp->arResult['IS_OBJECT'];
    
    	$APPLICATION->SetTitle($cp->arResult['MY_TITLE']); // will not work on each hit:  
    //will work only the first time, then everything will be retrieved from the cache and there will be no invocation of $APPLICATION->SetTitle()
    //That is why the title is changed in the component_epilog which is executed on each hit.
    
    }
    ?>

    After that, the changes made to arResult and performed in the template will become available in component_epilog.php.

    Example of the File component_epilog.php

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    global $APPLICATION;
    
    if (isset($arResult['MY_TITLE']))
    	$APPLICATION->SetTitle($arResult['MY_TITLE']);
    ?>

    Specifics of Use

    The file component_epilog.php is connected immediately after the connection and execution of the template. Thus, if the component provides for a template connection and then the component code provides for other operations, they will be executed after the execution of the file component_epilog.php.

    Accordingly, in case the changed data coincide in the component_epilog.php and in the component code after the template is connected, only the latest data will be displayed, i.e. those from the component code.

    Example of Such Situation

    Let us use the file component_epilog.php from the example above. The component code (file component.php) contains the following code:

    <?
    $this->IncludeComponentTemplate();
    if($arParams["SET_TITLE"])
    {
    	$APPLICATION->SetTitle($arResult["NAME"]);
    }
    ?>

    In this case, you will not obtain the desired result since the component data will be displayed instead of those from component_epilog.php.

    The file component_epilog.php contains the following:

    • $arParams - parameters, read/change. Does not affect the homonymous component member.
    • $arResult — result, read/change. Does not affect the homonymous component class member.
    • $componentPath — path to the component folder from DOCUMENT_ROOT (e.g., /bitrix/components/bitrix/iblock.list).
    • $component — link to $this.
    • $this — link to the currently invoked component (CBitrixComponent class object), all class methods may be used.
    • $epilogFile — path to the file component_epilog.php from DOCUMENT_ROOT
    • $templateName - component template name (e.g.: .default)
    • $templateFile — path to the template file from DOCUMENT_ROOT (e.g. /bitrix/components/bitrix/iblock.list/templates/.default/template.php)
    • $templateFolder — path to the template folder from DOCUMENT_ROOT (e.g. /bitrix/components/bitrix/iblock.list/templates/.default)
    • $templateData — please note that this is the way to transfer data from template.php to the file component_epilog.php, and these data will be sent to the cache and become available in component_epilog.php with each hit/
    • $APPLICATION, $USER, $DB — global variables.

    Operation of a Composite Component in a SEF Mode

    Composite components have an embedded SEF generation function. These components always come with the input parameter SEF_MODE which may admit the values Y and N. If the SEF_MODE parameter is equal to N, the component works with physical links and transfers all parameters through the standard parameters of an HTTP request. For example:

    /fld/cat.php?IBLOCK_ID=12&SECTION_ID=371

    If the parameter SEF_MODE is equal to Y, the component generates and processes links based on templates. For example, the component can “understand” and process the link:

    /catalog/section/371.php?IBLOCK_ID=12, even if the component itself is located in the file /fld/cat.php.

    If the parameter SEF_MODE is equal to Y, the component must also have the parameter SEF_FOLDER, containing a path to the folder necessary for the component. This path may or may not coincide with the physical path. For example, in the component bitrix:catalog, connected to the file /fld/cat.php, the parameters SEF_MODE = Y and SEF_FOLDER=/catalog/ can be set up. In this case, the component will respond to queries following the path /catalog/....

    A composite component that can work in an SEF mode must have a set of default path templates. For example, in a composite component bitrix:catalog the following array must be defined:

    $arDefaultUrlTemplatesSEF = array( 
          "list" => "index.php", 
          "section" => "section.php?IBLOCK_ID=#IBLOCK_ID#&SECTION_ID=#SECTION_ID#",
          "element" => "element.php?ELEMENT_ID=#ELEMENT_ID#" 
      );

    These path templates can be redefined using the input parameters of the composite component SEF_URL_TEMPLATES, containing a new array of all the path templates or a part of them.

    When saving a page with a component working in an SEF mode, the editor creates or updates the entry in the urlrewrite system. E.g., when saving the file /fld/cat.php containing the component bitrix:catalog switched in the SEF mode with the parameter SEF_FOLDER=/catalog/, the urlrewrite system creates or update the following entry:

    array( 
     "CONDITION" => "#^/catalog/#", 
     "ID" => "bitrix:catalog", 
     "PATH" => "/fld/cat.php" 
      ),
    • A value of the SEF_FOLDER parameter is written in  CONDITION between the symbols «#^» and «#»;
    • The name of the component is written in ID;
    • A physical path to the saved file is written in PATH.

    If an entry with such PATH and ID already exists, it is updated; if not, it is added.

    In run-time when a physically inexistent page is requested, the urlrewrite mechanism searches for the relevant entry by CONDITION and transfers control to the page PATH.

    The component on the PATH page figures out the requested page and recovers variables hidden in the path.

    Attention! It is mandatory that the set of path templates of this component regarding each path template is unique, not taking into account the parameters and variables. It must be checked when saving the page in a visual editor.

    I.e., the path template set

    "section" => "section/#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "element/#ELEMENT_ID#.php"

    is acceptable, but the path template set

    "section" => "#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "#ELEMENT_ID#.php"

    is not acceptable.

    List of links on the subject:

    Frequent Errors

    Cannot Find Component Call Code

    There are various reasons to this error:

    • Component call code is not placed between separate <? ?>.

      Solution: Check separation of the component code from another php code on the page.

      I.e. if you have php code on the page like this:

      <?
      php code
      
      component
      
      php code
      ?>

      it will return an error.

      The code shall be written as follows:

      <?
      php code
      ?>
      
      <?
      component
      ?>
      
      <?
      php code
      ?>
      
      
    • Errors in html code on the page.

      Solution: Check the validity of the html code.

    • Inconsistency of file encoding with the project in general.

      Solution: Check the encoding. A quite versatile solution is to write the following strings in the file .htaccess:

      For the website with the windows-1251 encoding:

      php_value mbstring.func_overload 0
      php_value mbstring.internal_encoding cp1251

      For the website with the UTF-8 encoding:

      php_value mbstring.func_overload 2
      php_value mbstring.internal_encoding utf-8
    • Inconsistency between file the owner and user under which the system edits the files.

      Solution: Check the user’s rights.

    Component Caching

    One of the types of caching in Bitrix Framework is component caching.

    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 Autocache mode mode (Components cache), the information retrieved by the components is updated according to the parameters of separate components.

    Auto cache control is located on the tab Component caching (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 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.

    Autocache mode

    Auto cache mode (Components cache) appeared in version 6 of the product replacing the standard component cache. The difference is that auto cache may be globally deactivated for the entire website using one button (Disable Caching) in the administrative part on the page Cache Settings Settings > System settings > Cache Settings.

    As a rule, it is deactivated at the development stage and is activated before the delivery of the project. Do not expect Bitrix Framework itself to choose the caching time and the right moment to do so for a cache flush. Only the developer can do that based on the actual needs of each project: the caching time that is suitable for the information update frequency must be indicated in the component settings.

    Structure and Storage Place

    Cache of the components is stored in the files of the folder /bitrix/cache.

    Component cache identifier is generated based on the following parameters:
    • ID of the current website which determines the path to the cache file (alternative storage means may be used, but it does not affect the work with components),
    • Component name,
    • Component template name,
    • Component parameters,
    • External conditions determined in the component (e.g., list of groups to which the current user is connected).

    In knowing all of these parameters, it is possible to flush the cache of any component. Otherwise, the cache will be reset upon the expiry of the cache time, by the click of a button on the control panel of the public part, or by performing complete cache flush from the administrative part.

    Note: When you reset the cache of a page using the “Refresh Cache” button, please keep in mind that the component may use the binding to groups for cache storage; in this case, non-registered users will continue seeing an outdated page.

    The cache of public components is not reset immediately after adding/changing an infoblock element in the administrative part. This is because, for example, the News information block "does not know", where the news is displayed in the public part and how many components display it. It causes no problems if the caching time is set correctly.

    Component Workflow Structure

    $arParams contains a set of component parameters, component.php works with request input parameters and database, generates a result to the array$arResult. Component template converts results into HTML text.

    Upon the first hit, the generated HTML goes to cache. With subsequent hits, no (or very few) queries are made to the database since the data are read from the cache.

    Attention! Please keep in mind the order of execution; in this case, the component template code and result_modifier.php are not executed.

    Frequent error: in the component template, deferred functions are retrieved: $APPLICATION->SetTitle(), $APPLICATION->AddChainItem() etc. In this case, they work only if caching is off.

    When developing the templates of own components, the developer shall follow a simple rule: the template’s task is to generate HTML text on return based on the input array $arResult.

    The generated cache ID of own components must be capable of unambiguously determining the resulting html. However, avoid sending too much data to the cache ID. That results in wasting disc space and reduces cache hits (thereby making cache less efficient).

    Cache Dependencies (Tagged Cache)

    Starting from the main module of version 9.1.0, cache tags are supported. Cache can be marked with tags and reset also by tags. The cache of the infoblock components can be reset when the information contained in them changes.

    Note. For large data amount that are often updated, tagged caching is not justified.

    Cache Tagging Base Code:

    01: $cache_id = md5(serialize($arParams));
    02: $cache_dir = "/tagged_getlist";
    03:
    04: $obCache = new CPHPCache;
    05: if($obCache->InitCache(36000, $cache_id, $cache_dir))
    06: {
    07:     $arElements = $obCache->GetVars();
    08: }
    09: elseif(CModule::IncludeModule("iblock") && $obCache->StartDataCache())
    10: {
    11:     $arElements = array();
    12:     $rsElements = CIBlockElement::GetList($arParams["order"], $arParams["filter"]);
    13:
    14:     global $CACHE_MANAGER;
    15:     $CACHE_MANAGER->StartTagCache($cache_dir);
    16:     while($arElement = $rsElements->Fetch())
    17:     {
    18:         $CACHE_MANAGER->RegisterTag("iblock_id_".$arElement["ID"]);
    19:         $arElements[] = $arElement;
    20:     }
    21:     $CACHE_MANAGER->RegisterTag("iblock_id_new");
    22:     $CACHE_MANAGER->EndTagCache();
    23:
    24:     $obCache->EndDataCache($arIBlocks);
    25: }
    26: else
    27: {
    28:     $arElements = array();
    29: }

    Line 01 initializes the unique cache file identifier. Then, the catalog is defined from /bitrix/cache where the cache files are stored with different values of $arParams. It is important that this path start from slash but not end with it. When using memcached or APC as the cache it will be of critical importance for the cache reset.

    Lines 04-05 initialize the cached object. If the caching time is not expired, line 07 will be executed and we will obtain the data from the cache file.

    The condition in line 09 will be true nearly always. Here, the module is connected and caching starts.

    Line 12 provides for a database query. It is important that all of the parameters on which a selection result depends “participate” in the cache identifier ($cache_id).

    In line 14, the access to the variable $CACHE_MANAGER. is set. This object will control tags.

    Line 15 – all subsequently allocated tags will be bound to the catalog $cache_dir. When the cache is reset in one of them, the contents of this catalog will be deleted. StartTagCache may be used recursively. For example:

    $CACHE_MANAGER->StartTagCache($cache_dir1);
        $CACHE_MANAGER->StartTagCache($cache_dir2);
            $CACHE_MANAGER->StartTagCache($cache_dir3);
            $CACHE_MANAGER->EndTagCache();
        $CACHE_MANAGER->EndTagCache();
    $CACHE_MANAGER->EndTagCache();
    

    It is important that the calls StartTagCache and EndTagCache are balanced. The object $CACHE_MANAGER creates and tracks the stack of cache catalogs. In this case, the tags allocated for the catalog $cache_dir3 áwill also be connected with $cache_dir2 and $cache_dir1. The tags allocated for cache_dir2 will be also connected with $cache_dir1.

    Line 18 provides for tagging the cache by using the method RegisterTag. The body length may not exceed 100 characters. Tag duplicates are deleted automatically when the RegisterTag method is used.

    Line 22 writes catalog tags to the database table. The count is one insert per tag.

    Cache reset:

    $CACHE_MANAGER->ClearByTag("iblock_id_7");

    Infoblock Components

    To launch the mechanism, a constant in the file dbconn.php must be defined.

    define("BX_COMP_MANAGED_CACHE", true);

    If the method StartResultCache is used, the entry will be retrieved by StartTagCache with a path to the component cache (depending on the page). If the method EndResultCache is used (which, in its turn, is retrieved from IncludeComponentTemplate) - by EndTagCache.

    In the infoblock module CIBlockElement::GetList and CIBlockSection::GetList return the object of the CIBlockResult class.

    The method Fetch/GetNext of this object will retrieve $CACHE_MANAGER->RegisterTag("iblock_id_".$res["IBLOCK_ID"]);. If the selection does not contain any elements, the value of the infoblock identifier will be retrieved from the filter.

    Cache reset is called from the methods Add/Update/Delete for elements, sections, and infoblocks. When the properties are changed, for example, using SetPropertyValueCode there will be no flushing. In this case, the following code can be used to clear cache:

    if(defined('BX_COMP_MANAGED_CACHE'))
       $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_'.$arParams['IBLOCK_ID']);

    The use of this tagged cache mechanism is not recommended in case of the frequent update of elements or sections. On the other hand, it must be convenient for content managers: all changes are immediately displayed on the website.

    Adding an Own Tag to Component Caches

    When performing instructions of this lesson, it is assumed that you have tagged caching activated.

    Solution 1

    Add the following code in the component body:

    if ($this->StartResultCache(......))
    {
       if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
       {
                $GLOBALS['CACHE_MANAGER']->RegisterTag('my_custom_tag');   
       }
    
       // do something
    
       $this->IncludeComponentTemplate();
    }
    else
    {
       $this->AbortResultCache();
    }
    

    Solution 2

    Add the following code to the component template (â result_modifier.php):

    <?
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
    {
       $cp =& $this->__component;
       if (strlen($cp->__cachePath))
       {      
          $GLOBALS['CACHE_MANAGER']->RegisterTag('my_custom_tag');
       }
    }
    ?>
    

    To reset all of the caches marked with your tag, execute the following code:

    if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
       $GLOBALS['CACHE_MANAGER']->ClearByTag('my_custom_tag');
    

    Note: The same cache can be marked with several tags. For example, if you mark the cache of the component bitrix:news.list with your tag, the cache will have two tags: the standard "iblock_id_XX" and your "my_custom_tag". Accordingly, the cache will be reset both in case of adding/changing an element in the infoblock XX (standard functionality) and in case of resetting cache manually through ClearByTag('my_custom_tag').

    Working with Components

    A component is the primary way to retrieve information in Bitrix Framework. Accordingly, it is the work with the component that opens the maximum possibilities to change data display conditions and change (add) system functionalities.

    he following task/solution ratio is recommended:

    • To solve tasks that are aimed at changing the data display form (design) modify the component template.
    • In order to change and expand cached data displayed by the component, please use the options of the file result_modifer.php.
    • In order to implement the logic executed upon each call of the component irrespective of cache, use the options of the file component_epilog.php.
    • In order to make additions and implicit changes (without interfering with the code) of component operation logic the technique of Events can be used.
    • To expand the component operation logic, copy the component to its namespace and change it.
    • To create new logic and new options, create a new component.

    Quite often a task has to be solved using a combination of methods. I.e., for example, editing the template and adding a code in result_modifier.php.

    In the chapters that follow, we will review the work with components in the indicated order of tasks and solutions.

    Attention! When performing any actions while working with components, please do not forget about caching. Heavy coding introduced in component_epilog.php not be subject to cache. However, there are situations when it is better to customize the component and it may result in better performance (especially when a heavy code is used on a home or a most frequently visited page).

    Template Customization

    Template editing is one of the means to obtain the result of the component work that is appropriate for each specific website. Editing the system template is not recommended because after the very first update, the system template will recover its initial state. Only user template can be edited and used.

    When starting to customize a template, please remember:

    All logic must be located in the component, and the template must contain only the display representation of the data obtained!

    As a rule, component template customization is aimed at:

    • Adjusting the component data display form according to the website design;
    • Arranging for component data display in a way unavailable as a standard option.

    User templates of a component are the templates that are changed to meet the requirements of a specific project. They must be located in the portal template folders (i.e. in /bitrix/templates/site_template/). When copying component template using system means they will be located at: /bitrix/templates/site_template/components/namespace/component_name/template_name.

    A template can be copied as follows:

    • Within the file system – by coping the folder /bitrix/components/bitrix/_required_component_/templates/ to the folder /bitrix/templates/website_template/components/namespace/component_name/_template_name.
    • Using system interface means using the command Copy component template (with Edit Mode activated):

    When copying the template, its application to a component can be set and a template editing form can be opened at once:

    It is possible not to apply the copied template to a component at once, but rather to do it later on by selecting the template in the component settings.

    Important! If during component template editing you add new identifiers of language messages, please remember that the identifiers must be unique throughout the entire product.

    Template editing admits adding action logic, but such a modification should be made in the files result_modifier.php and component_epilog.php (which must be located in the template folder) for a more complex change of work result.

    The chapter provides for some examples of template customization.

    Modification of a Simple Component Template within a Composite Component

    A composite component ensures the interaction of simple components with the general subject. Simple components contain the code of immediate work with data. For example, it is inconvenient to set up the components of a social network one by one.

    However, if you only seek to change the external appearance of some elements, it can be easily done without refusing other standard templates.

    For example, you need to change the form of the news list, and the entire news section is built using a composite component. The list of news is built using the simple component List of news (news.list) which, similarly to the component templates for building a detailed news page, feedback, search, etc., forms part of the composite component template News (news). When copying the News template to the site template by using the option Copy component template of the context menu of the component, all of the files of the component template are copied to the website template. This means that when the News component template is updated through the Site Update you, by using a customized template, will lose the template update of all other templates included in the composite component (do not worry, standard templates and components are getting updated as usual). In order to customize only the component template List of news (news.list) and to make sure the composite component News connects it but uses the other standard (updated) templates of the templates included into the composite component, do the following:

    • Create the folder news (or, depending on the composite component, a part of the template you need to customize) manually through file structure of the website in the website template folder (.default or current)
    • Copy the template of the simple component so that its path resulted as follows:
      /bitrix/templates/site_template/components/bitrix/news/.default/bitrix/news.list/.default/
    • Edit the template obtained

    The same approach can be used to edit the templates of simple components connected from the templates of composite components (i.e. when a template of a simple component does not form part of a composite component, and when a simple component is connected in the script of a composite component using the method CMain::IncludeComponent()).

    For example, you have to change the form of creating/editing a group. In a social template Bitrix24 this form is built using the component Create new group in modal dialog box (socialnetwork.group_create.ex) to be connected from the composite component Social Network (socialnetwork_group).

    If no modification of other parts of the composite component is needed, it will suffice to copy the component template socialnetwork.group_create.ex to the website template (/bitrix/templates/site_template/components/bitrix/socialnetwork_group/.default/bitrix/socialnetwork.group_create.ex/.default/) and modify it there.

    In this case, all remaining code will remain standard, i.e. it will be updated and supported by Bitrix, Inc.

    Template Modification or Creation of result_modifier?

    Quite often there are several solutions available to solve the same task. It is up to the developer to choose the final option based on the task at hand. Let us consider the examples of solving a task for which two solutions are available.

    How to embed a video when posting news on a website? It may seem difficult at first. However, it is actually quite easy. The idea consists in connecting the component Media Player (bitrix:player) for a file attached to the news. The component News details (bitrix:news.detail) will be used to display the news.

    Whatever solution you choose, you will have to create a property of the File type in the news infoblock.

    Solution Involving Template Editing

    • Copy the component template news.detail to the website template. You will not have to change the component itself.
    • Create a new page using visual editor and place the component Media Player (bitrix:player) on it. Indicate basic settings (do not enter the path to the video file at this stage). Copy the following code from the obtained:
      <?$APPLICATION->IncludeComponent(
         "bitrix:player",
         "",
         Array(
            "PLAYER_TYPE" => "auto", 
            "USE_PLAYLIST" => "N", 
            "PATH" => "",
            "WIDTH" => "400", 
            "HEIGHT" => "300", 
            "FULLSCREEN" => "Y", 
            "SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins", 
            "SKIN" => "bitrix.swf", 
            "CONTROLBAR" => "bottom", 
            "WMODE" => "transparent", 
            "HIDE_MENU" => "N", 
            "SHOW_CONTROLS" => "Y", 
            "SHOW_STOP" => "N", 
            "SHOW_DIGITS" => "Y", 
            "CONTROLS_BGCOLOR" => "FFFFFF", 
            "CONTROLS_COLOR" => "000000", 
            "CONTROLS_OVER_COLOR" => "000000", 
            "SCREEN_COLOR" => "000000", 
            "AUTOSTART" => "N", 
            "REPEAT" => "N", 
            "VOLUME" => "90", 
            "DISPLAY_CLICK" => "play", 
            "MUTE" => "N", 
            "HIGH_QUALITY" => "Y", 
            "ADVANCED_MODE_SETTINGS" => "N", 
            "BUFFER_LENGTH" => "10", 
            "DOWNLOAD_LINK_TARGET" => "_self" 
         )
      );?>
    • In the component template, set up the media player connection instead of the movie property. Find the strings for property display:
      30         <?foreach($arResult["DISPLAY_PROPERTIES"] as $pid=>$arProperty):?>
       31
       32                 <?=$arProperty["NAME"]?>: 
       33                 <?if(is_array($arProperty["DISPLAY_VALUE"])):?>
       34                         <?=implode(" / ", $arProperty["DISPLAY_VALUE"]);?>
       35                 <?else:?>
       36                         <?=$arProperty["DISPLAY_VALUE"];?>
       37                 <?endif?>
       38                 <br />
       39         <?endforeach;?>
    • Insert checking and replacement. The result:
      <?foreach($arResult["DISPLAY_PROPERTIES"] as $pid=>$arProperty):?>
      <?if ($arProperty["CODE"]=='movie' && $arProperty["DISPLAY_VALUE"]) {?>
      
      <?$APPLICATION->IncludeComponent(
         "bitrix:player",
         "",
         Array(
            "PLAYER_TYPE" => "auto", 
            "USE_PLAYLIST" => "N", 
            "PATH" => CFile::GetPath($arProperty["VALUE"]),
            "WIDTH" => "400", 
            "HEIGHT" => "300", 
            "FULLSCREEN" => "Y", 
            "SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins", 
            "SKIN" => "bitrix.swf", 
            "CONTROLBAR" => "bottom", 
            "WMODE" => "transparent", 
            "HIDE_MENU" => "N", 
            "SHOW_CONTROLS" => "Y", 
            "SHOW_STOP" => "N", 
            "SHOW_DIGITS" => "Y", 
            "CONTROLS_BGCOLOR" => "FFFFFF", 
            "CONTROLS_COLOR" => "000000", 
            "CONTROLS_OVER_COLOR" => "000000", 
            "SCREEN_COLOR" => "000000", 
            "AUTOSTART" => "N", 
            "REPEAT" => "N", 
            "VOLUME" => "90", 
            "DISPLAY_CLICK" => "play", 
            "MUTE" => "N", 
            "HIGH_QUALITY" => "Y", 
            "ADVANCED_MODE_SETTINGS" => "N", 
            "BUFFER_LENGTH" => "10", 
            "DOWNLOAD_LINK_TARGET" => "_self" 
         ),
         $component   
      );?> 
      <? } else {?>
            <?=$arProperty["NAME"]?>: 
            <?if(is_array($arProperty["DISPLAY_VALUE"])):?>
               <?=implode(" / ", $arProperty["DISPLAY_VALUE"]);?>
            <?else:?>
               <?=$arProperty["DISPLAY_VALUE"];?>
            <?endif?>
      <?}?>
            <br />
         <?endforeach;?>
    Note: Please pay attention to the following:
    • The system call CFile::GetPath is used to obtain the path to the file from the ID.
    • When connecting components, the fourth parameter $component is indicated so that its parameters cannot be changed from the public part

    Solution Using result_modifier.php

    If you want to continue using the updated component the task should be solved using result_modifier.php.

    • Create the file result_modifier.php with the code:
      <?
      // transfer property value using the link:
      $arProperty = &$arResult['DISPLAY_PROPERTIES'][$arParams['PROPERTY_VIDEO']];
      
      if ($arProperty['DISPLAY_VALUE']) // verify whether the property is set
      {
         global $APPLICATION;
         ob_start(); // activate buffering to catch component retrieval
         $APPLICATION->IncludeComponent(
            "bitrix:player",
            "",
            Array(
               "PLAYER_TYPE" => "auto", 
               "USE_PLAYLIST" => "N", 
               "PATH" => CFile::GetPath($arProperty["VALUE"]),
               "WIDTH" => "400", 
               "HEIGHT" => "300", 
               "FULLSCREEN" => "Y", 
               "SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins", 
               "SKIN" => "bitrix.swf", 
               "CONTROLBAR" => "bottom", 
               "WMODE" => "transparent", 
               "HIDE_MENU" => "N", 
               "SHOW_CONTROLS" => "Y", 
               "SHOW_STOP" => "N", 
               "SHOW_DIGITS" => "Y", 
               "CONTROLS_BGCOLOR" => "FFFFFF", 
               "CONTROLS_COLOR" => "000000", 
               "CONTROLS_OVER_COLOR" => "000000", 
               "SCREEN_COLOR" => "000000", 
               "AUTOSTART" => "N", 
               "REPEAT" => "N", 
               "VOLUME" => "90", 
               "DISPLAY_CLICK" => "play", 
               "MUTE" => "N", 
               "HIGH_QUALITY" => "Y", 
               "ADVANCED_MODE_SETTINGS" => "N", 
               "BUFFER_LENGTH" => "10", 
               "DOWNLOAD_LINK_TARGET" => "_self" 
            )
         ); 
         $arProperty['DISPLAY_VALUE'] = ob_get_contents(); // substitute $arResult
         ob_clean(); // clean our buffer so that the player do not appear twice
         ob_end_clean(); // close the buffer
      }
      ?>

      The symbol code of the property can be made the parameter of the component to avoid the strict connection to a specific infoblock. To do so, the file .parameters.php of the News details component located in the copied component template must be adjusted.

    • Change the code of the file .parameters.php:
      <?
      if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
      $arProps = array(); 
      $rs=CIBlockProperty::GetList(array(),array("IBLOCK_ID"=>$arCurrentValues['IBLOCK_ID'],"ACTIVE"=>"Y"));
      while($f = $rs->Fetch())
         $arProps[$f['CODE']] = $f['NAME'];
      
      $arTemplateParameters = array(
         "DISPLAY_DATE" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_DATE"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "DISPLAY_NAME" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_NAME"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "DISPLAY_PICTURE" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_PICTURE"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "DISPLAY_PREVIEW_TEXT" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_TEXT"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "PROPERTY_VIDEO" => Array(
            "NAME" => "Property where the video is stored",
            "TYPE" => "LIST",
            "VALUES" => $arProps
         ),
      );
      ?>

    As a result, a new parameter will appear in the component settings.

    Do not forget to indicate a property where the video is stored in the component connection parameters. Otherwise, it will not be displayed.

    Component Customization

    Standard component customization - means copying a standard component to the own namespace and changing its operation logic to change/add a functionality.

    Most tasks in Bitrix Framework are implemented through components, and in the component template you use the $arResult arrays that constitute the work result of the component (data) and $arParams that are input parameters.

    The following steps must be taken in order to customize a standard component:

    • Create a new component namespace in the folder /bitrix/components/; for example, create the catalog /bitrix/components/my_components/.
    • A folder with component you wish to change must be copied to the created folder (copy is to be made from the folder /bitrix/components/bitrix/).
    • Change the component according to the tasks at hand.
      • Change component description to your description in the files .description.php and /lang/en/.description.php;
      • Correct the files .parameters.php and component.php by modifying (adding necessary) functionality using API product;
    • Edit the component template according to the tasks at hand.
    • Clear the cache of the visual editor. As a result, the visual editor will display the customized component.

      Note: Cache of the visual editor is updated in the tab Components:

    Important! While customizing a component, please remember that all of the keys in $MESS containing the name, description, and parameters of a component and also the identifiers of component branches in the component tree of the visual editor must be unique throughout the entire product.

    It is preferable to refrain from component customization unless it is really necessary. In this case:

    • No updates are lost;
    • It is easier to solve problems through helpdesk (helpdesk does not solve the problems occurring in operation of a customized code unless an error in API is clearly identified);
    • AJAX is implemented in composite components as a standard.

    Simple Example of a Component Customization

    If there are too many elements, the component news.list may significantly slow down the generation of a page. The component must be optimized. You may consider deleting the link to the detailed news text in the preview description as one of the optimization options (a link with the name of the news will remain).

    • Copy the component to the namespace (/bitrix/components/my_components/).
    • Delete the following lines in the code of the component copied (/bitrix/components/my_components/news.list/component.php):
      "DETAIL_TEXT",
      "DETAIL_TEXT_TYPE",
      and
      if($bGetProperty)
      	$arSelect[]="PROPERTY_*";
    • Save the changes made.
    • Apply your own component instead of the standard on.

    As a result, there will be more database queries but the page will be formed faster.

    Modifying a Simple Component as a Part of a Composite One

    When working with a composite component, one or more simple components may be modified, leaving the remaining simple components unchanged.

    For example, if you want to increase the length of a group description to be displayed from 50 to 100 characters in the component socialnetwork.user_groups, which forms part of the composite component socialnetwork_group and displays a list of groups, then perform the following.

    • Copy the template of the composite component.

      Now, the template of the composite component is located in the website template. If we go there, we will see lots of files in the folder /bitrix/templates/<website template>/components/bitrix/socialnetwork_group/.default.

      Each file is invoked on a specific page of the social network and connects the simple components that are required.

      Now, the file that connects this component must be found and changed. In this case, the file is index.php. The rest of the files in the template of the composite component located in the website template can be deleted. The composite component will connect these files from the core. This means that they will be updated.

    • Now in the remaining file, we replace
      $APPLICATION->IncludeComponent(
                  "bitrix:socialnetwork.user_groups",
      with
      $APPLICATION->IncludeComponent(
                  "custom:socialnetwork.user_groups",
    • Copy the folder /bitrix/components/bitrix/socialnetwork.user_groups to /bitrix/components/custom/socialnetwork.user_groups.
    • In the file /bitrix/components/custom/socialnetwork.user_groups/component.php replace
      "GROUP_DESCRIPTION" => SubStr($arGroups["GROUP_DESCRIPTION"], 0, 50)."...",
      with
      "GROUP_DESCRIPTION" => SubStr($arGroups["GROUP_DESCRIPTION"], 0, 100)."...",

    Now, all of the functional capacity of the social network remains standard, except for the component socialnetwork.user_groups.

    Creating Components

    As a matter of fact, today own component may have to be written only when an absolutely new functionality for the website is needed. Since the set of standard components is quite extensive, in most cases just expanding the functionality of the already available components will suffice and there is no need to write new components.

    However, sooner or later a developer must learn to create their own components.

    Standard Sequence of Operations

    • In a web project, the possible types of own components are identified and described during the drawing up of the terms of reference and design.
    • The namespace of own components is determined, for example, by using the project name. The system components of Bitrix Framework are located in the namespace bitrix, which is where the project components can be located, for example in the namespace citybank.

      Attention! The names of the components to be created must not be the same as the names of standard components.

    • It is determined that a standard component may serve as a basis for creating an own component. The code of standard components contains lots of examples of typical and correct use of API and programming techniques, which is why they are considered a good starting point.
    • An interface is designed for each component 2.0. It should be decided as to which component parameters must be available to the website administrator for editing. For example, for the component displaying a weather forecast, the property Web Service Address and Web Service Connection Timeout may be put in the administrator’s settings, etc.
    • It is decided as to which section of the component tree in the visual editor this component should be located.
    • Component programming. Special attention shall be paid to set up the autocache of the component and its operating profile properly. It must not execute database queries in caching mode, must execute a minimum amount of database queries in case of cache aging, store only necessary data in cache, use a minimum possible volume of RAM (it must not sort arrays of tens or hundreds of megabytes, etc.).

    Component Creation Procedure

    Make the required php code into a separate file in order to use it later as an invoked file. However, the component must also be connected to the system using a description file identifiable by the core of Bitrix Framework. As a result, a user can see the icon with the component name in the visual editor and can set up the component properties.

    Remember that a component consists of a php code with terminated functionality made into a separate file, a file of component registration in the system and its parameter descriptions, and also localization files.

    • Component registration
      • Detachment of a required php code into a separate file.
      • Create the description file .description.php
      • Locate files in a folder in the own namespace.
    • Setting up parameters in the component code
    • Localization
      • Prepare files with text constants for the component and registration file: /lang/en/<component_name>/<component_name>.php and /lang/en/<component_name>/.description.php
      • make changes in the code of both files of the component in order to use these constants (the localization file is connected using the function IncludeTemplateLangFile).

    Attention! All keys in $MESS containing the name, description, and parameters of the component and also the identifiers of component branches in the component tree of the visual editor must be unique for the entire product.

    Additional Methods

    Additional Methods Available in Components and Templates

    Additional methods from the class CComponentEngine can be used in components and templates.

    string CComponentEngine::MakePathFromTemplate($pageTemplate, $arParams);

    where::
    $pageTemplate - a template of the type /catalog/#IBLOCK_ID#/section/#SECTION_ID#.php or catalog.php?BID=#IBLOCK_ID#&SID=#SECTION_ID#,
    $arParams - an associative array of reparametrization where the parameter name is the key and the parameter value is the value. It returns a path based on the path template $pageTemplate and reparametrization.

    Example:

    $url = CComponentEngine::MakePathFromTemplate
    ("/catalog/#IBLOCK_ID#/section/#SECTION_ID#.php", 
            array( 
                 "IBLOCK_ID" => 21, 
                 "SECTION_ID" => 452  
                 ) 
    );

    Organizing an Explicit Connection among the Components on the One Page of a Composite Component

    Explicit connection among the components can be organized through return values and incoming parameters of these components.

    If data from the component comp1 must be transferred to the component comp2, in the end of the component code comp1 must be written: return data;

    The component comp1 must be connected as follows:

    $result = $APPLICATION->IncludeComponent(comp1, ...);

    Now the data are located in the variable $result , and they can be transferred as incoming parameters into another component comp2.

    Redefinition of Incoming Variables

    Each component has a set of variables in which it receives codes or other attributes of requested data from the outside. For example, the component bitrix:catalog.section has variables IBLOCK_ID and SECTION_ID in which it receives and processes codes of the catalog and the product group, accordingly.

    All components that form part of a composite component must have a single set of variables. For example, the composite component bitrix:catalog and all simple components (bitrix:catalog.list, bitrix:catalog.section etc.), under its control work with the variables IBLOCK_ID, SECTION_ID, ELEMENT_ID, and others.

    If the developer wants to redefine the component variables when placing a composite component on a page, the developer must set up the parameter VARIABLE_ALIASES among the incoming parameters of the component.

    When connecting a component in the SEF mode, this parameter must look as follows:

    "VARIABLE_ALIASES" => array( 
          "list" => array(),
          "section" => array(
                            "IBLOCK_ID" => "BID",
                            "SECTION_ID" => "ID"
                            ),
                            "element" => array(
                            "SECTION_ID" => "SID",
                            "ELEMENT_ID" => "ID"
                            ),
    )

    Here, array codes are consistent with the codes in the path template array. For each path, their own redefinitions of variables can be set up.

    When connecting a component not in the SEF mode, this parameter must be:

    "VARIABLE_ALIASES" => array(
                               "IBLOCK_ID" => "BID",
                               "SECTION_ID" => "GID",
                               "ELEMENT_ID" => "ID",
    )

    Example No. 1:

    Let us assume that the component bitrix:catalog connected in the file /fld/cat.php must work with the paths:
    /catalog/index.php – for a list of catalogs,
    /catalog/section/group_code.php?ID=catalogue_code – for a group of goods,
    /catalog/element/goods_code.php?ID=group_code – for detailed information about an item of goods.

    The following parameters must be set up in the incoming parameters for component connection:

    "SEF_MODE" => "Y",    
    "SEF_FOLDER" => "/catalog/",
    "SEF_URL_TEMPLATES" => array(
                        "list" => "index.php",
                        "section" => "section/#SECTION_ID#.php?ID=#IBLOCK_ID#",
                        "element" => "element/#ELEMENT_ID#.php?ID=#SECTION_ID#"    
                                ),
    "VARIABLE_ALIASES" => array(
                         "list" => array(),
                         "section" => array(
                                       "IBLOCK_ID" => "ID"),
                         "element" => array(
                                       "SECTION_ID" => "ID",),    
    

    Example No. 2:

    Let us assume that the component bitrix:catalog connected in the file /fld/cat.php must work with the paths
    /fld/cat.php – for a list of catalogs,
    /fld/cat.php?BID=catalogue_code&SID=group_code – for a group of goods,
    /fld/cat.php?ID=goods_code&SID=group_code – for detailed information about an item of goods.

    The following parameters must be set up in the incoming parameters for a component connection:

    "SEF_MODE" => "N",
    "VARIABLE_ALIASES" => array(
                               "IBLOCK_ID" => "BID",
                               "SECTION_ID" => "SID",
                               "ELEMENT_ID" => "ID",
                               ),

    User-Defined Templating Engines

    Components can work with any templating engines that can be connected from PHP. In order to add a new templating motor onto a website it is necessary to determine (or expand) the global variable $arCustomTemplateEngines in the file /bitrix/php_interface/init.php. This variable contains an associative array where each element is similar to:

       "templator_code" => array(
          "templateExt" => array("extension1"[, "extension2"...]),
          "function" => "motor_connection_function_name"
       )

    where:

    • templator_code - an arbitrary word that is unique for the website,
    • extensionN - the extension of the file that must be processed by this templating motor,
    • motor_connection_function_name - the name of the function that can be invoked if the component template has the indicated extension. The function can be located in the same file /bitrix/php_interface/init.php.

    For example, if the use of Smarty is required on the website in addition to the standard templating motor (PHP), the following code must be added to the file /bitrix/php_interface/init.php:

       global $arCustomTemplateEngines;
       $arCustomTemplateEngines = array(
          "smarty" => array(
             "templateExt" => array("tpl"),
             "function" => "SmartyEngine"
          ),
       );

    As a result, when a .tpl template is connected, the function SmartyEngine will start up instead of the standard motor PHP. SmartyEngine must connect theSmarty motor.

    The syntaxis of motor connection functions is as follows:

       function  motor_connection_function_name ($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)

    where:

    • $templateFile – path to the template file from the website root,
    • $arResult – array of results of the component operation,
    • $arParams – array of incoming parameters of the component,
    • $arLangMessages – array of language messages (translations) of the template,
    • $templateFolder – path to the template folder from the website root (if the template is not located in the folder, this variable is empty),
    • $parentTemplateFolder - path from the website root to the composite component folder as a part of which this component is connected (if the component is connected independently, this variable is empty),
    • $template – template object.

    The code of the templating motor connection function depends on the motor to be connected.

    Complete Example of Smarty Motor Connection Smarty

    The following code must be added to the file /bitrix/php_interface/init.php:

    global $arCustomTemplateEngines;
    $arCustomTemplateEngines = array(
       "smarty" => array(
          "templateExt" => array("tpl"),
          "function" => "SmartyEngine"
       )
    );
    
    function SmartyEngine($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
    {
       if (!defined("SMARTY_DIR"))
          define("SMARTY_DIR", "/libs/");
    
       require_once('/libs/Smarty.class.php');
    
       $smarty = new Smarty;
    
       $smarty->compile_dir = "/templates_c/";
       $smarty->config_dir = "/configs/";
       $smarty->template_dir = "/templates/";
       $smarty->cache_dir = "/cache/";
    
       $smarty->compile_check = true;
       $smarty->debugging = false;
    
       $smarty->assign("arResult", $arResult);
       $smarty->assign("arParams", $arParams);
       $smarty->assign("MESS", $arLangMessages);
       $smarty->assign("templateFolder", $templateFolder);
       $smarty->assign("parentTemplateFolder", $parentTemplateFolder);
    
       $smarty->display($_SERVER["DOCUMENT_ROOT"].$templateFile);
    }

    The line <absolute path to Smarty motor> must be replaced everywhere with the absolute path to Smarty motor. More detailed information about the installation of the motor on a website is provided in the Smarty help system.

    In the sample code, the Smarty motor is registered in the array $arCustomTemplateEngines. Parameters of the motor are initialized in the SmartyEngine function in accordance with system requirements (see the Smarty documentation). Then, the variables of component operation results, incoming parameters, language messages, etc. are transferred to Smarty. And, in the end, the Smarty template processing and displaying method is invoked.

    Complete Example of XML/XSLT Motor Connection

    The following code must be added to the fileë /bitrix/php_interface/init.php:

    global $arCustomTemplateEngines;
    $arCustomTemplateEngines = array(
       "xslt" => array(
          "templateExt" => array("xsl"),
          "function" => "XSLTEngine"
       ),
    );
    
    function CreateXMLFromArray($xDoc, $xNode, $ar)
    {
       foreach($ar as $key=>$val)
       {
          if(!is_string($key) || strlen($key)<=0)
             $key = "value";
    
          $xElement = $xDoc->createElement($key);
          if(is_array($val))
          {
             CreateXMLFromArray($xDoc, $xElement, $val);
          }
          else
          {
             $xElement->appendChild($xDoc->createTextNode(iconv(SITE_CHARSET, "utf-8", $val)));
          }
          $xNode->appendChild($xElement);
       }
       return $xNode;
    }
    
    function XSLTEngine($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
    {
       $arResult["PARAMS"] = array(
          "templateFolder" => $templateFolder,
          "parentTemplateFolder" => $parentTemplateFolder,
          "arParams" => $arParams,
          "arLangMessages" => $arLangMessages
       );
    
       $xDoc = new DOMDocument("1.0", SITE_CHARSET);
       $xRoot = $xDoc->createElement('result');
       CreateXMLFromArray($xDoc, $xRoot, $arResult);
       $xDoc->appendChild($xRoot);
    
       $xXsl = new DOMDocument();
       $xXsl->load($_SERVER["DOCUMENT_ROOT"].$templateFile);
    
       $xProc = new XSLTProcessor;
       $xProc->importStyleSheet($xXsl);
    
       echo $xProc->transformToXML($xDoc);
    }

    Operation of a Composite Component in SEF Mode

    The SEF generating function is embedded in the composite components. These components always have an input parameter SEF_MODE that admits the values Y and N. If SEF_MODE is equal to N the component works with physical links and transfers all of the parameters through the standard parameters of HTTP query. For example:

    /fld/cat.php?IBLOCK_ID=12&SECTION_ID=371

    If the SEF_MODE parameter is equal to Y then the component generates and processes links based on templates. For example, the component can “understand” and process the link:

    /catalog/section/371.php?IBLOCK_ID=12 even if the component itself is located in the file /fld/cat.php.

    If the SEF_MODE parameter is equal to Y, the component must also have the SEF_FOLDER, parameter that must contain a path to the folder with which the component works. This path may either coincide with the physical path or not. For example, the parameters SEF_MODE = Y and SEF_FOLDER=/catalog/ may be set up in the component bitrix:catalog connected in the file /fld/cat.php. In this case, the component will respond to queries using the path /catalog/.... By default, the editor must set up the current physical path to the editable file.

    A composite component that can work in an SEF mode must have a set of path templates by default. For example, in the composite component bitrix:catalog the following array can be defined:

    $arDefaultUrlTemplatesSEF = array( 
          "list" => "index.php", 
          "section" => "section.php?IBLOCK_ID=#IBLOCK_ID#&SECTION_ID=#SECTION_ID#",
          "element" => "element.php?ELEMENT_ID=#ELEMENT_ID#" 
      );

    These path templates can be redefined using the input parameter of the composite component SEF_URL_TEMPLATES that contains a new array of all path templates or a part of them.

    When saving a page with a component operating in an SEF mode, the editor creates or updates an entry in the urlrewrite system. For example, when saving the file /fld/cat.php containing the component bitrix:catalog switched to an SEF mode with the parameter SEF_FOLDER=/catalog/, the urlrewrite system creates or updates an entry similar to:

    array( 
     "CONDITION" => "#^/catalog/#", 
     "ID" => "bitrix:catalog", 
     "PATH" => "/fld/cat.php" 
      ),
    • The value of the parameter SEF_FOLDER is written in CONDITION between the symbols #^ and #;
    • The name of the component is written in ID;
    • The physical path to the file saved is written in PATH.

    If an entry with such PATH and ID already exists, it gets updated and, if not, it is added.

    In run-time, upon receiving a query for a physically nonexistent page, the urlrewrite mechanism searches for the appropriate entry according to CONDITION and transfers control to the page PATH.

    The component located on the PATH page identifies the requested page based on path templates and recovers variables hidden in the path.

    Attention! The mandatory requirement for the set of path templates for this component is the uniqueness of each path template, except parameters and variables. It must be verified when saving the page in the visual editor.

    For example, a set of path templates:

    "section" => "section/#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "element/#ELEMENT_ID#.php"

    is acceptable, and a set of path templates:

    "section" => "#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "#ELEMENT_ID#.php"

    is not acceptable.

    Ways of Data Transmission among Components

    Ways of Data Transmission among Components:

    1. Global variables. For example:
      $GLOBALS['mycomponent_variable'] = $arResult["ID"];

      In addition to GLOBALS you can also use $_SESSION provided that:

      • The volume of data is not big;
      • Immediately after transmission, the data will be deleted from $_SESSION, otherwise, they will be “alive” so long as the session is active.
    2. Wrapper class, for example:
      Class GarbageStorage{
         private static $storage = array();
         public static function set($name, $value){ self::$storage[$name] = $value;}
         public static function get($name){ return self::$storage[$name];}
      }
      Accordingly, the use:
      \GarbageStorage::set('MyCustomID', $arResult["ID"]); #set the value
      \GarbageStorage::get('MyCustomID'); #obtain the value

    Choose the way depending on the components, on what, exactly, you want to transmit to another component, and whether or not there are necessary data available in non-cached files (speaking of component_epilog.php). The use of the wrapper class is more difficult but far more correct.

    A Simple Example of a Component Creation

    As an example, we will create a component which displays current date and time. In this case, the date and time format is set in the component settings. In real life, creating such a component makes no sense, but we do it here in order to understand how a component is developed. The procedure is similar for more complex cases.

    Preliminary Actions

    Preparing php Code of the Component

    The first thing we have to do is write a php code that performs what we need.

    <?
    echo date('Y-m-d');
    ?>

    However, this code just displays the date and there is no option to choose another format. We’d better put the data display format into the variable:

    <?
    $format = 'Y-m-d';
    echo date($format);
    ?>

    And, as a final touch, we have to separate logics and representation:

    <?
    // parameters
    $format = 'Y-m-d';
    // logics
    $d = date($format);
    // representation
    echo $d;
    ?>

    Creating a Structure of Component Folders and Files

    Now we have to create an own namespace, for example: dv. To do this, we have to create a folder /bitrix/components/dv. Inside, we create a component folder — date.current. And inside this folder, in its turn, we create two mandatory files and a folder to store templates titled templates. The folder templates must contain the folder .default with the file template.php inside.

    We obtain the following structure in the folder /bitrix/components/dv/date.current:

    • component.php
    • .description.php
    • templates/.default/template.php

    Implementing the Component without Input Parameters

    For now, we create the component without the option to set up the input parameter – data format.

    File contents:

    • component.php
      <? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
      $arResult['DATE'] = date('Y-m-d');
      $this->IncludeComponentTemplate();
      ?>
    • .description.php
      <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die(); $arComponentDescription = array(
      "NAME" => GetMessage(“Current date”),
      “DESCRIPTION” => GetMessage(“Display current date”),
      );
      ?>
    • templates/.default/template.php
      <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>

    As you might have noted, each component file contains the string if (!defined(“B_PROLOG_INCLUDED”) || B_PROLOG_INCLUDED!==true) die(); in the beginning. This is required so that these files cannot be invoked directly from the browser window.

    The component in its simplest form is ready. It can be invoked in page codes using the structure:

    <? $APPLICATION->IncludeComponent(
    “dv:date.current”,
    “.default”,
    Array(
    ),
    false
    );?>

    Implementing the Component with Input Parameters

    Now, let us make it possible to add the component to a page from the visual editor and set up the date format through component parameters.

    In order to make our component appear in the visual editor we have to expand the component description file.

    .description.php:
    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die(); 
    $arComponentDescription = array(
    "NAME" => GetMessage("Current date"),
    "DESCRIPTION" => GetMessage(“Display current date"),
    "PATH" => array(
    "ID" => "dv_components",
    "CHILD" => array(
    "ID" => "curdate",
    "NAME" => "Current date"
    )
    ),
    "ICON" => "/images/icon.gif",
    );
    ?>

    We have added the PATH array description element in order to place the component in the component tree. Thus, our component will be shown in a separate folder. Alternatively, a component icon may be set, and it will be shown in the tree and in the visual editor.

    Let us take a closer look at the component settings. Assuming that the date template option will be set by a string, we create the file .parameters.php as follows:

    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
     $arComponentParameters = array(
    "GROUPS" => array(),
    “PARAMETERS” => array(
    “TEMPLATE_FOR_DATE” => array(
    “PARENT” => “BASE”,
    “NAME” => “Template for date”,
    “TYPE” => “STRING”,
    “MULTIPLE” => “N”,
    “DEFAULT” => “Y-m-d”,
    “REFRESH” => “Y”,
    ),
    ),
    );
    ?>

    And change the file containing the component logics so that it could use the parameter we set in component.php:

    <? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    $arResult['DATE'] = date($arParams["TEMPLATE_FOR_DATE"]);
    $this->IncludeComponentTemplate();
    ?>

    So, What Have We Done?

    We have created a component in its simplest. We have not taken into account multiple languages, the option to create help for the component, and the option of component caching.

    The majority of custom components for Bitrix Framework are created by changing the components supplied with the products. That is why it is very important to review standard components before starting programming new ones. The task you are to work on most likely has already been resolved by the developers of Bitrix, Inc.

    Component Creation Example

    Let us consider an example of a component creation for messages to the administrator about an error.

    Using this component we can implement a functionality that would permit users to notify content managers about errors found on a website. The error will be sent as an email notice. User’s work algorithm with the component is very simple: on seeing an error on the website, the user highlights the text, presses Ctrl+Enter , and obtains the form:

    The user has to complete the form, and the message is sent to a responsible person.

    Creating a Mail Template

    Since an error notice will be sent by email, a new email template must be created.

    • Go to the page Settings > System settings > Email Events > Email event types.
    • Complete the form:

    • Go to the page Settings > System settings > Email Events >E-Mail templates.
    • Click Add template on the context panel to open template setup form.
    • Set up a template for the email event created:

    Component Creation

    Create a folder feedback.error in an own namespace with the following structure:

    • folder images
      • file feedback.gif
    • folder templates
      • folder .default
        • file script.js
        • file template.php
    • file .description.php
    • file .parameters.php
    • file component.php

    The file feedback.gif is the icon that will be shown in the visual editor.

    The code of the script.js file is as follows:

    BX.bind(document, "keypress", SendError);
    
    function SendError(event, formElem)
    {
    		event = event || window.event;
    
    		if((event.ctrlKey) && ((event.keyCode == 0xA)||(event.keyCode == 0xD)))
    		{
    			var Dialog = new BX.CDialog({
    								title: "An error is found on the website!!",
    								head: "Error description",
    								content: 	'<form method="POST" id="help_form">\
    											<textarea name="error_desc" style="height: 78px; width: 374px;"></textarea>\
    											<input type="hidden" name="error_message"value="'+getSelectedText()+'">\
    											<input type="hidden" name="error_url" value="'+window.location+'">\
    											<input type="hidden" name="error_referer" value="'+document.referrer+'">\
    											<input type="hidden" name="error_useragent" value="'+navigator.userAgent+'">\
    											<input type="hidden" name="sessid" value="'+BX.bitrix_sessid()+'"></form>',
    								resizable: false,
    								height: '198',
    								width: '400'});
    
    			Dialog.SetButtons([
                {
                    'title': 'Send',
    				'id': 'action_send',
    				'name': 'action_send',
                    'action': function(){
    					BX.ajax.submit(BX("help_form"));
                        this.parentWindow.Close();
                    }
                },
    			{
                    'title': 'Cancel',
    				'id': 'cancel',
    				'name': 'cancel',
                    'action': function(){
                        this.parentWindow.Close();
                    }
                }
    			]);
    			Dialog.Show();
    		}
    }
    function getSelectedText(){
      if (window.getSelection){
        txt = window.getSelection();
      }
      else if (document.getSelection) {
        txt = document.getSelection();
      }
      else if (document.selection){
        txt = document.selection.createRange().text;
      }
      else return;
      return txt;
    }
    

    The code of the template.php file is as follows:

    <?
    CUtil::InitJSCore(array('window', 'ajax'));
    ?>

    The code of the .description.php file is as follows:

    <?
    if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    $arComponentDescription = array(
        "NAME" => "Send Error",
        "DESCRIPTION" => "Send Error",
        "ICON" => "/images/feedback.gif",
        "PATH" => array(
            "ID" => "utility",
        ),
    );
    ?>

    The code of the .parameters.php file is as follows:

    <?
    if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    $arComponentParameters = array();
    ?>

    The code of the component.php file is as follows:

    <?
    if (check_bitrix_sessid() && $_SERVER['REQUEST_METHOD'] == "POST" && !empty($_REQUEST["error_message"]) && !empty($_REQUEST["error_url"]))
    {
    	$arMailFields = Array();
    	$arMailFields["ERROR_MESSAGE"] = trim ($_REQUEST["error_message"]);
    	$arMailFields["ERROR_DESCRIPTION"] = trim ($_REQUEST["error_url"]);
    	$arMailFields["ERROR_URL"] = $_REQUEST["error_desc"];
    	$arMailFields["ERROR_REFERER"] = $_REQUEST["error_referer"];
    	$arMailFields["ERROR_USERAGENT"] = $_REQUEST["error_useragent"];
    
    	CEvent::Send("BX", SITE_ID, $arMailFields);
    }
    $this->IncludeComponentTemplate();
    ?>

    Now let us take a closer look at the contents of the file feedback.error\templates\.default\script.js which is of interest for developers.

    Declaration of the handler function that is displayed onto the <body>:

    function SendError(event, formElem)

    Ctrl+Enter wait:

    if((event.ctrlKey) && ((event.keyCode == 0xA)||(event.keyCode == 0xD)))

    Setting up the parameters of a new window and its contents:

    var Dialog = new BX.CDialog({
                title: "An error is found on the website!!",
                head: "Error description",
                content:    '<form method="POST" id="help_form" action="/bitrix/templates/.default/send_error.php">\
                                     <textarea name="error_desc" style="height: 78px; width: 374px;"></textarea>\
                                     <input type="hidden" name="error_message"value="'+getSelectedText()+'">\
                                     <input type="hidden" name="error_url" value="'+window.location+'">\
                                     <input type="hidden" name="sessid" value="'+BX.bitrix_sessid()+'"></form>',
                resizable: false,
                height: '198',
                width: '400'});

    Determining a set of buttons:

    Dialog.SetButtons([
    {
       'title': 'Send',
       'id': 'action_send',
       'name': 'action_send',
       'action': function(){
          BX.ajax.submit(BX("help_form"));
          this.parentWindow.Close();
       }
    },
    {
       'title': 'Cancel',
       'id': 'cancel',
       'name': 'cancel',
       'action': function(){
          this.parentWindow.Close();
       }
    },
    ]);

    Window opening:

    Dialog.Show();

    The function getSelectedText() receives the text marked with the mouse. And then the letter is sent in the text of the file component.php:

    if (check_bitrix_sessid() && $_SERVER['REQUEST_METHOD'] == "POST" && !empty($_REQUEST["error_message"]) && !empty($_REQUEST["error_url"]))
    {
       $arMailFields = Array();
       $arMailFields["ERROR_MESSAGE"] = trim ($_REQUEST["error_message"]);
       $arMailFields["ERROR_DESCRIPTION"] = trim ($_REQUEST["error_desc"]);
       $arMailFields["ERROR_URL"] = trim ($_REQUEST["error_url"]);
       CEvent::Send("BX", SITE_ID, $arMailFields);
    };

    TinyMCE Visual Editor Integration Component

    Let us create a component integrating the popular editor TinyMCE into Bitrix Framework.

    Download the latest version of the editor from the manufacturer’s website.

    In an own namespace, set up a structure of folders and files:

    • /bitrix/components/tools/;
      • /bitrix/components/tools/editor.tiny.mce/;
        • /bitrix/components/tools/editor.tiny.mce/templates/;
          • /bitrix/components/tools/editor.tiny.mce/templates/.default/;
        • /bitrix/components/tools/editor.tiny.mce/tiny_mce/ - a folder for editor installation package;
        • component.php — component logics;
        • .parameters.php — a file to describe input parameters.

    Copy the downloaded installation package into the folder /tiny_mce.

    Add the following code in the file component.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    	$APPLICATION->AddHeadScript($this->__path .'/tiny_mce/tiny_mce.js');
    
    	$sNameTextAria   =  (isset($arParams['TEXTARIA_NAME'])   == false) ? 'content'   : $arParams['TEXTARIA_NAME'];
    	$sIdTextAria   	 =  (isset($arParams['TEXTARIA_ID'])     == false) ? '' 		 : $arParams['TEXTARIA_ID'];
                 if ('' == trim($sIdTextAria))
                 $sIdTextAria = 'content';
    	$sEditorID 		 =  (isset($arParams['INIT_ID'])  	     == false) ? 'textareas' : $arParams['INIT_ID'];
    	$iTextariaWidth  =  (isset($arParams['TEXTARIA_WIDTH'])  == false) ? '100%'      : $arParams['TEXTARIA_WIDTH'];
    	$iTextariaHeight =  (isset($arParams['TEXTARIA_HEIGHT']) == false) ? '300'       : $arParams['TEXTARIA_HEIGHT'];
    	$sText 			 =  (isset($arParams['TEXT']) 			 == false) ? ''       	 : $arParams['TEXT'];
    	?>
    
    <script type="text/javascript">
    
    <? 
    if($arParams['TYPE_EDITOR'] == 'TYPE_1')
    {
    	?>
    	tinyMCE.init(
    		{
    			language : 'ru',
    			mode 	 : "textareas",
    			//elements : "<?=$sEditorID?>",
    			editor_selector : "<?=$sEditorID?>",
    			theme    : "advanced",
    			plugins  : "safari, spellchecker, upload.images.komka, wordcount, fullscreen",
    			theme_advanced_buttons1 : "formatselect,fontselect,fontsizeselect,bold,italic,underline,link,justifyleft,justifycenter,
                                           justifyright,pasteword,pastetext,images,|,bullist,numlist,|,undo,redo,|,spellchecker,fullscreen",
    			theme_advanced_buttons2 : "",
    			theme_advanced_buttons3 : "",
    			theme_advanced_toolbar_location   : "top",
    			theme_advanced_toolbar_align      : "left",
    			theme_advanced_statusbar_location : "bottom",
    			theme_advanced_resizing           : false,
    			content_css                       : "<?=$this->__path?>/example.css",
    			height : "<?=$iTextariaHeight?>",
    			spellchecker_languages : '+Ðóññêèé=ru,English=en',
    			spellchecker_word_separator_chars : '\\s!\"#$%&()*+,-./:;<=>?@[\]^_{|}'
    		}
    	);
    	<? 
    }
    elseif($arParams['TYPE_EDITOR'] == 'TYPE_2')
    {
    	?>
    		tinyMCE.init({
    				language : 'ru',
    				mode 	 : "textareas",
    				editor_selector : "<?=$sEditorID?>",
    				theme    : "advanced",
    				plugins : "safari,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,
                               iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,
                               fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,imagemanager,filemanager",
    				theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,
                                               justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect",
    				theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,
                                               numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,
                                               insertdate,inserttime,preview,|,forecolor,backcolor",
    				theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,
                                               emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
    				theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,
                                               spellchecker,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,blockquote,
                                               pagebreak,|,insertfile,insertimage",
    				theme_advanced_toolbar_location : "top",
    				theme_advanced_toolbar_align : "left",
    				theme_advanced_statusbar_location : "bottom",
    				theme_advanced_resizing : true,
    				content_css : "<?=$this->__path?>/example.css",
    				height : "<?=$iTextariaHeight?>",
    				template_external_list_url : "js/template_list.js",
    				external_link_list_url : "js/link_list.js",
    				external_image_list_url : "js/image_list.js",
    				media_external_list_url : "js/media_list.js",
    				template_replace_values : {username : "Some User", staffid : "991234"}
    			}
    		);
    	<? 
    }
    ?>
    
    </script>
    <textarea id="<?=$sIdTextAria?>" class="<?=$sEditorID?>"  name="<?=$sNameTextAria?>" style="width:<?=$iTextariaWidth?>"><?=$sText?></textarea>
    <? $this->IncludeComponentTemplate();?>
    

    The file .parameters.php

    
    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    $arComponentParameters = array(
    	"PARAMETERS" => array(
    		"TYPE_EDITOR" => Array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Editor mode",
    	         "TYPE" => "LIST",
    	         "VALUES" => array('TYPE_1' => 'Simplified editor', 'TYPE_2' => 'Full editor'),
    		),
    
    		'INIT_ID' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Editor ID (unique)",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => '',
    		),
    		
    		'TEXT' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Content to be inserted to the editor",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => $_POST['content'],
    		),
    		
    		'TEXTARIA_NAME' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "TEXTARIA field name",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => 'content',
    		),
    		
    		'TEXTARIA_ID' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "TEXTARIA ID field",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => 'content',
    		),
    		
    		'TEXTARIA_WIDTH' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Editor width",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => '100%',
    		),
    
    		'TEXTARIA_HEIGHT' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Editor height",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => '300',
    		),
    	)
    );
    /*
    				array(
    					'TEXT' 			  => $_POST['content'], # contents, in html
    					'TEXTARIA_NAME'   => 'content',   		# field name
    					'TEXTARIA_ID'     => 'content',   		# field ID
    					'INIT_ID'	      => 'textareas', 		# editor ID
    					'TEXTARIA_WIDTH'  => '100%', 	  		
    					'TEXTARIA_HEIGHT' => '300' 		 
    				)
    */
    ?>

    Several important points in the code of the component.php must be clarified. Connection of the editor proper:

    <?  $APPLICATION->AddHeadScript($this->__path .’/tiny_mce/tiny_mce.js’); ?>

    Connection of styles put separately in the component folder for convenience; this setting is made in js in the initialization code:

    content_css : ‘<?=$this->__path?>/example.css’,
    Attention! If there are two or more editors on a page we have to identify them by the class name editor_selector : ‘<?=$sEditorID?>’.

    The text area proper for which all the work is being done:

    <textarea id=’<?=$sIdTextAria?>’  name=’<?=$sNameTextAria?>’ style=’width:<?=$iTextariaWidth?>’><?=$sText?></textarea>

    How to Use

    We get connected as follows:

    <? echo $_POST['content'] ?>
    <? echo $_POST['content2'] ?>
    <form action="" method="post" name="">
    <? $APPLICATION->IncludeComponent("tools:editor.tiny.mce", ".default", array(
    "TEXT" => $_POST["content"], // contents from the request which must be inserted
    "TEXTARIA_NAME" => "content", // field name
    "TEXTARIA_ID" => "content",         // field id
    "TEXTARIA_WIDTH" => "100%",  // width
    "TEXTARIA_HEIGHT" => "300",    // height
     
    "INIT_ID" => "ID" // ID of the proper editor
    ),
    false
    );
    ?>
    <input value="submit" name="sub" type="submit" />
    </form>

    Caching in own components

    Note: In Bitrix Framework caching time is recorded in seconds.

    What is the use of caching in own components

    Making direct database queries through API, obtaining information, formatting it in the component template, and displaying it to the user may seem to be the simplest solution.

    However, the issue is the performance of the web project in case many users work with it at the same time. If it takes the component 0.1 sec. to respond without cache and executing, say, 100 database queries, then if 100 users work simultaneously the database server load will increase, and so will the component response time up to, for example, 5-10 sec.

    An equally important point to keep in mind is the speed of response of a component when receiving data from cache. If it takes the component 2 sec. to respond to each user without cache, then, with cache, it will take the component 2 sec. to respond to one user and 0.1 sec. – to the remaining 100 users over the next, say, 30 minutes.

    When using cache in own components 2.0:

    • Web project performance and load tolerance drastically increase because the database load drops to a minimum, and the web solution will be able to serve, for example, not just 50,000 users per day but 1,000,000 and more.
    • Web pages are loaded to the user’s browser much faster (in tenths of a second) because their structural information is saved on the server and is not taken from the database.

    Caching Time

    The time required for caching depends on the type of caching. If the caching Auto+Management is used, the information will be supplied from cache until it is changed in the database and cache resets automatically. The caching time for this mode must be long, e.g., 1 year.

    If Auto caching is used, it is recommended to set up the longest cache interval permitted with due regard to business logic. The caching time for this mode depends on the frequency of the information update. For some components, the time must be set to 24 hours. For frequently updated components, a controlled cache is recommended; alternatively, a value of, for example, 10 minutes shall be set.

    Embedded Cache Support

    2.0 components come with an embedded support of a typical cache algorithm. The component structure using embedded cache support will be similar to:

    // Verification and initialization of input parameters
    if ($arParams["ID"] <= 0)
       $arParams["ID"] = 10;
    
    // If no valid cache is available (i.e. data must be requested
    // and valid cache must be created)
    if ($this->StartResultCache())
    {
       // Data query and completion of $arResult
       $arResult = array(
          "ID" => rand(1, 100)
       );
    
       for ($i = 0; $i < 5; $i++)
          $arResult["FIELDS"][] = rand(1, 100);
    
       // If any condition is met, there is no need
       // to cache data
       if ($arParams["ID"] < 10)
          $this->AbortResultCache();
    
       // Connect output template
       $this->IncludeComponentTemplate();
    }
    
    // Set up page header using deferred
    // function
    $APPLICATION->SetTitle($arResult["ID"]); 

    Comments to the Code

    The method CBitrixComponent::StartResultCache()has the following description: bool $this->StartResultCache($cacheTime = False, $additionalCacheID = False, $cachePath = False)

    where:

    • $cacheTime - caching time (if False - IntVal($arParams["CACHE_TIME"]) is used for substitution);
    • $additionalCacheID - additional parameters on which cache depends, apart from the current website’s SITE_ID,component name, file path, and input parameters;
    • $cachePath - path to cache file (if False - "/".SITE_ID. is used for substitution).

    If a valid cache is available, the method displays its contents, completes $arResult, and returns False. If there is no valid cache, the method returns True.

    If cache depends not only on the website, input parameters, component name, and a path to the current website, but also on other parameters, these parameters must be sent to the method as a second parameter in the form of a string. For example, if cache also depends on the user groups to which a current visitor belongs, the condition should be written as follows:

    if ($this->StartResultCache(false, $USER->GetGroups()))
    {
       // There is no valid cache. We have to choose data from the base to $arResult
    }

    If following data selection (if no valid cache is available), it becomes evident that there is no need to cache data, the method $this->AbortResultCache(); must be invoked. E.g., if it turns out that there is no news with such ID, caching should be interrupted by displaying a message that there is no such news. If caching is not interrupted, some intruders may clog up with cache all disc space allocated to the website by invoking the page with arbitrary (including non-existent) IDs.

    The method $this->IncludeComponentTemplate(); connects the component template and saves to the cache file the output and array of results $arResult. All changes to $arResult and output will not be saved to cache after the template connection method is invoked.

    If during the component code execution we have not entered into the body of the condition if ($this->StartResultCache()), it means that there is a valid cache for this component, page, and input parameters. After this method is invoked, HTML from cache is displayed and we have a completed array $arResult. Here, we can do something. For example, set up the page header using deferred functions.

    If during the execution of certain conditions the component cache must be cleared (e.g., the component “knows” that the data have been changed), the method $this->ClearResultCache($additionalCacheID = False, $cachePath = False) can be used. Parameters of this method are consistent with the same-name parameters of the StartResultCache method.

    Complex Caching

    If the component requires any special caching that cannot be executed using embedded cache support, the standard class CPHPCache can be used. The component structure using the class CPHPCache will be more or less as follows:

    // Verification and initialization of the input parameters
    if ($arParams["ID"] <= 0)
       $arParams["ID"] = 10;
    
    $arParams["CACHE_TIME"] = IntVal($arParams["CACHE_TIME"]);
    $CACHE_ID = SITE_ID."|".$APPLICATION->GetCurPage()."|";
    // Cache only depends on prepared parameters without "~"
    foreach ($this->arParams as $k => $v)
       if (strncmp("~", $k, 1))
          $CACHE_ID .= ",".$k."=".$v;
    $CACHE_ID .= "|".$USER->GetGroups();
    
    $cache = new CPageCache;
    if ($cache->StartDataCache($arParams["CACHE_TIME"], $CACHE_ID, "/".SITE_ID.$this->GetRelativePath()))
    {
       // Request of data and formation of the array $arResult
       $arResult = array("a" => 1, "b" => 2);
    
       // Component template connection
       $this->IncludeComponentTemplate();
    
       $templateCachedData = $this->GetTemplateCachedData();
    
       $cache->EndDataCache(
          array(
             "arResult" => $arResult,
        "templateCachedData" => $templateCachedData
          )
       );
    }
    else
    {
       extract($cache->GetVars());
       $this->SetTemplateCachedData($templateCachedData);
    }

    Comments to the Code

    Cache must only depend on the prepared parameters, i.e. on the parameters that are properly initialized and reduced to a required type (e.g., using IntVal()) etc. The array $arParams contains both prepared parameters and initial parameters (with the same key but with the prefix "~"). If the cache depends on unprepared parameters, intruders will be able to clog up all the disk space allocated to the website with cache by calling a page with IDs equal to "8a", "8b", ... (which give 8 after IntVal()).

    The method $this->IncludeComponentTemplate() does not request data from the database. However, it is better to also include it into the cached area because this method performs certain disk operations.

    Before calling the method for cache completion and cache saving (the method EndDataCache), it is necessary to request parameters from the template. These parameters must be used even if the template itself is not connected and data are taken from cache. In the current version, such parameters include css styles of the template that are connected by deferred functions and thus do not go to cache. The structure of the data returned by the template is not documented and has no meaning for the component. These are just data that must be placed to cache and then taken from cache and returned to the template.

    In order to return to the template, the data previously saved in cache according to the template’s “wish”, the methods $this->SetTemplateCachedData($templateCachedData); or CBitrixComponentTemplate::ApplyCachedData($templateCachedData); can be used. One of these methods must be invoked in the component area which is executed in case there is a valid cache available. It must receive (in parameters) the data that the template “asked” to save.

    Some Recommendations


    If the component uses standard caching but no template is connected (because it is not necessary), the following shall be used:

    $this->EndResultCache();


    A possible solution where the component template is removed from the cached area. Other components can be connected in the template itself.

    $cache_id = serialize(array($arParams, ($arParams['CACHE_GROUPS']==='N'? false: $USER->GetGroups()))); 
    $obCache = new CPHPCache; 
    if ($obCache->InitCache($arParams['CACHE_TIME'], $cache_id, '/')) 
    { 
       $vars = $obCache->GetVars(); 
       $arResult = $vars['arResult']; 
    } 
    elseif ($obCache->StartDataCache()) 
    { 
    
       // code
    
       $obCache->EndDataCache(array( 
          'arResult' => $arResult, 
       )); 
    } 
    

    If the code is written properly and template.php ícontains no “heavy” code, this option might work well enough.

    CUSTOM Parameter Type

    The CUSTOM parameter type gives total freedom of customization to the developer. For example, there is a system or a third party component. Depending on the template there is a need to add specific settings to the component.

    It can be done as follows:

    Example Description
    JS_FILE The file containing JS code that is responsible for displaying the custom option
    JS_EVENT Callback function that will be called after loading JS_FILE
    JS_DATA Additional data transmitted to JS_EVENT

    Example JS_DATA:

    {
    data:JS_DATA, //JS_DATA from .parameters.php
    oCont: td,    /* the container where custom control panel can be located with the parameter */
    oInput: input,//input in which the parameter value will be transmitted to server during saving
    popertyID:"MAP_DATA",//parameter name
    propertyParams: { /*...*/ },//The object with the same contents as the parameter array in .parameters.php
    fChange:function(){ /*...*/ },//callback for call during parameter change
    getElements:function(){ /*...*/ }//returns the object with all parameters of the component
    }

    Implementation in a Standard Component

    Let us consider an example of using CUSTOM parameter type in the standard component map.google.view.

    We can see the following in the file .parameters.php:

    $arComponentParameters = array(
    //...
    'MAP_DATA' => array(
                'NAME' => GetMessage('MYMS_PARAM_DATA'),
                'TYPE' => 'CUSTOM',
                'JS_FILE' => '/bitrix/components/bitrix/map.google.view/settings/settings.js',
                'JS_EVENT' => 'OnGoogleMapSettingsEdit',
                'JS_DATA' => LANGUAGE_ID.'||'.GetMessage('MYMS_PARAM_DATA_SET'),
                'DEFAULT' => serialize(array(
                    'google_lat' => GetMessage('MYMS_PARAM_DATA_DEFAULT_LAT'),
                    'google_lon' => GetMessage('MYMS_PARAM_DATA_DEFAULT_LON'),
                    'google_scale' => 13
                )),
                'PARENT' => 'BASE',
            )
    //...
    );

    In the file /bitrix/components/bitrix/map.google.view/settings/settings.js:

    function JCEditorOpener(arParams)
    {
        this.jsOptions = arParams.data.split('||');
        this.arParams = arParams;
    
        var obButton = document.createElement('BUTTON');//creating a button
        this.arParams.oCont.appendChild(obButton);// adding to container
       
        obButton.innerHTML = this.jsOptions[1];//text from JS_DATA
       
        obButton.onclick = BX.delegate(this.btnClick, this);//specify callback functions
        this.saveData = BX.delegate(this.__saveData, this);
    }

    Clicking the button opens the dialog generated in /bitrix/components/bitrix/map.google.view/settings/settings.php. The current value of MAP_DTA is transmitted in the query to settings.php.

    Header

    obJSPopup->ShowTitlebar();
    $obJSPopup->StartDescription('bx-edit-menu');
    <p><b><? echo GetMessage('MYMV_SET_POPUP_WINDOW_TITLE')?></b></p>
    <p class="note"><? echo GetMessage('MYMV_SET_POPUP_WINDOW_DESCRIPTION')?></p>

    Content Block

    $obJSPopup->StartContent();

    Buttons Block

    $obJSPopup->StartButtons();

    Save Button

    <input type="submit" value="<?echo GetMessage('MYMV_SET_SUBMIT')?/>" onclick="return jsGoogleCE.__saveChanges();"/>
    $obJSPopup->ShowStandardButtons(array('cancel'));//cancel button
    $obJSPopup->EndButtons();

    В __saveChanges() the data are serialized in a string and written into oInput. The serialization function in js to the php format can be looked up in bitrix/components/bitrix/map.google.view/settings/settings_lod.js. In the component the de-serialization is performed from $arParam[~MAP_DATA].

    Localization

    The language file is located in lang/en/.parameters.php. When using the CUSTOM parameter type, do not forget to add messages to this file.


    More examples

    How Can CAPTCHA Be Displayed in an Own Component?

    An example of using CAPTCHA on a page.

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
    $APPLICATION->SetTitle("Title");
    ?>
    <?
    if (isset($submit)) {
      echo 'ñàáìèò ïðîøåë...<br>';
      echo $myname.'<br>';
      echo $cap.'<br>';
       if (!$GLOBALS["APPLICATION"]->CaptchaCheckCode($cap, $captcha_sid))
          {
             $error=true;
             echo 'error captcha
    '; } } ?> <form id="linkForm" name="mailForm" action="test.php" method="post"> <table cellspacing="3" cellpadding="0" width="100%" bgcolor="#eeeeee" border="0"> <tbody> <tr><td valign="top" align="right">Name *</td><td><input size="40" value="" name="myname" /></td></tr> <tr><td valign="top" align="right">CAPTCHA *</td><td><? $capCode = $GLOBALS["APPLICATION"]->CaptchaGetCode(); ?> <input type="hidden" name="captcha_sid" value="<?= htmlspecialchars($capCode) ?>"> <img src="/bitrix/tools/captcha.php?captcha_sid=<?= htmlspecialchars($capCode) ?>" width="180" height="40"><br> <input size="40" value="" name="cap" /></td></tr> <tr><td valign="top" align="right"> </td> <td><input type="submit" value="Sent" name="submit" />  <input type="reset" value="Ñáðîñèòü" name="Reset" /></td></tr> </tbody> </table> </form><?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>

    How to Make a Component Invisible on the Home Page and Visible on Other Pages?

    Solution:

    if ($curPage = $APPLICATION->GetCurPage())
    {
       if (($curPage != "/index.php"))
            {
                ....
            }
    } 

    Errors When Working with Components

    Cannot Find Component Access Code

    This error is quite common when you try to edit component parameters in the page editing mode. Although the code contains the line $APPLICATION->IncludeComponent() (component access), sometimes the error Cannot find component access code appears anyway. Unfortunately, there is no one-size-fits-all solution for this problem.

    Possible solutions:

    • Include two strings to .htaccess:

      For non-UTF:

          php_value mbstring.func_overload 0
          php_value mbstring.internal_encoding latin1

      For UTF:

          php_value mbstring.func_overload 2
          php_value mbstring.internal_encoding UTF-8
    • The error possibly appears due to the incorrect placement of html tags (e.g., one of the tags is closed in a wrong place);
    • Remove all of the html comments from the page;
    • Frame the component access code with the symbols <? ?> (thus separating it from another php code);
    • Place the structure <?/**/?> before component access;
    • Remove several similar components close to the component that fails to work.

    Modules

    Bitrix Framework has a module structure. Each module is responsible for managing certain website elements and parameters – website information contents and structure, forums, advertising, mailings, distribution of rights among user groups, collection of visiting statistics, evaluation of advertising campaign efficiency, etc.

    Module - is a data model and API for access to these data. Static methods of module classes can be accessed in components, templates, and other modules. In addition, class instances can be created inside Bitrix Framework.

    System modules mostly work independently from one another. However, in a number of cases the functionality of certain modules depends on the capabilities of other modules. For example:

    • The module Commercial Catalog expands the capacity of the Information Block module and permits you to set up goods prices depending on various conditions and apply a surcharge and discounts to goods, etc.
    • The Workflow module permits to organize consecutive team work with contents of the modules Information Block and Site Explorer.

    After the system is installed, the list of modules used can be viewed on the page Module Management (Settings > System settings > Modules) in the administrative section of the system:

    It is recommended to delete unused modules in order to save disc space. There is always a possibility to reinstall any module if necessary. During the deinstallation of certain modules, the system offers to save the data accumulated by the module (module tables). If you intend to use these data later on, do not forget to mark up this option when deleting a module.

    The level of users’ rights to access system modules is managed separately for each module on its setup page. The general parameters of module operation are managed on the same page.

    The setup page of a specific module may have a different amount of tabs and fields, depending on module functionality. It can be accessed as follows:

    • Using the administrative menu: Settings > System settings > Module settings > module_name;
    • Using the button Settings, located on the administrative panel. This button permits access to the settings of the module which pages (forms) are currently open in the main work area.

    Note: Before using the API of a module it is necessary to make sure it is installed and connect it using the following structure:
    <?
       if(CModule::IncludeModule("******"))
       { 
    	//module functions and classes can be used here
       } 
       ?>
    where **** - module identifier.

    Modules and Components

    Modules in Bitrix Framework represent models and controllers of a low level (in terms of MVC) and components – controllers of high level which include representations based on the hierarchy of the website file structure. As a rule, all functional capacity of any website is implemented using standard modules, but components have to be customized (or own components have to be written) in order to generate and display pages; these components have to be connected on the relevant website pages.

    Module Development

    Bitrix Framework permits developing user modules.

    File Structure

    Module File Structure:

    • /bitrix/modules/module ID/ - module root catalogue
      • /admin/ - catalogue with administrative scripts of the module;
        • menu.php - file containing administrative menu of the module;
      • /classes/ - scripts with module classes;
        • /general/ - module classes that do not depend on the database used;
        • /mysql/ - module classes intended for work only with MySQL;
        • /mssql/ - module classes intended for work only with MS SQL;
        • /oracle/ - module classes intended for work only with Oracle;
      • /lang/language ID/ - catalogue with language files of module scripts;
      • /install/ - catalog with files used for module installation and deinstallation;
        • /admin/ - catalog with scripts connecting administrative scripts of the module (access scripts);
        • /js/ - catalog with js scripts of the module; these are copied to /bitrix/js/module_ID/;
        • /db/ - catalogue with SQL scripts for installation/deinstallation of database;
          • /mysql/ - SQL scripts for installation/deinstallation of tables in MySQL;
          • /mssql/ - SQL scripts for installation/deinstallation of tables in MS SQL;
          • /oracle/ - SQL scripts for installation/deinstallation of tables in Oracle;
        • /images/ - catalog with images used by the module; after the module is installed they must be copied into the catalog /bitrix/images/module ID/;
        • /templates/ - catalog with components 1.0 of the module. (The catalog is saved only to ensure version compatibility);
          • /module ID/ - catalog containing main files of the component;
          • /lang/language ID/module ID/ - language files of the module components are located in this catalog;
        • /components/namespace/component name/ - catalog containing components 2.0 of the module;
        • /themes/module_name/ - contains css and images for the styles of the administrative panel, if the module needs them (outdated, used before version 12.0);
        • /panel/module_name/ - contains css and images for the styles of the administrative panel, if the module needs them.
        • index.php - file containing module description;
      • include.php - this file is connected when the module is connected in a code; all files containing libraries of functions and module classes must be located here;
      • default_option.php - contains an array named $ID module_default_option where default values are set for module parameters.

        Note: If the module name contains a dot (e.g., mycompany.forum) in the name of a variable, the dot will be replaced with the underscore character.

      • options.php - this file is connected on the setup page of module parameters in the administrative menu Settings;
      • prolog.php - this file can be connected in all administrative scripts of the module. Normally the constant ADMIN_MODULE_NAME (module identifier) which is used in the control panel is determined here.

    Description and Parameters

    Description

    Each module must be properly described in the system. That way, the system will “know” how to work with such a module. Improperly described modules can cause the complete or partial unserviceability of the system (e.g., the update system might fail).

    The main file used by the system in order to manipulate the module is /bitrix/modules/module ID/install/index.php. The main purpose of this file is the placement of a class in it with a name that coincides with the module ID.

    Example:

    01	<?
    02	Class mymodule extends CModule
    03	{
    04	    var $MODULE_ID = "mymodule";
    05	    var $MODULE_NAME;
    06	 
    07	    function DoInstall()
    08	    {
    09	        global $DB, $APPLICATION, $step;
    10	        $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"), $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/mymodule/install/step1.php");
    11	    }
    12	 
    13	    function DoUninstall()
    14	    {
    15	        global $DB, $APPLICATION, $step;
    16	        $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"), $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/mymodule/install/unstep1.php");
    17	 
    18	    }
    19	}
    20	?>

    Mandatory methods of this class:

    • DoInstall - launches upon clicking the button Inatsall on the page Module Management of the administrative section and installs the module.
    • DoUninstall - aunches upon clicking the button Remove on the page Module Management of the administrative section and uninstalls the module.

    Optional method for this class:

    • GetModuleRightList - returns the list of unique rights (or roles) of the moduleÿ.

    Mandatory properties of an object of this class:

    • MODULE_ID - stores module ID;
    • MODULE_VERSION - current version of the module in the format XX.XX.XX;
    • MODULE_VERSION_DATE - the line containing the date of the module version; the date must be set in the format YYYY-MM-DD HH:MI:SS;
    • MODULE_NAME - module name;
    • MODULE_DESCRIPTION - module description;
    • MODULE_GROUP_RIGHTS - if the method GetModuleRightList is set, this property must contain Y.

    Examples

    Example of the file with the description of the module Web Forms:

    <?
    global $MESS;
    $PathInstall = str_replace("\\", "/", __FILE__);
    $PathInstall = substr($PathInstall, 0, strlen($PathInstall)-strlen("/index.php"));
    IncludeModuleLangFile($PathInstall."/install.php");
    include($PathInstall."/version.php");
    if(class_exists("form")) return;
    Class form extends CModule
    {
        var $MODULE_ID = "form";
        var $MODULE_VERSION;
        var $MODULE_VERSION_DATE;
        var $MODULE_NAME;
        var $MODULE_DESCRIPTION;
        var $MODULE_GROUP_RIGHTS = "Y";
    
        function form()
        {
            $this->MODULE_VERSION = FORM_VERSION;
            $this->MODULE_VERSION_DATE = FORM_VERSION_DATE;
            $this->MODULE_NAME = GetMessage("FORM_MODULE_NAME");
            $this->MODULE_DESCRIPTION = GetMessage("FORM_MODULE_DESCRIPTION");
        }
    
        function DoInstall()
        {
            global $DB, $APPLICATION, $step;
            $FORM_RIGHT = $APPLICATION->GetGroupRight("form");
            if ($FORM_RIGHT=="W")
            {
                $step = IntVal($step);
                if($step<2)
                    $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"),
                    $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/step1.php");
                elseif($step==2)
                    $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"),
                    $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/step2.php");
            }
        }
    
        function DoUninstall()
        {
            global $DB, $APPLICATION, $step;
            $FORM_RIGHT = $APPLICATION->GetGroupRight("form");
            if ($FORM_RIGHT=="W")
            {
                $step = IntVal($step);
                if($step<2)
                    $APPLICATION->IncludeAdminFile(GetMessage("FORM_UNINSTALL_TITLE"),
                    $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/unstep1.php");
                elseif($step==2)
                    $APPLICATION->IncludeAdminFile(GetMessage("FORM_UNINSTALL_TITLE"),
                    $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/unstep2.php");
            }
        }
    
        function GetModuleRightList()
        {
            global $MESS;
            $arr = array(
                "reference_id" => array("D","R","W"),
                "reference" => array(
                    GetMessage("FORM_DENIED"),
                    GetMessage("FORM_OPENED"),
                    GetMessage("FORM_FULL"))
                );
            return $arr;
        }
    }
    ?>

    Example of the file indicating module version

    <?
    $arModuleVersion = array(
        "VERSION" => "11.0.4",
        "VERSION_DATE" => "2011-11-17 14:00:00"
    );
    ?>

    Parameters

    Module parameters are available for change in the administrative interface on the page Module Settings (Settings > System settings > Module settings). When choosing a module on this page, the system connects the file /bitrix/modules/module ID/options.php intended for controlling module parameters, setting up rights to the module, etc.

    Module parameters are stored in the database.

    When receiving module parameters, a default value can be used that is set in the file /bitrix/modules/module ID/default_option.php. In this file, the array $ID module_default_option is defined which stores the default values.

    Example of the file /bitrix/modules/module ID/default_option.php:

    <?
    $support_default_option = array(
        "SUPPORT_DIR"                => "#SITE_DIR#support/",
        "SUPPORT_MAX_FILESIZE"       => "100",
        "ONLINE_INTERVAL"            => "900",
        "DEFAULT_VALUE_HIDDEN"       => "N",
        "NOT_IMAGE_EXTENSION_SUFFIX" => "_",
        "NOT_IMAGE_UPLOAD_DIR"       => "support/not_image",
        "DEFAULT_AUTO_CLOSE_DAYS"    => "7"
        );
    ?>

    Example of use:

    <?
    // We set up a string parameter
    COption::SetOptionString("my_module_id", "MY_PARAMETER_ID", "VALUE");
    
    // We will obtain a string parameter
    $value = COption::GetOptionString("my_module_id", "MY_PARAMETER_ID", "DEFAULT_VALUE");
    ?>

    The class COption is intended for work with module parameters.


    Administrative Scripts

    Administrative Scripts - are the scripts used by the module in the administrative part of the system. They must be located in the catalog /bitrix/modules/module ID/admin/.

    t must be taken into account that administrative scripts cannot be accessed directly in the browser (like any scripts of the /bitrix/modules/). That is why additional homonymous access scripts are used in order to access administrative scripts. Access scripts are located in the catalog /bitrix/admin/. As a rule, they only contain the connection of the administrative script of the same name:

    <?
    require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/module ID/admin/script");
    ?>

    When installing the module, access scripts must be copied from the catalog /bitrix/modules/module ID/install/admin/ to the catalog /bitrix/admin/. Upon deinstallation of the module access scripts must be deleted from this catalog.

    Note: It must be taken into account that access scripts of all installed modules are located in the same catalog /bitrix/admin/; that is why it is recommended that their names start with a prefix characteristic only for the relevant module to avoid duplication.

    Two constants that are necessary to generate an icon above the page header must be determined in each administrative script before connecting the visual part of the administrative prologue:

    • ADMIN_MODULE_NAME - module identifier (see the example in standard module identifiers);
    • ADMIN_MODULE_ICON - HTML code for a big icon of the module to be displayed above page header.

    Example of the definitions of such constants:

    define("ADMIN_MODULE_NAME", "statistic");
    define("ADMIN_MODULE_ICON", "<a href=\"stat_list.php?lang=".LANGUAGE_ID."\">
    <img src=\"/bitrix/images/statistic/statistic.gif\" 
    width=\"48\" height=\"48\" border=\"0\" alt=\"".GetMessage("STAT_MODULE_TITLE")."\"
    title=\"".GetMessage("STAT_MODULE_TITLE")."\"></a>");

    Language Files for Administrative Scripts of the Module

    Language files must be located in the catalog /bitrix/modules/module ID/lang/language ID/. The particularity of their location inside this catalog is that they must be located strictly following the same path from the catalog /bitrix/modules/module ID/ as the homonymous files in which they are connected. In this case, language files can only be connected using the function IncludeModuleLangFile.

    For example, for the script /bitrix/modules/module ID/admin/body/my_script.php the language file must be located here: /bitrix/modules/module ID/lang/language ID/admin/body/my_script.php.

    And connected using the code:
    IncludeModuleLangFile(__FILE__);

    Administrative Menu

    The menu of the administrative part is displayed by the standard function CMain::GetMenuHtmlEx.

    The menu template is stored in the file /bitrix/modules/main/interface/.left.menu_template.php

    The main file collecting menu options is /bitrix/modules/main/interface/.left.menu.php. Here, all the files contained in /bitrix/modules/module ID/admin/menu.php are examined. Each such file contains a definition of the array $aModuleMenuLinks containing the menu options of the relevant module. All of these arrays will be later unified into a standard array $arMenuSections that contains information about all the menu options.

    Sample menu structure using the example of \bitrix\modules\main\admin\menu.php

    $aMenu[] = array(
       "parent_menu" => "global_menu_settings",
       "sort" => 1800,
       "text" => GetMessage("MAIN_MENU_TOOLS"),
       "title" => GetMessage("MAIN_MENU_TOOLS_TITLE"),
       "url" => "tools_index.php?lang=".LANGUAGE_ID,
       "icon" => "util_menu_icon",
       "page_icon" => "util_page_icon",
       "items_id" => "menu_util",
       "items" => array(
          array(
             "text" => GetMessage("MAIN_MENU_SITE_CHECKER"),
             "url" => "site_checker.php?lang=".LANGUAGE_ID,
             "more_url" => array(),
             "title" => GetMessage("MAIN_MENU_SITE_CHECKER_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_FILE_CHECKER"),
             "url" => "file_checker.php?lang=".LANGUAGE_ID,
             "more_url" => array(),
             "title" => GetMessage("MAIN_MENU_FILE_CHECKER_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_PHPINFO"),
             "url" => "phpinfo.php?test_var1=AAA&test_var2=BBB",
             "more_url" => array("phpinfo.php"),
             "title" => GetMessage("MAIN_MENU_PHPINFO_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_SQL"),
             "url" => "sql.php?lang=".LANGUAGE_ID."&del_query=Y",
             "more_url" => array("sql.php"),
             "title" => GetMessage("MAIN_MENU_SQL_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_PHP"),
             "url" => "php_command_line.php?lang=".LANGUAGE_ID."",
             "more_url" => array("php_command_line.php"),
             "title" => GetMessage("MAIN_MENU_PHP_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_AGENT"),
             "url" => "agent_list.php?lang=".LANGUAGE_ID,
             "more_url" => array("agent_list.php", "agent_edit.php"),
             "title" => GetMessage("MAIN_MENU_AGENT_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_DUMP"),
             "url" => "dump.php?lang=".LANGUAGE_ID,
             "more_url" => array("dump.php", "restore_export.php"),
             "title" => GetMessage("MAIN_MENU_DUMP_ALT"),
          ),
    (strtoupper($DBType) == "MYSQL"?
       Array(
          "text" => GetMessage("MAIN_MENU_REPAIR_DB"),
          "url" => "repair_db.php?lang=".LANGUAGE_ID,
          "more_url" => array(),
          "title" => GetMessage("MAIN_MENU_REPAIR_DB_ALT"),
       )
    :null
    ),
    ($USER->CanDoOperation('view_event_log')?
       Array(
          "text" => GetMessage("MAIN_MENU_EVENT_LOG"),
          "url" => "event_log.php?lang=".LANGUAGE_ID,
          "more_url" => array(),
          "title" => GetMessage("MAIN_MENU_EVENT_LOG_ALT"),
       )
    :null
          ),
       ),
    );

    If you do not write your own module and there is a need to add an arbitrary option to the administrative menu, the array $aMenuLinks contained in the file /bitrix/admin/.left.menu.php be initialized with the relevant values.

    Sample file /bitrix/admin/.left.menu.php adding an arbitrary menu option:

    <?
    // let us add the menu option “Import of machinery” to the section “Information Blocks"
    $aMenuLinks = Array(
        Array(
            "Import of machinery", 
            "/bitrix/admin/equipment_import.php?lang=ru", 
            Array(), 
            Array(
                "ALT" => "Import of machinery from dbf files", 
                "SECTION_ID" => "iblock",
                "SORT" => "100"
            )
        )
    );
    ?>


    Interaction of Modules

    Modules can interact among them in two ways: explicitly (by direct query) and inwardly (through event system).

    Explicit Interaction

    Explicit interaction means:

    • Module connection using the function CModule::IncludeModule;
    • Direct query of a class method or a module function.

    Example of an explicit interaction:

    <?
    // we connect the module mymodule
    if (CModule::IncludeModule("mymodule"))
    {
        // we execute its method
        CMyModuleClass::DoIt();
    }
    ?>

    Interaction through Events

    Event - is an arbitrary action at the time of execution of which (before or after) all the handlers of such an event are collected and executed one by one.

    The entity of an event permits making modules independent from one another to a maximum degree. The module “knows” nothing about the particulars of functioning of another module, but it can interact with it through the interface of events.

    The events operating procedure is as follows. The module initializing an event must perform the following operations at the place within the code where this event occurs:

    • Collect all the registered handlers using the function GetModuleEvents.
    • Perform them one by one using the function ExecuteModuleEvent processing the values returned by the handlers accordingly.
    In its turn, the module that “wants” to perform any actions to this event must:
    • Register its handler using the function RegisterModuleDependences at the time of installation.
    • Accordingly, this handler function must be available, and you have to make sure that the script where this function is located is connected in the file /bitrix/modules/module ID/include.php.

    Example of Interaction

    The perfect example of such an interaction is the interaction of system modules with the Search module. This module has no information about the data of other modules, their storage and processing particulars. It only provides an interface for data indexing. Any system model to be indexed registers a handler for the event OnReindex. upon installation. Each such handler, in its turn, returns data for the indexing to be used by the Search module for filling its base.

    Sample codes of examples of interaction through events:

    <?
    // handler registration:
    // when the event OnSomeEvent occurs in the module init_module
    // the method CMyModuleClass::Handler of the module handler_module will be called
    
    RegisterModuleDependences(
        "init_module", "OnSomeEvent",
        "handler_module", "CMyModuleClass", "Handler"
        );
    ?>
    <?
    // arbitrary function of the module init_module
    // where the event is generated
    
    function MyFunction()
    {
        // the arbitrary code is located here
        // it represents the event
    
        // after that, registered handlers are collected
        $rsHandlers = GetModuleEvents("anothermodule", "OnSomeEvent");
        while($arHandler = $rsHandlers->Fetch())
        {
            // and executed one by one
            if(!ExecuteModuleEvent($arHandler, $param1, $param2))
            {
                // if the handler returns false,
                // then, for example, we return the phrase "I can't do it..."
                return "I can't do it...";
            }
        }
        return "I have done it!";
    }
    ?>
    <?
    // handler
    
    class CMyModuleClass
    {
        function Handler($param1, $param2)
        {
            if($param1=="delete all")
                return false;
            return true;
        }
    }
    ?>

    Installation and Deletion

    Module Installation

    The module is installed in the administrative interface on the page Settings > System settings > Modules by clicking the button Install. In this case, the method of the DoInstall class will be called with a name coinciding with the module ID. This class must be described in the file /bitrix/modules/ID ìîäóëÿ/install/index.php.

    During module installation, the following steps must be performed without fail:

    • Register the module using the function RegisterModule.
    • If the module has administrative scripts then access scripts must be copied to the catalog /bitrix/admin/ in order to access such administrative scripts.
    • All images used by the module must be copied in the catalog /bitrix/images/module ID/.

    Module Deletion

    The module is uninstalled by clicking the button Remove. In this case, the method of the DoUninstall class will be called with a name coinciding with the module ID. This class must be described in the file /bitrix/modules/module ID/install/index.php.

    During the module uninstallation, the following steps must be performed without fail:

    • Deregister the module using the function UnRegisterModule
    • If the module has administrative scripts then access scripts must be deleted from the catalog /bitrix/admin/.
    • All images used by the module must be deleted from the catalog /bitrix/images/module ID/.

    Module Customization

    Creation of a new module or change of operation of a standard module is rarely required in Bitrix Framework since the majority of tasks are solved using components and their customization.

    Attention! Module customization is a modification of the system kernel with all the consequences. There is a risk that the system will become inoperable after an update or you may lose the right to refer to the technical support service.

    That is why module customization is an operation that is not recommended and almost prohibited. However, there is a technical possibility to do it, and in this chapter we will study an example of a simple customization.


    Example of Changing Module Operation

    Attention! Prior to modifying module operation (i.e. modifying system kernel) you must be sure that there is no other way to resolve your task. All changes that you add will be eliminated upon the next update of the product, and you will have to make them again.

    Let us solve the task of downloading contacts from a corporate portal to Outlook 2003. Please remember that Bitrix24 Self-hosted in a standard package is intended for interaction with Outlook 2007.

    The file /bitrix/modules/intranet/classes/general/ws_contacts.php is responsible for forming and preparing data for Outlook.

    There are two standard problems:

    • Avatars are not downloaded to Outlook 2003 - and an error occurs at once.
    • Sometimes the companies, where the users work, are not downloaded (the field Organization in Outlook).

    We solve the first problem using the function __getRow($arRes, $listName, &$last_change). Commenting on the strings of image attribute setup:

    /*if ($this->bGetImages && $arRes['PERSONAL_PHOTO'] > 0)
          {
             $arImage = CIntranetUtils::InitImage($arRes['PERSONAL_PHOTO'], 100, 100);
    
             $obRow->setAttribute('ows_Attachments', ';#'.($APPLICATION->IsHTTPS() ? 'https://' : 'http://')
                                  .$_SERVER['HTTP_HOST'].$arImage['CACHE']['src'].';#'.CIntranetUtils::makeGUID(md5($arRes['PERSONAL_PHOTO'])).',1;#');
             $obRow->setAttribute('ows_MetaInfo_AttachProps', '<File Photo="-1">'.$arImage['FILE']['FILE_NAME'].'</File>');
          }
          else
          {*/
             $obRow->setAttribute('ows_Attachments', 0);
          //}

    We solve the second problem using the function GetListItemChangesSinceToken($listName, $viewFields = '', $query = '', $rowLimit = 0, $changeToken = ''). In the cycle while while ($arUser = $obUsers->NavNext()) we comment on all strings starting with $arUser['WORK_COMPANY'] (i.e. where the value of this attribute changes).

    Note: In the function GetList the attribute diagram is formed. If before the line: return array('GetListResult' => $data); the array $data is displayed, we will have a chance to see the downloading diagram.

    Programming in Bitrix Framework

    Programming in Bitrix Framework is relatively easy. It is a normal PHP-based programming using API provided by Bitrix Framework.

    The particulars of programming in Bitrix Framework are reviewed in this chapter.

    Golden Rules

    Before starting work in Bitrix Framework you have to understand the main rules that will help you to avoid many mistakes:

    • Collect the statistics on the use of the functionality that you need to adjust;
    • If you want to make any changes to the website operation, perform the following:
      • First formalize your needs on paper, do not start correcting the code immediately.
      • After that’s done, take another look at all the cases when the block you want to modify is used on the website. Make sure everything is logical. Very often some seemingly small changes negatively affect some related functionality that was omitted or forgotten.
      • After you have formalized your need to make changes, check which entities are affected by these requirements.
      • Only after that think as to which means should be used to achieve your goals.
    • Ways to make changes and preferred order of their application:
      • First try to this by editing the template of the website itself and CSS files;
      • If that is impossible, try to do it by way of website page editing;

        Note: This means must be approached with care. If you add code without due care, the components may end up framed in code that should have been placed in the component template and not on a page.

      • If the task cannot be solved using the first options, go to the editing of component templates and CSS files of the component or change data output using the files result_modifier.php and component_epilog.php.
      • Use event handlers, that permit solving a very wide range of tasks.
      • Component customization or development of an own component or a module is the last of possible options in order to achieve a non-standard functionality.
    • Writing HTML code into PHP code in order to change data representation is not recommended. Logic and representation are separated in components 2.0. Logic is the component per se, and representation is the template of the component display. A template is significantly simpler than a component in general. There is no need to change the logic of a component in order to change the particulars of its data output. There can be several representations for the same logic, including representations that depend on the template of the current website. Representation (output template) can be written in any template language that may be connected from PHP. For example, templates can be written in PHP, Smarty, XSL, etc.
    • Own components and templates should be located in their own namespace. When customizing standard components and templates or developing your own, place them in an own namespace. All the changes made in the bitrix space will be eliminated upon system update.
    • Do not access the database directly when working with components. The concept of working with the product involves working with data through the API function. The structure of data may change in each version, but the functions maintain backward compatibility. We strongly discourage using direct database queries because it may breach the integrity of the data and result in the inoperability of the website. Due to the aforementioned, the structure of tables is not disclosed.
    • The kernel code should not be corrected due to the following reasons:
      • The changes made will be eliminated after a system update;
      • By changing the kernel, the license holder loses the right to refer to the technical support service;
      • When a website developer changes the kernel, it may result in the incorrect operation of the system because the kernel is a complex system that requires the operation of all of the modules to be taken into account.
      Product kernel - consists of files located in the directory /bitrix/modules/ and also the files of the system components: /bitrix/components/bitrix/.

    Attention! he files that cannot be accessed directly (they must not be executed by direct access using an address in the browser) must contain the following verification code in the beginning:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>

    These files include all the files that operate inside the product, e.g. website templates, component templates, files .parameters.php and .description.php.


    The file init.php

    init.php - is an optional file within the Bitrix Framework file structure. It is automatically connected in the prologue.

    The file may contain the initialization of event handlers and connection of additional functions that are common for all websites. In this case, it shall be located in the path /bitrix/php_interface/init.php. Each particular website may have its own similar file. In this case, it shall be located in the path /bitrix/php_interface/website ID/init.php. If both files are there, the system will connect them both, but the file /bitrix/php_interface/init.php will be the first.

    Note: The file /bitrix/php_interface/website ID/init.php is not connected in the administrative section because there is no notion of a website. Please also take into account that SITE_ID is equal to the current language and, consequently, a wrong file can get connected.

    A code in init.php should be located according to logical grouping by files and classes.

    The following very general rules should be followed:

    1. init.php contains only file connections. These files are better connected through __autoload. It can be done as follows using the standards means
      CModule::AddAutoloadClasses(
              '', // we do not indicate the name of the module
              array(
                 // key – a class name, value – a path from the website root to the file with the class
                      'CMyClassName1' => '/path/cmyclassname1file.php',
                      'CMyClassName2' => '/path/cmyclassname2file.php',
              )
      );
    2. If the functionality is used only on one of the websites in the system, it should be placed in its own init.php;
    3. Event handlers should be grouped in the same file and accompanied with a detailed note indicating where they are used and what task they solve.

    How to avoid problems during editing init.php without ftp/ssh access

    An error in the file init.php results in the complete inoperability of the website and it is impossible to correct anything without access to the file through ftp/ssh. This may occur in the following cases:

    • The client has a Windows-based hosting, and you have a “gray” IP and ftp will not work.
    • The hosting is Linux-based, but php and ftp work from different users, and the file is unavailable for editing through ftp.
    • The client has its own server that is denying you access through ftp.

    If the access is available only through Web, one of the simplest ways is to place all of your code in an external file and connect it like this:

    if (isset($_GET['noinit']) && !empty($_GET['noinit']))
    {
    	$strNoInit = strval($_GET['noinit']);
    	if ($strNoInit == 'N')
    	{
    		if (isset($_SESSION['NO_INIT']))
    			unset($_SESSION['NO_INIT']);
    	}
    	elseif ($strNoInit == 'Y')
    	{
    		$_SESSION['NO_INIT'] = 'Y';
    	}
    }
    
    if (!(isset($_SESSION['NO_INIT']) && $_SESSION['NO_INIT'] == 'Y'))
    {
    	if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/functions.php"))
    		require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/functions.php");
    }

    Note: The parameter in the address line noinit=Y turns the connection off, noinit=N turns the connection on. Own functionality must be located (in the example) in /bitrix/php_interface/functions.php.

    It is recommended to use an own key name. For example, nomysuperinit, because the training course is in open access, and anyone can get to know this method, including those who have malign purposes.

    With such an approach you will be able to safely edit and make errors in the file functions.php without fear of getting an inoperable website that cannot be recovered without ftp.


    init.php or Own Module?

    A project developer has two ways to use already created solutions: own module or the file init.php. Both options have their advantages and disadvantages.

    init.php. When you have classes that are common for several websites (in case of multiple websites or on the same server), the files containing classes are located in the same folder for the sake of convenience. Later on, symbolic links are created.

    In case of multiple websites, there is one more way: to use already created folders. For example, folders with templates.

    Both in the second and the first ways the code include $_SERVER["DOCUMENT_ROOT"]."/bitrix/templates/..." will lead to the same place for all websites where the files that are common for all projects can be located. In addition, own folder may be created for own classes, and connect classes from there by accessing /bitrix/templates/my_classes/....

    Module. The use of init.php is preferable if you create projects that are sure to be located on the same server throughout the entire existence of such projects, and you want to keep the expenses associated with the support of custom libraries to a minimum. These libraries are better supported by the same person. However, if you plan to use these solutions for other projects, a separate module should be created.

    The module suits more for distributed APIs. In this case, their interface will also have to be supported along with response format and other parameters. Otherwise, an occasional update on one of the websites using the module may be fatal in case of change of interfaces or API response formats. On the one hand, you win; on the other hand, you must create guarantees for all of the websites that use the module API.

    UTF-8 or a National Encoding?

    A website developer always faces the problem: which encoding to choose for the project.

    UTF-8 is considered promising. However, everything has its drawbacks. And the decision to use any encoding only because it is promising, without taking into account many other factors is not correct. The choice will be good only when it completely takes into account all aspects of a specific project. That it is not easy to foresee all of the aspects is another matter.

    We think that the use of UTF-8 is preferable, but it is up to a project developer to decide what to choose.

    How to Switch a Website from the National Encoding to UTF-8

    General procedure:

      1. Re-encode all databases in UTF-8 (you will most likely have to seek assistance from the server administrator).

      2. Re-encode all of the website files into UTF-8 (you can do it yourself).

      3. Add the following lines in the file /bitrix/php_interface/dbconn.php:

      define("BX_UTF", true); 

      4. Add the following lines in the file /.htaccess:

      php_value mbstring.func_overload 2 
      php_value mbstring.internal_encoding UTF-8 

    It is possible to re-encode all website files to UTF-8 (second option) through SSH using the command convmv. For example, convert ISO8859-1 (legacy Western European) filenames to UTF-8:

    convmv -f iso-8859-1 -t utf8 -r --notest ./

    Working with Databases

    Programmers who start using Bitrix Framework often have difficulties understanding database queries. Bitrix Framework code often results in very long queries that are not immediately understandable. Several windows of quite unclear SQL normally scare people who rarely write queries more complex than select * from ... where id=... and who are sure that merging tables by conditions through where is equally efficient as through on.

    But not everything is so easy. Working with database involves the following aspects:

    • Very specific process-oriented issues, like throughput rate of certain data selections;
    • Issues of convenient application of database working techniques in large evolutionary projects based on expanding CMS;
    • Issues of ownership and support cost.

    Why is a Bitrix Framework query so hard to read?

    The system has infoblocks where data structure is created quickly and conveniently. There are standard components that implement the most common things. There is API that permits sending quite arbitrary queries. You work with all this, and your fairly high level logic transforms into a query. Query generator, regardless of its complexity, performs quite monotonous task of translating logic written on a high level, into queries to specific fields of specific tables by conditions.

    High capacity DBMS have clever and very efficient query optimizers. These DBMS will not even permit you to perform bad queries. The vast majority of products installed on Bitrix Framework use MySQL in which the optimizer is rather weak. As a result, we have long queries, limited means to change them maintaining the logic, and a caching mechanism on top of it all.


    Disadvantages of the Situation

    Complex queries are very long and your options to change its structure are very limited. Even if you want to profile and adjust them, you are actually limited to the same rather poor means to query the change offered by Bitrix Framework. You will not be able to:

    • Change the merge order;
    • Set up specific conditions for selections;
    • Create local caching temporary tables.

    Advantages of the Situation

    The advantages turn out to be more important than disadvantages:

    • You do not have to write queries yourself. Creating a project in Bitrix Framework usually involves visual setup of ready-made components, creation of website structure, and design integration. You do not have to customize components for every project, but you do not have to write queries even for customizing.
    • Security. Query wrappers protect from attacks or developer’s nonsense.

    Developer’s Qualification

    Like in any other environment, everything depends on professionalism of work with Bitrix Framework. In this case, proficiency in database operation is not crucial. In the majority of projects the workload on sql (execution time) is insignificant compared to the workload on the processor and the efforts spent on script interpretation. It is not MySQL but PHP that puts on the brakes.

    That is why the correct design of data structure and selection and implementation of links through the system of infoblocks and tables are of crucial importance. Project “brakes” could be eliminated much more properly and efficiently using correct design rather than through the optimization of a query generated by Bitrix Framework following your instructions.

    The use of cache permits to save time on query execution. Proper distribution of logic by components makes it possible working without even accessing them. There is a rule: the home page (generally not the most simple) must not send database queries when the cache is activated.

    A developer must take into account the project specifics and select a proper table type: InnoDB or MyISAM .

    ÐA developer must be able to evaluate the level of tasks. For really serious projects, there are Oracle, MS SQL and My SQL clusterization.


    Database Tools in Bitrix Framework

    Before optimizing projects, use the Performance Monitor tool in order to find bottlenecks on the website.

    The Database check and system optimization tools are available as a part of administrative part of the system. These actions are automated, and developer cannot interfere with them.

    Use infoblocks 2.0. This is an infoblock operating mode that can be activated in the administrative section by a check mark. It makes infoblock’s fields transferred into a separate table. But they have their own specifics that must be learned and taken into account.

    On standard MySQL mechanisms the use of database clusterization is recommended. It can be set up from the administrative part.

    Use the indexes specific for your project. A long query does not always mean a long execution. Take a heavy query from the system and click explain on the console. You will be surprised to see how many indexes and how little round robins are there.

    Working with Database

    Before start working with a database, please keep in mind the following:

    Attention! Direct database access in Bitrix Framework is discouraged. Moreover, if the system tables of Bitrix Framework itself are involved it is strictly prohibited. System API should be used for working with tables because the database physical structure can change, and API operation is not guaranteed.

    For this reason, the names of the tables are not disclosed. But if you assume the responsibility and decide to work directly with the database, take into account that all tables from Bitrix Framework start with b_. Accordingly, your prefixes must be different: it is mandatory that the names of your tables do not interfere with those of Bitrix. It is entirely possible that in new updates a table with the same name as a table created by you will appear (if you use the prefix b_). At best, the update will not be installed.

    Use the methods of the global variable $DB (class CDatabase) to work with own tables.

    Setup of SEF for URLs

    In order to store identifiers of elements/sections of information blocks the field Symbolic Code is most convenient. For example, in the link www.myserver.com/catalog/mobile/nokia_3310/, mobile is the symbol code of the section Mobile telephones, and nokia_3310 is the symbol code of the element located in the section Mobile telephones. The symbol code must be unique, and the system itself checks its uniqueness.

    The variable $_SERVER["REQUEST_URI"] in the error 404 handler must be broken down by parameters. To do so, a number of useful functions are available in PHP:

    For example, links similar to myserver.com/users/ are processed in the file 404.php as follows:

    <?
    if(preg_match("~^/users/([a-z_][a-z0-9_]{2,14})/?$­~i",$_SERVER["REQUEST_URI"],$match))
    {
    header("HTTP/1.1 200 OK");
    //selection by the identifier
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.­php");
    $res = CUser::GetList($O, $B, Array("LOGIN_EQUAL_EXACT"=>$match[1],"ACTIVE"=>"Y"­));
    //$match[1] contains login
    if($arUser = $res->GetNext())
    {
    //user’s data are displayed
    }
    else
    {
    //error: there is no such user
    }
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
    }
    else
    {
    header("HTTP/1.1 404 Not Found");
    //error
    }
    ?>
    

    But fixed check in preg_match not permit to do links similar to www.myserver.com/users/user_login/?r1=banner&r2=com­puterra.com that are very much needed to analyze advertising campaigns. That is why we write the following in the beginning of the file 404.php:

    <?$arURI = parse_url($_SERVER["REQUEST_URI"]);
    $_SERVER["REQUEST_URI"] = $arURI["path"];
    if(!empty($arURI["query"]))
    {
    parse_str($arURI["query"],$par);
    foreach($par as $key => $val)
    {
    global $$key;
    $$key = $val;
    }
    }
    ?>
    

    Examples

    Example 1

    The news similar to /about/news/23.html (link for a printable version /about/news/print_23.html) instead of /about/news/detail.php?ID=23 (/about/news/detail.php?ID=23&print=Y)

    • mod_rewrite
      RewriteEngine On
      RewriteBase /
      RewriteRule ^about/news/([0-9]+).html$ about/news/detail.php?ID=$1
      RewriteRule ^about/news/print_([0-9]+).html$ about/news/detail.php?ID=$1&print=Y
      
    • Error 404 handler
      <?if(preg_match("~^/about/news/(print_)?([0-9]+).html$~",$_SERVER["REQUEST_URI"],$match))
      {
      header("HTTP/1.1 200 OK");
      $_GET["print"] = (strlen($match[1])>0 ? "Y": "");
      $_REQUEST["ID"] = $match[2];
      include($_SERVER["DOCUMENT_ROOT"]."/about/news/detail.php");
      }
      else
      {
      define("ERROR_404", "Y");
      header("HTTP/1.1 404 Not Found");
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("404 - ôàéë íå íàéäåí");
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
      }
      ?>
      

    Additional Information

    How to delete "PHPSESSID=..." from URL?

    In order to get rid of the session identifier in URL, uncomment the following line in /.htaccess:

    php_flag session.use_trans_sid off

    If it brings no result, the parameter value session.use_trans_sid must be changed to Off directly in php.ini on the server.

    Also make sure the value of the parameter session.use_cookies is set as On.


    How to remove question mark from a page URL?

    Follow the following steps:

    • Create the file .htaccess with the following contents in the catalog /news/:
      ErrorDocument 404 /news/404.php
    • Create the file 404.php with the following contents in the catalog /news/:
      <?
      $arrPath = pathinfo($_SERVER["REQUEST_URI"]);
      function initialize_params($url)
      {
      
      if (strpos($url,"?")>0)
      {
      $par = substr($url,strpos($url,"?")+1,strlen($url));
      $arr = explode("#",$par);
      $par = $arr[0];
      $arr1 = explode("&",$par);
      
      foreach ($arr1 as $pair)
      {
      $arr2 = explode("=",$pair);
      global $$arr2[0];
      $$arr2[0] = $arr2[1];
      }
      
      }
      
      }
      
      initialize_params($_SERVER["REQUEST_URI"]);
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
      $arr = explode("?",$arrPath["basename"]);
      $fname = $arr[0];
      
      if (strlen(trim($arrPath["extension"]))>0)
      {
      $arr = explode(".",$fname);
      $NEWS_ID = intval($arr[0]);
      
      if ($NEWS_ID>0)
      {
      $ID = $NEWS_ID;
      $APPLICATION->SetTitle("News Details");
      $sapi = php_sapi_name();
      if ($sapi=="cgi") header("Status: 200 OK"); else header("HTTP/1.1 200 OK");
      require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/iblock/iblock.php");
      CIblock::ShowPanel($IBLOCK_ID, $ID);
      include($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/include/news/news_detail.php"); // interface script which is accessed also 
                                                                                              // in /news/detail.php
      }
      
      }
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog.php");
      ?>

    Language Files

    Language file - is a PHP script storing translations of language phrases to a foreign language. This script consists of the $MESS array that keys are identifiers of language phrases and values are translations to the relevant language.

    Language files are not mandatory.

    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";
    ?>

    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";
    ?>

    Work Example

    • View the entire dictionary file:

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

    Each language has its own set of language files stored in subcatalogs /lang/ (for more details please refer to the documentation system file structure and module file structure).


    Language files are normally used in the administrative scripts of modules or in the components and it defines which of the following functions is used to connect them:

    In order to make searching more convenient and to modify the language phrases further, the page parameter show_lang_files=Y, can be used, which permits you to quickly locate and correct any language phrase using means of the module Localization.

    Language Files in Own Components

    When creating own component the path to the language file must look as follows:

    /bitrix/templates/[website_template|.default]/components/[namespace]/[component_name]/[component_template_name]/lang/[language_code]/template.php

    Where the language code, for example, = lt.

    In this case language files will be connected automatically.


    The following function can be used to connect to the component language messages of another component:

    function IncludeComponentLangFile ($abs_path, $lang = false)
    {
    if ($lang === false) $lang = LANGUAGE_ID;
    
    global $BX_DOC_ROOT;
    
    $filepath = rtrim (preg_replace ("'[\\\\/]+'", "/", $abs_path), "/ ");
    
    if (strpos ($filepath, $BX_DOC_ROOT) !== 0)
    {
    return;
    }
    
    $relative_path = substr ($filepath, strlen ($BX_DOC_ROOT));
    
    if (preg_match ("~^/bitrix/components/([-a-zA-Z0-9_\.%]+)/([-a-zA-Z0-9\._%]+)/templates/([-a-zA-Z0-9\._%]+)/(.*)$~", $relative_path, $matches))
    {
    $lang_path = $BX_DOC_ROOT."/bitrix/components/$matches[1]/$matches[2]/templates/$matches[3]/lang/$lang/$matches[4]";
    __IncludeLang ($lang_path);
    return;
    }
    
    if (preg_match ("~^/bitrix/components/([-a-zA-Z0-9_\.%]+)/([-a-zA-Z0-9\._%]+)/(.*)$~", $relative_path, $matches))
    {
    $lang_path = $BX_DOC_ROOT."/bitrix/components/$matches[1]/$matches[2]/lang/$lang/$matches[3]";
    __IncludeLang ($lang_path);
    return;
    }
    }
    

    Replacement of Language Phrases of a Product

    Sometimes the development of a website requires that some words or phrases be changed in components or modules.

    Let us look into this technique wherein the bottom line is that after a language file is connected, product phrases are replaced with those determined by the developer.

    File path is replaced:

    /bitrix/php_interface/user_lang/<language code>/lang.php

    Note: If php_interface contains no required folders, they must be created.

    The file must determine the elements of the array $MESS in the form $MESS['language file']['phrase code'] = 'new phrase', for example:

    <?
    $MESS["/bitrix/components/bitrix/system.auth.form/templates/.default/lang/en/template.php"]["AUTH_PROFILE"] = "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 link text in the authorization form component; the second line changes the name of the public panel tab; the third line changes the name of the index page of the control panel.

    Important! Problems may occur in file maintenance when a phrase code or language file location are changed.

    JS Library

    When working with JS in Bitrix Framework both a standard library and third party libraries can be used. Third party libraries can be used only after they have been connected.

    An Example of Connection for jQuery

    CJSCore::Init('jquery');

    For different versions of jQuery and third party libraries:

    • At the place where the library is needed
      $APPLICATION->AddHeadScript('http://libraby-host/library_path');
    • Or in the website template
      <script src="http://libraby-host/library_path"></script>

    Examples of JS Code Optimization

    Developers rarely think about the number of hits when making a tool they do not use themselves and for which they do not lease capacities. They leave this for clients’ discretion: “they might buy a server with a higher capacity, and everything will work really fast.” However, a professional developer should be able to see all consequences of their work and find solutions that are appropriate not only for themselves as programmers, but also for the client.

    Where to Search for an Error in the Ajax Handler in POST Queries

    Let us review an example of reduction of the number of hits based on Bitrix24 desktop application.

    The specifics of this tool are that it can make an exorbitant amount of hits. There is no other tool in Bitrix Framework which can make even a half of the messenger’s hits.

    The number of Bitrix24 users grew almost exponentially, and so did the amount of hits. A critical moment arrived when these numbers simply could not be ignored any longer:

    The following problem was revealed during log review: the log contained only the name of a handler, because all the data were sent by POST query to the server. Logging POST queries is not very convenient, that is why another solution was required in order to determine whether these queries were legitimate or stray.

    The solution is very easy and consists in GET tags. The query just has to be executed together with a GET tag: im.ajax.php?GET_HISTORY, im.ajax.php?UPDATE_STATE etc.

    Server queries could be grouped by these tags thus permitting to reveal bottlenecks.

    The most popular tags were determined and run time was optimized. Some of the tags were completely remade to avoid unnecessary server queries (agents were created to send data using the Push & Pull module).

    All these operations permitted to achieve the following values:


    Frequent Errors and Recommendations

    Below we include a short list of frequent errors and recommendations on work with the library.

    • Do not use old libraries utils.js, ajax.js and chttprequest.js.
    • Avoid using BX.findChild and BX.findChildren. These functions are not harmful, but because of their incorrect use and large DOM the amount of iterates grows up to several thousands. The use of samplings by ID or by firstChild, parentNode, nextSibling, or previousSibling is recommended.
    • Avoid BX.loadScript and BX.loadCSS. Their disadvantages are:
      • There is no JS and CSS compression
      • loadScript re-executes the script. In the example below
        BX.loadScript("script.js"); BX.loadScript("script.js");
        the first call will download and execute the script, and the second call will re-execute the script.
      • loadScript loads scripts sequentially (the scripts connected through scrip tag are loaded in parallel), with delays of 50 ms between loads. If you load 5 files it will take you 200 ms at best.
      • Loading CSS requires style recount
      • When using loadScript developers tend to omit the timestamp after file name (my_script.js?12345678). It causes double load and the execution of the script. Also, if the file is changed, the cache is not reset at the client’s system.
      Solution: use the methods AddHeadScript and SetAdditionalCSS.

      One more thing that may seem evident. The browser guarantees the connection and execution of scripts in the same order as they go on a page. Scripts with the attributes async and defer do not fall under this rule, but Bitrix Framework contains no such scripts. There is no need to check an object for existence before use and much less load it through loadScript.

    • Inline CSS is not recommended (we mean link and style tags displayed in body).
    • Avoid making long inlinå scripts. It is better to store the main code in external files. Data from PHP shall be introduced using JSON. A page containing only constructor or Init call with JSON data transmission is a good solution.
    • Use setTimeout carefully. Pretty often there is no reason for timeouts. For example, if a code will not work in a browser, often the timeout is set to “approximately.” It is mainly caused by lack of understanding of the event model.

      Infinite timers should be avoided completely. If they are really needed, consider the option of core_timer.js. It is a singleton for timers.

    • Use “lazy” initialization (load). Create objects, layouts, windows, etc. only when it is really necessary. For example, it makes no sense to create BX.PopupWindow in advance (it inserts new nodes in DOM) until it is really necessary (click on a link or a button). If the interface is rarely used and it is not critical to show or not to show the loading process, it is better to load it through ajax.
    • Use global variables as less as possible. Instead, use local variables with var.
    • Treat global handlers carefully (window, document, document.body). This code is called by each click, each movement of a mouse, and each scrolling change. Do not forget to unbind.
    • Do one appendChild instead of several in a cycle. Also, check out what the DocumentFragment is.
    • Merge and divide CSS/JS. Here, use your common sense. There are components that contain a lot of small scripts and CSS files. It is worth merging them into one file.
      There are also reverse situations. For example, the module you are creating contains a large CSS file which includes almost all styles for its display. It is better than a lot of small ones (separately for table, tool bar, pop-ups, and filters). But a new task has appeared. Now a small block from your module must be added to the live feed. In this case you do not have to drag the entire CSS file from the module to the live feed, it is better to create a small separate file.
    • Create protection from double connection to the page, because the component may be connected to the page more than once.
    • In order to measure performance, run Chrome and Internet Explorer profilers.
    • Check your code in PhpStorm or using similar tools.

    Interface of the Control Panel Toolbar as Seen by a Developer

    Panel Connection

    After authorization on a website, the Control Panel becomes available at the top of the page for a user with the appropriate rights. This panel can be used toî:

    • Manage parameters of the current section;
    • Go to editing of the current page and connectable areas;
    • Add and change the menu of the current section;
    • Set up component parameters;
    • Quickly go to the administrative section of the website;
    • And much more.
    Note: A detailed description of the panel interface is provided in the training course System administration.

    The administrative panel connection code shall be introduced in the service area of the website design template immediately after the <body> tag before the beginning of the first table.

    <?
    $APPLICATION->ShowPanel();
    ?>
    

    Note: The panel will not be displayed for a user with insufficient rights.


    The panel can also be displayed for a specific group or separate users. To do so, use the option Always show toolbar for users in the settings of the Kernel module, tab Settings.

    In this case, the panel will be displayed, but a set of its buttons will depend on the user’s rights.


    Adding Buttons to the Control Panel

    When creating own projects, it may become necessary to create new buttons on the Control Panel. Buttons can be added to the Control Panel as follows:

    <?$APPLICATION->AddPanelButton(
    	Array(
    		"ID" => "Button ID", //Defines the uniqueness of the button
    		"TEXT" => "Name of the button",
    		"TYPE" => "BIG", //BIG – a big button, otherwise it will be a small button
    		"MAIN_SORT" => 100, //sorting index for a group of buttons
    		"SORT" => 10, //sorting inside the group
    		"HREF" => "Goto URL", //or javascript:MyJSFunction())
    		"ICON" => "icon-class", //name of CSS class with a button icon
    		"SRC" => "button icon path",
    		"ALT" => "Prompt text", //old variant
    		"HINT" => array( //tool type of the button
    			"TITLE" => "Tool type header",
    			"TEXT" => "Tool type tex" //HTML is allowed
    		),
    		"HINT_MENU" => array( //Tool type for a context menu button
    			"TITLE" => "Tool type header",
    			"TEXT" => "Tool type text" //HTML is allowed
    		),
    		"MENU" => Array(
    			Array( //array of context menu options
    				"TEXT" => "Name of the option",
    				"TITLE" => "Option prompt",
    				"SORT" => 10, //Option sorting index
    				"ICON" => "", //Option icon
    				"ACTION" => "Javascript code",
    				"SEPARATOR" => true, //determines a separating point
    				"DEFAULT" => true, //default option?
    				"MENU" => Array() //submenu array
    				)
    			)
    		),
    	$bReplace = false //replace the existing button with new data?
    );	
    ?>
    

    There are several options to add a button from:

    • In a component
    • On a page
    • In a website template
    • In the even OnBeforeProlog

    Adding Context Menu

    Use the following code in order to add context menu options to any panel button:

    $APPLICATION -> AddPanelButtonMenu($btnId, $arMenuItem)
    

    where:

    • $btnId is the button identifier;
    • $arMenuItem is the array of options.

    Note: ïthe options can be resorted according to a sorting index using the following structure:
    "RESORT_MENU" => true

    Component Toolbar

    Use the following code in order to display the component toolbar:

    $this->AddIncludeAreaIcons(
    	Array( //array of buttons of the toolbar
    		Array(
    			"ID" => "Button identifier",
    			"TEXT" => "Name of the button of the toolbar",
    			"URL" => "Goto link" //or javascript^MyJSFunction ()
    			"ICON" => "menu-delete", //CSS class with an icon
    			"MENU" => Array(
    				//array of options of a context men
    			),
    			"HINT" => array( //button tool type
    				"TITLE" => "Tool type header",
    				"TEXT" => "Tool type text" //HTML is allowed
    			),
    			"HINT_MENU" => array ( //Tool type of the context menu button
    				"TITLE" => "Tool type header",
    				"TEXT" => "Tool type text" //HTML is allowed
    			),
    			"IN_PARAMS_MENU" => true //show in the context menu
    			"IN_MENU" => true //show in the component submenu
    		)
    	)
    );
    //Is the editing mode on?
    if ($APPLICATION->GetShowIncludeAreas())
    {
    	$this->AddIncludeAreaIcons(Array(
    		//Arrays of the buttons of the toolbar
    	));
    }

    Context Menu of the List Items

    • Set the HTML attribute id for a block tag:
      <div id="<?=$this->GetEditAreaID("area_identifier")?>">
      	<!-- block contents -->
      </div>
    • Determine context menu buttons in compote_epilog.php using the method:
          $APPLICATION->SetEditArea($areaId, $arIcons);

      where:

      • $areaId is an area identifier with the context menu;
      • $arIcons is an array of context menu icons.
    • The method adds a button that opens the indicated URL in a pop-up window:
      $this->AddEditAction(
      	"Area_identifie",
      	"URL of the page to be opened in a pop-up window",
      	"Name of the button in the toolbar",
      	Array(
      		"WINDOW" => array("wight"=>780, "height"=>500),
      		"ICON" => "bx-content-toolbar-edit-icon",
      		"SRC" => "/bitrix/images/myicon.gif"
      	)
      );
    • The method adds the button that deletes an element:
      $this->AddDeleteAction(
      	"Area_identifier",
      	"URL of the page deleting the specified element",
      	"Name of the button",
      	Array(
      		"CONFIRM" => "Do you really want to delete this element?",
      
      	)
      );

    Administrative Pages in the Public Section

    • The method generates JavaScript which opens a URL in a pop-up window:
      $APPLICATION->GetPopupLink(Array(
      	"URL"=> "URL of the page to be opened in a pop-up window",
      	"PARAMS" => Array(
      		"wight" => 780,
      		"height" => 570,
      		"resizable" => true,
      		"min_wight" => 780,
      		"min_height" => 400
      	)
      );
    • The method generates element control and infoblock section buttons:
      CIBlock::GetPanelButtons(
      	$IBLOCK_ID = 0, //infoblock ID
      	$ELEMENT_ID = 0, //infoblock element ID
      	$SECTION_ID = 0, //infoblock section ID
      	$arOptions = Array(
      		"SECTION_BUTTONS" => true, //generate buttons for control of sections
      		"SESSID" => false, //add a link into the authorized token
      		"RETURN_URL" => "",
      		"LABELS" => Array() //button labels; by default they are taken from infoblock settings
      	)
      );

    New Buffering Methods

    Enhanced template buffering methods permit not to use EndViewTarget() any longer because the end of a template automatically terminates buffering.

    Now, there is a standard caching support in components.


    • template.php:
      <?$this->SetViewTarget("sidebar");?>
      
      	<div class="element-filter">
      		<!--filter display -->
      	</div>
      
      <?$this->EndViewTarget();?>
      
      <div class="element-list">
      	<!--list display -->
      </div>
    • header.php:
      <div id="sidebar">
      	<?$APPLICATION->ShowViewContent("sidebar")?>
      </div>

    Methods available in a template (through &this)

    • CBitrixComponentTemplate::SetViewTarget($view, $pos)
    • CBitrixComponentTemplate::EndViewTarget()

    $APPLICATION global object methods

    • Cmain::AddViewContent($view, $content, $pos)
    • Cmain::ShowViewContent($view)

    where:

    $view is the buffered area identifier;

    $content is the buffered content;

    $pos is the sorting of the displayed contents.

    Note: Several buffers may correspond to one $view identifiers. The $pos sorting determines the sequence of the content display.

    Some Theory

    In this chapter we will give you recommendations on writing a PHP source code. Also, we will set standards for code writing in Bitrix Framework.

    Notes on $arParams and $arResult

    $arParams

    $arParams is a variable redefined for a component which consists of the input parameters of a component. In this array, the names of parameters are the keys, and parameter values are the array values.

    Before connecting a component to all parameter values, the function htmlspecialcharsEx is applied. The source values of the parameters are stored in the same array with the same keys but with the prefix ~. For example, $arParams["NAME"] is an input parameter to which the function htmlspecialcharsEx, and $arParams["~NAME"] is a source input parameter.

    The variable $arParams is a reference for a component class member. That is why all the changes in this variable are also reflected on such a class member. In the beginning of the component code input parameters must be checked, non-set parameters must be initialized, and adjustment to a required type must be performed (e.g., IntVal()). All these changes in the input parameters will also be available in the template. I.e. parameters in the template will be already checked and as safe as possible. Duplicating parameter preparation in the component template is not required.


    $arResult

    $arResult is a preset variable for a component to which the component work result is collected for subsequent submission to the template. Before connecting a component file, this variable is initialized by the empty array array().

    The $arResult variable is a reference for a component class member. That is why all changes in this variable are also reflected on such a class member. It means that there is no need to submit this variable to the template, since internal mechanisms of the component class will do it.


    References in PHP

    References in PHP are necessary so that the same data could be accessed using different names. If the variables $arParams and $arResult are somehow changed in the component code, they will be updated and available in the template, too.

    At the same time, the following aspects shall be taken into account:

    • If the following code is written in the component:
      $arParams = & $arSomeArray;
      the variable $arParams will be detached from the component class member and attached to the array $arSomeArray.In this case, further changes in $arParams will not get to the component template.
    • If the following, the code is written in the component:
      unset($arParams);
      it will also break the connection between $arParams and the relevant component class member.

    HTTP POST Queries

    If the option ErrorDocument (ErrorDocument 404 /404.php) is used on the website to process non-existent pages, then upon sending a HTTP POST query to a non-existent page the data of the POST query will be lost. That is why such queries must be sent to the scripts that exist physically.

    One of the ways to organize SEF in components 2.0 consists in using the ErrorDocument option. There is a standard solution for components 2.0 which permits not to worry about the option used for SEF support, be it ErrorDocument or mod_rewrite.

    When writing forms in component templates which send data using the POST method the constant POST_FORM_ACTION_URI must be indicated as action:

    <form method="post" action="<?=POST_FORM_ACTION_URI?>">
    	* * *
    </form>

    In this case, when working with the ErrorDocument option, a POST query will be sent to a script that exists physically, and in other cases – to a current address. It will make no difference for a component as to whether it was called by a POST or a GET query. All the required variables will be set up accordingly.

    Code Writing Rules

    Properly written source code is one of the major factors from which the quality of software stems. In its turn, the crucial factors of the quality source code are readability and comprehensibility. To make your source code readable to other developers, a set or formal rules is required.

    The source code formatting rules must be adhered to throughout the whole project files. If there are multiple projects that may possibly have interconnections, the rules must apply to these projects with no exception.

    Properly written source code is one of the major factors from which the quality of software stems. In its turn, the crucial factors of the quality source code are readability and comprehensibility. To make your source code readable to other developers, a set or formal rules is required.

    The source code formatting rules must be adhered to throughout the whole project files. If there are multiple projects that may possibly have interconnections, the rules must apply to these projects with no exception.

    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.

    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 the name contains an abbreviation, it is better to capitalize only the first letter, not all of them, and write the remaining letters in lower case. I.e. it is better to set the name as getHtmlStatistic and not getHTMLStatistic.


    2.2. Variable Names

    Start with a lowercase character and use uppercase character as separators (camelCase). Variable names can have prefixes if a variable type must be indicated explicitly: ar for arrays, db for data set from database, etc.

    For example: $testCounter, $userPassword.


    2.3. Method Names

    Start with a uppercase character and use uppercase character as separators (Pascal style).

    For example: CountVariable, ChangeUserPassword.


    2.4. Variable Prefixes

    PHP is a loosely typed language. It has only three type groups that are distinguished specifically: scalars, arrays and objects.


    Array names should begin with the ar prefix and start other words with an uppercase character. For example: $arResult, $arModifiedUsers.


    Object names should begin with the ob prefix and start other words with an uppercase character. For example: $obElement, $obUser.


    Objects of the CDBResult type should begin with the db prefix and start other words with an uppercase character. For example: $dbResult.


    Variables of scalar types may be prepended with a prefix only if their type is exact and fixed.

    For example, in the following code:

    $userID = $_REQUEST["var"];
    $userID = IntVal($userID);
    

    the variable $userID has no prefix because its type may change at runtime.


    However, in the code:

    $bFlag = (($aaa > 0)? True : False);

    the variable $bFlag has the b prefix because its type is known and is unlikely to be changed.


    2.5. Class Names

    Class names must begin with the C character. If the class is a part of a module, the next parts of the name should uniquely identify the module. Use uppercase character as separators.

    For example: CIBlockElement, CIBlockType, CSaleAffiliate.


    If a class is a base class that has specializations for different databases, the base class must begin with CAll.

    For example: CAllSaleAffiliate.


    2.6. Class Member Access Control

    Because PHP does not provide any class member access control mechanism, the developers should stick to the following rules.

    • Private member variables and methods which must not be accessed by anyone (neither by the public section calls nor by the other modules), should begin with the two underscores. For example: __CheckEmail, __arData. Such members are never described in the documentation and may be changed or deleted disregarding backward compatibility.
    • Internal member variables and methods which can be accessed by the other modules but must not be accessed by the public section, should begin with one underscore. For example: _CheckEmail, _arData. Such members are not described in the public documentation (but can be described internally), and may be changed or deleted disregarding backward compatibility with prior notification sent to all affected developers.
    • Other methods and variables are public; they should be described in the public documentation and cannot be changed without providing backward compatibility.

    2.7. Constants

    Constants must be in uppercase and begin with the BX_ prefix.

    For example: BX_ROOT, BX_FILE_PERMISSIONS.


    3. Comments

    Put a comment before the class or method declaration to describe the purpose of the latter.

    Avoid obvious comments like:

    $i = $i + 1; // increment i

    Add comments to every public class and method.

    If you divide the code into logical sections, add comments to each section and describe its purpose.

    All comments must be in English.


    4. Idioms


    4.1. General Provisions

    Any programming language has idioms specific to that particular language. An idiom can be a commonly used expression, an iteration technique etc. For example, some of the PHP idioms are the array iteration which is usually written as:

    for ($i = 0, $cnt = count($arArray); $i < $cnt; $i++)
    {
    }
    

    or as:

    foreach ($arArray as $key => $value)
    {
    }
    

    The use of idioms helps other developers skip obvious patterns while concentrating on the important chunks of code, or find the required code fragments by typical patterns (idioms).


    To summarize, use widely used patterns and idioms instead of reinventing the wheel.

    For example, the following code:

    reset($arHashLink);
    while(list($hash, $arData)=each($arHashLink))
    {
    }
    

    should be rewritten as:

    foreach ($arHashLink as $hash => $arData)
    {
    }
    


    4.2. Examples Of Idioms

    Conditional operator idiom "?":

    $res = ($bTrue? "True" : "False");


    5. SQL Queries

    Each of the queries: SELECT, FROM, WHERE, ORDER BY, GROUP BY, HAVING should begin at a new line.

    Use the same new line rule as in PHP code: a new line and a tab.



    Performance

    Web server performance means the amount of pages delivered per time unit. When we speak about performance of a specific website, it comes down to the page opening time (strictly speaking, dependence is not linear, because the server processes several queries in parallel, but it is the page opening time that can be optimized). Website performance depends on the following factors:

    1. Server resources;
    2. Server software settings;
    3. Software platform (SCU);
    4. Applied development on the platform.

    Performance reduction can be experienced at one or more stages. Applied developments mentioned in this list are of great importance. A non-optimized code can bring to naught the advantages of any perfect platform and “hang up” any potent hardware.

    Web Programming Specifics

    One of the differences of web programming from Windows programming (single-user) is that in Windows, all computer resources belong to you for almost unlimited time. In web development you own a limited amount of memory and little time (normally no more than 30-90 seconds).

    The main task of a website (in 99% of the cases) is to quickly return a requested page to the user and release resources for servicing other visitors. It is exactly for resolving this task that such tools as nginx, lighthttpd, caching, eAccelerator, and others exist. For the majority of websites with little traffic these tools available on almost any hosting or in almost any CMS are quite sufficient.

    However, for the project with a higher traffic additional optimization may become necessary.


    A complex and resource-intensive project is a project with 30,000 visits, 500,000 and more database elements, over 40 types of infoblocks, and more than 200 infoblocks proper. Key infoblocks contain 150-200 properties each.

    What makes such projects work:

    • Infoblocks, from which selections can be made using a standard API;
    • Caching.

    Aspects to be taken into account:

    • Large amount of properties may cause problems or minor failures: problems with changing an element property from the administrative part, a filter by value may fail to perform.
    • Working in the administrative part with such a big amount of data is somewhat inconvenient.

    Infoblock design is the most important. Also, special attention should be paid to infoblock handlers and agents. API specifics must be learned and kept in mind. For example, select elements by IBLOCK_CODE or IBLOCK_TYPE is worse than by IBLOCK_ID.

    Complex and extensive projects should be implemented on own servers. In this case, the use of 2 servers is recommended: one for the base and static data and one for cache, separately.

    Project developers’ experience using Bitrix Framework shows that close to 1 million hits on a project is a real value, provided that MySQL is configured properly.

    There is also a kind of a crossroad: database and reliable hard disc drive. If data are not cached, it is worse for the base and better for the disc. I.e. if a very high-capacity server is used for the database and it is configured properly, it can be a good alternative to file cache.

    Note: In order to reduce database load and make the work with system in Bitrix Framework more convenient, different data are stored in the file system. Please refer to the lesson File structure in order to find more information about files and the way they affect the performance.


    Caching During Website Design


    Caching

    Caching - Caching is a technique which permits to cache results of work of rarely updated and resource-intensive parts of code (e.g., those actively working with the database).

    By providing for an intensive use of caching technique at the terms of reference stage of a project it will be possible to keep database load to a minimum. It will permit your project to support significant loads later on.

    Note: The use of caching in a project should be stipulated in the terms of reference in advance, because once the project is launched, the implementation of caching will be significantly more expensive and complex.

    Two classes are created in the system in order to implement caching:

    Caching results are stored as files in the catalog /bitrix/cache/. If the caching time has not expired, a preliminary created cache file will be connected instead of a resource-intensive code.

    The correct use of cache permits to significantly increase general performance of a website. However, it must be taken into account that unreasonable use of cache may result in a serious enlargement of the /bitrix/cache/ catalog.

    Example of HTML Caching

    <?
    // create an object
    $obCache = new CPageCache; 
    
    // caching time - 30 minutes
    $life_time = 30*60; 
    
    // form cache identifier depending on all parameters
    // which may affect the resulting HTML
    $cache_id = $ELEMENT_ID.$IBLOCK_TYPE.$USER->GetUserGroupString(); 
    
    // initialize output buffering
    if($obCache->StartDataCache($life_time, $cache_id, "/")):
    
        // select infoblock element parameters from the base
        if($arIBlockElement = GetIBlockElement($ELEMENT_ID, $IBLOCK_TYPE)):
            echo "<pre>"; print_r($arIBlockElement); echo "</pre>";
        endif;
    
        // write preliminary buffered output in the cache file
        $obCache->EndDataCache(); 
    
    endif;
    ?>

    In this example, the method CPageCache::StartDataCache will check the availability of unexpired and valid cache file. If such file exists, it will be connected and displayed on the screen. Otherwise, buffering will be on. Buffering results will be written in the cache file using the method CPageCache::EndDataCache.

    The first parameter of the method CPageCache::StartDataCache sets up a time interval in seconds during which the cache file will be considered valid and unexpired (time interval is counted from the time when the cache file is created).

    The second parameter indicates the unique identifier of this cache instance. Here, it must be stressed that if any variables can affect the result of the cached code execution, their values must be included in this identifier.

    For example:

    • If the current user authorization can affect the result of the cached code, the result of execution of the function CUser::GetUserGroupString which returns a string with current user group IDs must be added to the identifier.
    • If you use page navigation in the cached code, this identifier must contain the result of work of the function CDBResult::NavStringForCache.
    • If sorting is used, do not forget that the identifier must contain values of the variables which store the field by which sorting is made and the sorting order (e.g., $by.$sort).

    Example of Caching of HTML and PHP Variables

    <?
    // create an object
    $obCache = new CPHPCache; 
    
    // caching time – 30 minutes
    $life_time = 30*60; 
    
    // form cache identifier depending on all parameters
    // which may affect the resulting HTML
    $cache_id = $ELEMENT_ID.$SECTION_ID.$USER->GetUserGroupString(); 
    
    // if cache exists and it has not expired
    if($obCache->InitCache($life_time, $cache_id, "/")):
        // obtain cached variables
        $vars = $obCache->GetVars();
        $SECTION_TITLE = $vars["SECTION_TITLE"];
    else :
        // otherwise access the base
        $arSection = GetIBlockSection($SECTION_ID);
        $SECTION_TITLE = $arSection["NAME"];
    endif;
    
    // add a menu option to the navigation chain
    $APPLICATION->AddChainItem($SECTION_TITLE, $SECTION_URL."SECTION_ID=".$SECTION_ID);
    
    // start output buffering
    if($obCache->StartDataCache()):
    
        // select parameters of the infoblock element from the base
        if($arIBlockElement = GetIBlockElement($ELEMENT_ID, $IBLOCK_TYPE)):
            echo "<pre>"; print_r($arIBlockElement); echo "</pre>";
        endif;
    
        // write preliminary buffered output to the cache file
        // along with the additional variable
        $obCache->EndDataCache(array(
            "SECTION_TITLE" => $SECTION_TITLE
            )); 
    endif;
    ?>

    This example differs from the previous one because in addition to HTML PHP variable $SECTION_TITLE is also cached. The structure and type of cached PHP variables can be arbitrary. The method CPHPCache::InitCache checks the availability of an unexpired and valid cache file. If such a file is found, it is connected, and all the cached variables will be available after using the method CPHPCache::GetVars. The method CPHPCache::EndDataCache, in addition to buffered HTML code, also writes PHP variables to the cache file.

    In order to deactivate caching on one page you have to log in with administrative rights and call this page with the parameter &clear_cache=Y. If, having logged in with administrative rights, you call any page with the parameter &clear_cache_session=Y, the cache will be deactivated for all pages which will be viewed during the session. Cache files can be deleted in the administrative parts on the settings page of the main module.


    Alternative Ways for Cache Storage

    Now that we have made sure that the use of caching technique for a website is needed and caching must be planned when preparing the terms of reference, let us consider the following situation.

    Let us assume that after a certain time your website will become a popular resource. By actively using the caching technique you have secured the database against high load, and it will be able to withstand a three to fivefold overload.

    However, due to the website specifics, you have collected a large volume of cached data (tens of thousands of files) which are inconvenient for use because the load on the disc subsystem has also increased. Also, unfortunately, some errors were made at the developing stage, and cached data are not deleted in full, that is why their volume on the disc keeps on growing.

    There is a solution:


    memcached

    When using memcached , temporary data will be stored in RAM. An inexpensive server with a capacity of several gigabytes can be allocated for cache storage.

    At the same time, outdated data will be forced out automatically. Let us assume that you have allocated 4 GB in memcached for caching. That way you can be sure that cache will not exceed 4 GB, and the data used less frequently will be forced out (LRU algorithm). It is a very convenient and efficient procedure.

    memcached is connected in the file dbconn.php.

    Default settings must be changed to ensure efficient operation of the system. Examples:

    // Memcached with a storage capacity of 2GB, 8 streams, and operation through tcp
    PORT="11211"
    USER="memcached"
    MAXCONN="10240"
    CACHESIZE="2048"
    OPTIONS="-t 8"
    
    // settings in dbconn.php
    define("BX_CACHE_TYPE", "memcache");
    define("BX_CACHE_SID", $_SERVER["DOCUMENT_ROOT"]."#01");
    define("BX_MEMCACHE_HOST", "127.0.0.1");
    define("BX_MEMCACHE_PORT", "11211");
    
    // Memcached with a storage capacity of 2GB, 8 streams, and operation through socket
    PORT="11211"
    USER="bitrix"
    MAXCONN="10240"
    CACHESIZE="2048"
    OPTIONS="-t 8 -s /tmp/memcached.sock"
    
    // íàñòðîéêè â dbconn.php
    define("BX_CACHE_TYPE", "memcache");
    define("BX_CACHE_SID", $_SERVER["DOCUMENT_ROOT"]."#01");
    define("BX_MEMCACHE_HOST", "unix:///tmp/memcached.sock");
    define("BX_MEMCACHE_PORT", "0");
    

    If memcached is used on one computer at the same place where the web server is located, the use of a socket will be faster.

    Links on the Subject:


    Alternative PHP Cache (APC)

    APC is connected in the file dbconn.php:

    // settings in dbconn.php
    define("BX_CACHE_TYPE", "apc");
    define("BX_CACHE_SID", $_SERVER["DOCUMENT_ROOT"]."#01");
    

    For more information about installation and setup of the PHP accelerator, please refer to the documentation at http://php.net/manual/en/book.apc.php.


    Caching in Own Components

    Detailed description of caching in components is described in the lesson Caching in Own Components in the chapter Components.


    Recommendations for Working with Cache

    • Optimize the project so that complexity and amount of queries be minimal if no caching is used. For example, specific indexes may be required for the project in order to simplify queries. Also, sometimes breaking down one complex query by several more simple and easy ones may prove to be the most efficient solution. It will reduce the general load on the database and permit using cache more efficiently.
    • Choose the appropriate caching time. A short time will make the cache update more frequently, and it will create additional (excessive) server load, especially if the data do not change.

      A bigger caching time is preferable. Actuality of data in cache shall be maintained using Tagged caching. In this case cache will be updated only in case of data change, and, accordingly, there will be no excessive server load as when the time is short and when the cache is created upon the expiry of the indicated time period irrespective of the data actuality.

    • When generating the cache identifier only the necessary parameters shall be taken into account; excessive variability shall be avoided.
      An incorrect identifier will result in inefficient caching because the majority of hits will strike outside the cache.

      The example of cache for news, when the activity term of a piece of news will expire soon, and it is sufficient to create cache for the entire day only once:

      // Incorrect ID, because in this case a new cache will be created for each hit.
      // (Provided that we have to select all elements which are active on this day.)
      
      $arFilter = array(
          'IBLOCK_ID' => 19,
          'ACTIVE' => 'Y',
          '>=PROPERTY_end_date' => date("Y-m-d H:i:s")
      );
      
      $CACHE_TIME = 86400;
      $CACHE_ID = "some_key_iblock".implode('_', $arFilter);
      $obCache = new CPHPCache;
      
      if($obCache->InitCache($CACHE_TIME, $CACHE_ID, "/"))
      {
          $arVars = $obCache->GetVars();
      }
      
      // Correct ID, because in this case cache will be created for the entire day.
      // (Provided that we have to select all elements which are active on this day.)
      $arFilter = array(
          'IBLOCK_ID' => 19,
          'ACTIVE' => 'Y',
          '>=PROPERTY_end_date' => date("Y-m-d 00:00:00")
      );
      
      $CACHE_TIME = 86400;
      $CACHE_ID = "some_key_iblock".implode('_', $arFilter);
      $obCache = new CPHPCache;
      
      if($obCache->InitCache($CACHE_TIME, $CACHE_ID, "/"))
      {
          $arVars = $obCache->GetVars();
      }
      

    Optimizing Additional Data Selection

    The Use of the GetList Method

    Quite often many developers use the query CIBlockElement::GetById in their projects. It is a simple and convenient method for when a value of fields of an infoblock element is needed. But this method returns all fields and all properties of an element. In case of an infoblock with a big amount of properties and intense traffic on a website this simple query will result in performance reduction. And if there are several dozens of such queries in various result_modifier of different components, it turns out that, in aggregate, despite of caching, such selections create peak loads at the time of cache update.

    If a value of a specific field or fields (and not all fields and properties) is needed, it is better to use GetList indicating the specific field of an element.

    Note: GetByID invokes GetList inside. The difference is that GetByID in any case returns all the fields and properties, and in GetList the list of returned fields can be limited with a filter.

    Also, the GetList methods permit you to select several entries at once, while GetByID permits to select just one entry.

    The sole advantage of GetById over GetList from the developer’s point of view is that GetById can be just called. Direct use of GetList requires certain work with code (creation of an array of parameters).

    Important Examples of Performance Enhancement

    Important! Multiple same-type queries create a big load. The amount of queries should be reduced whenever possible.

    1. Let us consider an example when for some elements of one infoblock (books’ authors) additional properties from another infoblock (information about authors) must be obtained, for example, by changing the template or result modifier.

      In the majority of cases, it is made in a cycle, for example:

       // incorrect sampling option, an additional query is made for each element
      foreach($arResult['ITEMS'] as $ikey => ival)
      {
          $avtorID = intval($ival['PROPERTIES']['AVTOR']['VALUE']);
          if($avtorID > 0)
          {
              $rs = CIBlockElement::GetByID($avtorID);
              while($ar = $rs->GetNext())
              {
                  $arResulr['ITEMS'][$ikey]['AVTOR_INFO'][] = $ar;
              }
          }
      }
      

      Such cycle creates multiple queries and it reduces performance.

      The correct option is to use one query to receive data for the elements needed:

      // correct option, information on authors is selected in one query, and only required information is included
      $avtorID = array();
      
      foreach($arResult['ITEMS'] as $ikey => ival)
      {
          $aID = intval($ival['PROPERTIES']['AVTOR']['VALUE']);
          if($aID > 0)
          {
              $avtorID[] = $aID;
          }
      }
      
      $avtorID = array_unique($avtorID);
      
      
      $rs = CIBlockElement::GetList(
          array('ID' => 'ASC'),
          array(
              'IBLOCK_ID' => XX,
              'ID' => $avtorID,
              'ACTIVE' => 'Y'
          ),
          false,
          false,
          array('ID', 'NAME', 'PREVIEW_PICTURE')
      
      );
      while($ar = $rs->GetNext())
      {
          $arResulr['AVTOR_INFO'][$ar['ID']] = $ar;
      }
      

    2. If we can change the main API query, for example, using own component, the data on authors from the examples shown above can be obtained at once:
      // correct option,
      // information on books and related information on authors is selected in one query
      $rs = CIBlockElement::GetList(
          array('ID' => 'ASC'),
          array(
              'IBLOCK_ID' => YY,
              'ACTIVE' => 'Y'
          ),
          false,
          false,
          array(
              'ID',
              'NAME',
              'PREVIEW_PICTURE',
              'PREVIEW_TEXT',
              'PROPERTY_AVTOR.NAME',
              'PROPERTY_AVTOR.PREVIEW_PICTURE',
          )
      
      );
      while($ar = $rs->GetNext())
      {
          $arResulr['ITEMS'][] = $ar;
      }
      

    3. Use of a more appropriate code which reduces the amount of queries.

      Example: It is necessary to check for a list of specific users regarding whether or not they are in a given group.

      Standard code:

      foreach ($arSomeUser as $arUser)
      {
          $arGroups = CUser::GetUserGroup($arUser['ID']);
          if(in_array(5, $arGroups))
          {
              // code
          }
      }
      

      In this case, the amount of queries will be equal to the amount of users on the list + one for selection of required users from among all possible users (the very first query).

      If there are not many users in the group, the following approach will be more appropriate:

      $rsUser = $arUser->GetList(($by="ID"), ($order="desc"), array('ACTIVE' => 'Y', 'GROUPS_ID' => 5 ....))
      while($arUser = $rsUser->GetNext())
      {
              // code
      }
      

    Selecting Only Required Data and Their Storing in Cache

    The main idea of optimization in terms of the amount of information and cache consists in selecting and storing in cache of only required data and not all the data.

    Selecting all the data may require more time and significantly increase the cache size.


    Important Examples of Performance Enhancement

    1. Some API methods may generate excessive information. It is recommended that queries be limited to required data only early on.

      Example:

      $rs = CIBlockResult::GetNext(true, false);
      // setting the parameter false permits limiting the selection to the transformed values of fields only.
      $res = CIBlock::GetList(array(), array('TYPE'=>'catalog', 'SITE_ID'=>SITE_ID, 'ACTIVE'=>'Y'), false);
    2. Sometimes the method Fetch works faster than GetNext, but it does not make the data safe.
    3. Caching of required data only.

      In the component itself the result of its operation $arResult shall be limited to the required data only.

      For example, a component must display the element identifier and its name, and then only this element identifier and its name must be brought to cache:

      $this->SetResultCacheKeys(array("ID", "NAME"));

      If you perform any operations in result_modifier.php in order to transfer their result to component_epilog.php, keys in SetResultCacheKeys will also have to be installed. In this case, the result of the operations will also be cached.

      foreach ($sAr as $key => $arElement)
      {
      $this->__component->arResult["IDS"][] = $arElement["ID"];
      }
      $this->SetResultCacheKeys(array("IDS"))

      Note: If no keys are installed in SetResultCacheKeys, then, by default, all the results of $arResult will be cached.

      Links on the Subject:


    4. Cache size optimization.

      If, for example, the size of the cache file exceeds 1MB, excessive caching might be under way. In this case, cached data should be analyzed. The more the cache file size is, the less efficient it is.

    5. Limiting the volume of requested information.

      You can also enhance the performance by displaying information in parts.

      Displaying all information at once, for example, list of news for the entire year, is not recommended. In most cases, it is not needed and extremely inefficient. Displaying just the latest news of the day is enough.

      The system has several methods for limiting data selection by quantity. E.g., using a page navigation for the list of news, the selection can be limited to, say, 20 elements per page, and if the user goes to the next page, the next 20 elements will be requested and displayed.

      Example:

      The parameter nPageSize=20 of the array arNavStartParams permits displaying 20 elements, but it makes 2 database queries:

      1. "COUNT" - obtaining the number of elements
      2. "SELECT" - data selection proper
      

      while the parameter nTopCount=4 of the same array creates query 1-n as follows:

      SELECT * FROM tbl LIMIT 4;  
      

      The use nTopCount is much more efficient, because it makes no additional query in order to obtain selection entries.

    6. If own component is being created and queries will be needed to obtain different data, it is recommended that an option should be introduced in the component settings which will determine the data to be obtained. This way, the data that are unnecessary at this very moment will not be requested each time.

    Queries and Methods

    Query Optimization Example

    Always keep queries to a minimum. For example, if the cycle makes a query to the IB element, it may be the time to think about minimization. Yes, it will take time, but your clients will appreciate it.

    Incorrect:

    foreach($arResult["ORDERS"] as $key => $val)
    {
       foreach($val["BASKET_ITEMS"] as $vvval)
       {
          $rsEls = CIBlockElement::GetGetByID();
       }
    }

    Correct:

    $arIDs = array();
    foreach($arResult["ORDERS"] as $key => $val)
      {
        foreach($val["BASKET_ITEMS"] as $vvval)
          {
            $arIDs[] = $vvval["PRODUCT_ID"];
          }
      }
    if(!empty($arIDs))
    {
        $rsEls = CIBlockElement::GetList(array(), array("ID" => $arIDs));
        ...
    }
    
    foreach($arResult["ORDERS"] as $key => $val)
    {
       foreach($val["BASKET_ITEMS"] as $vvval)
       {
          $arIDs[] = $vvval["PRODUCT_ID"];
       }
    }
    
    $rsEls = CIBlockElement::GetList(array(), array("ID" => $arIDs));
    ....
    
    foreach($arResult["ORDERS"] as $key => $val)
    {
       foreach($val["BASKET_ITEMS"] as $vvval)
       {
          //your code
       }
    }

    You actually make one query instead of tens or even hundreds.

    Special Methods

    If there is a special method for any change in the database, this method should be used instead of a more general method of database change.

    A good example is the module of the e-store and work with an order: the payment order toggle can be changed using CSaleOrder::Update or CSaleOrder::PayOrder. PayOrder is preferable because it will invoke the appropriate handlers.

    Even if you have to change multiple fields (of the same order) and payment toggle, first make a change using PayOrder and then update the remaining fields.

    Recommendations

    Do not forget that the managed cache of infoblocks is cleared only if CIBlockElement::Update is called. For example, if properties are changed using CIBlockElement::SetPropertyValueCode, cache will not be cleared. After using CIBlockElement::SetPropertyValueCode() the following code shall be executed:

    if(defined('BX_COMP_MANAGED_CACHE'))
       $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_'.$arParams['IBLOCK_ID']);

    Security

    In this chapter, we will review some security issues which should be taken into account for programming in Bitrix Framework.



    Sanitizer

    Sanitizer is a tool that analyzes the html code introduced by a user. The main task of the sanitizer consists in preventing the implementation/display of a potentially hazardous code in HTML.

    The sanitizer comes handy where the user introduces arbitrary html. E.g., in a visual editor or when copying a text from MS Word. In addition to the control of the introduced code, the sanitizer also partially monitors layout validity. In particular, it closes unclosed tags.

    How to Filter a Text

    If a text (containing HTML tags) typed by the user must be filtered from undesirable HTML tags using the sanitizer, it can be achieved as follows:

    $Sanitizer = new CBXSanitizer;
    
    $Sanitizer->AddTags( array (
                      'a' = > array('href','id','style','alt