'pull' => Array(
'value' => array(
'path_to_listener' => "http://#DOMAIN#/bitrix/sub/";,
'path_to_listener_secure' => "https://#DOMAIN#/bitrix/sub/";,
'path_to_modern_listener' => "http://#DOMAIN#/bitrix/sub/";,
'path_to_modern_listener_secure' => "https://#DOMAIN#/bitrix/sub/";,
'path_to_mobile_listener' => "http://#DOMAIN#:8893/bitrix/sub/";,
'path_to_mobile_listener_secure' => "https://#DOMAIN#:8894/bitrix/sub/";,
'path_to_websocket' => "ws://#DOMAIN#/bitrix/subws/",
'path_to_websocket_secure' => "wss://#DOMAIN#/bitrix/subws/",
'path_to_publish' => 'http://127.0.0.1:8895/bitrix/pub/',
'nginx_version' => '3',
'nginx_command_per_hit' => '100',
'nginx' => 'Y',
'nginx_headers' => 'N',
'push' => 'Y',
'websocket' => 'Y',
'signature_key' => '1111111111',
'signature_algo' => 'sha1',
'guest' => 'N',
),
),
Parameter | Value |
path_to_listener | path to connect to server (http) to receive commands |
path_to_listener_secure | path to connect to server (https) to receive commands |
path_to_modern_listener | path to connect to server for modern web browsers (http) to receive command |
path_to_modern_listener_secure | path to connect to server for modern browsers (https) to receive commands |
path_to_websocket | path to connect to server for modern browsers with socket support (http) to receive commands |
path_to_websocket_secure | path to connect to server for modern browsers with web-socket support (https) to receive commands |
path_to_publish | path to publish commands from the server |
path_to_publish_web | path to publish commands from the web browser (http) |
path_to_publish_web_secure | path to publish commands from the web browser (https) |
nginx_version | queue server version |
nginx_command_per_hit | number of channels to his which the same data will be sent per hit |
nginx | enables the queue server |
nginx_headers | allows to send special headers |
push | enables the queue server |
websocket | activates the web-sockets |
signature_key | secret signature key for messages |
signature_algo | signature encryption algorithm |
guest | enables module active status for guests |
enable_protobuf | enables new format for messages |
limit_max_payload | maximum size of a message in bytes |
limit_max_messages_per_request | maximum number of messages per hit |
limit_max_channels_per_request | maximum number of channels per hit |
|
Http_client_options section
This section specifies options by default for the Bitrix\Main\Web\HttpClient.
[ICO_NEW data-adding-timestamp="1706700990"]
Starting from main 23.0.0 HttpClient now supports PSR-18. In addition to PSR, client works in legacy mode, supports queues with asynchronous queries and CURL.
[/ICO_NEW]
Parameter | Value |
redirect | true by default, execute redirect |
redirectMax | maximum number of such redirects (by default: 5) |
waitResponse | if true - wait for response (by default), otherwise - return the response immediately |
socketTimeout | response timeout period in seconds (by default: 30) |
streamTimeout | stream timeout in seconds (by default: 60) |
version | version http - 1.0 or 1.1 (by default: 1.0) |
proxyHost / proxyPort / proxyUser / proxyPassword | parameter group to setup a proxy |
compress | if true, sends Accept-Encoding: gzip |
charset | encoding for the object body (is used in the header field for the Content-Type request for POST and PUT) |
disableSslVerification | if true, ssl certificates verification will not be performed |
|
Config example:
'http_client_options' =>
array (
'value' =>
array (
'redirect' => true,//execute redirects, if required
'redirectMax' => 10,//but not more than 10
'version' => '1.1'//work via the http 1.1 protocol
),
'readonly' => false,
),
You can check if your settings are correct as follows:
use Bitrix\Main\Config\Configuration;
print_r(Configuration::getValue("http_client_options"));
Your array should be displayed.
services section
This section is intended for registering services. Find more details in the lesson [ds]Service Locator[/ds][di]Service locator is a design template for convenient handling of application services. Instead of creating specific services directly (via new) service locator is responsible for creating and finding services.
Learn more ...[/di]
Connection to Redis, Memcache
Add a titled connection into connections section to create a connection inside bitrix/.settings.php
settings file.
Redis
Ensure that you have installed Redis extension for working via PHP.
Standard connection:
'connections' => [
'value' => [
'default' => [
'className' => \Bitrix\Main\DB\MysqliConnection::class,
//... config for existing database connection
],
'custom.redis' => [
'className' => \Bitrix\Main\Data\RedisConnection::class,
'port' => 6379,
'host' => '127.0.0.1',
'serializer' => \Redis::SERIALIZER_IGBINARY,
],
'custom2.redis' => [
'className' => \Bitrix\Main\Data\RedisConnection::class,
'port' => 6379,
'host' => '127.0.0.4',
'serializer' => \Redis::SERIALIZER_IGBINARY,
],
],
'readonly' => true,
]
You can find additional information about serializer config variants in the official documentation.
Cluster
When creating cluster from redis servers, just add servers config.
'connections' => [
'value' => [
'default' => [
'className' => \Bitrix\Main\DB\MysqliConnection::class,
//... config for existing database connection
],
'custom.redis' => [
'className' => \Bitrix\Main\Data\RedisConnection::class,
'servers' => [
[
'port' => 6379,
'host' => '127.0.0.1',
],
[
'port' => 6379,
'host' => '127.0.0.2',
],
[
'port' => 6379,
'host' => '127.0.0.3',
],
],
'serializer' => \Redis::SERIALIZER_IGBINARY,
'persistent' => false,
'failover' => \RedisCluster::FAILOVER_DISTRIBUTE,
'timeout' => null,
'read_timeout' => null,
],
],
'readonly' => true,
]
Additional information about configurations for variants serializer, persistent, failover, timeout, read_timeout can be found in the official documentation.
Use
To get a connection instance, you may query the connection name using the method \Bitrix\Main\Application::getConnection.
/** @var \Redis $redisConnection **/
$redisConnection = \Bitrix\Main\Application::getConnection('custom.redis')->getResource();
$redisConnection->setnx('foo', 'bar');
Memcache
Ensure that you have installed Memcache extension for working via PHP.
Standard connection
'connections' => [
'value' => [
'default' => [
'className' => \Bitrix\Main\DB\MysqliConnection::class,
//... config for existing database connection
],
'custom.memcache' => [
'className' => \Bitrix\Main\Data\MemcacheConnection::class,
'port' => 11211,
'host' => '127.0.0.1',
],
'custom42.memcache' => [
'className' => \Bitrix\Main\Data\MemcacheConnection::class,
'port' => 6379,
'host' => '127.0.0.4',
],
],
'readonly' => true,
]
Cluster
When creating cluster from memcache servers, just add servers config.
'connections' => [
'value' => [
'default' => [
'className' => \Bitrix\Main\DB\MysqliConnection::class,
//... config for existing database connection
],
'custom.memcache' => [
'className' => \Bitrix\Main\Data\MemcacheConnection::class,
'servers' => [
[
'port' => 11211,
'host' => '127.0.0.1',
'weight' => 1, //read more about weight config in the memcahe documenation
],
[
'port' => 11211,
'host' => '127.0.0.2',
'weight' => 1, //read more about weight config in the memcahe documenation
],
],
],
],
'readonly' => true,
]
Use
To get a connection instance, query connection name using the method \Bitrix\Main\Application::getConnection.
/** @var \Memcache $memcacheConnection **/
$memcacheConnection = \Bitrix\Main\Application::getConnection('custom.memcache')->getResource();
$memcacheConnection->set('foo', 'bar');
SMTP server local settings
Bitrix24 On-premise editions Main module version 21.900.0 has a new SMTP option allowing to organize dedicated email streams.
General information
How emails are sent fr om site/instance
For Bitrix24 On-premise, user calls a global function bxmail, triggering standard PHP mail function, wrapping an email with headers. After that, mail function queries your internal infrastructure (depending on your [ds]configuration[/ds][di]
Bitrix Framework requires setup for sending and receiving emails. There are three options for sending emails:
- via local sendmail or postfix (when site uses Linux);
- via third-party SMTP server without authentication (for Windows);
- via third-party server with authentication by replacing email sender function.[/di]: postfix, sendmail or any custom solution, operating based on queues, sending all emails via mail function).
All emails (email campaigns and single emails, CRM emails, password restoration emails, workflows, etc.) are included into a single stream.
Using new local SMTP server settings you can configure dedicated streams as per custom requirements.
Advantages of sender's SMTP server
- dedicated streams;
- use of [dw]aliases[/dw][di]
Aliases are additional mailbox names. They are used to assign additional names to a single mailbox located at your domain. For example, you have a mailbox mysite@example.com. You can add an alias marketing<mysite@example.com> for marketing department. After this action, users receiving an email from marketing department will see only marketing as a sender.
When users respond to this email, their reply is sent to an actual mailbox mysite@example.com.
[/di] (alias);
- simple setup;
- debugging option;
- option to send on hits;
- option to maintain open connection (useful for bulk mail).
Enabling SMTP server
To enable sender's SMTP server in Bitrix24 On-premise editions, you need to edit the file [ds]/bitrix/.settings.php[/ds][di]
Bitrix Framework has a variety of kernel/core settings that do not have visual UI for editing. This is due to updated settings or an error can easily cause the complete system to malfunction (database connection settings, cache settings and etc.).
Learn more...[/di], by adding the corresponding code:
return [
//…
'smtp' => [
'value' => [
'enabled' => true,
'debug' => true, //optional
'log_file' => '/var/mailer.log' //optional
]
]
];
Important! Extra caution is advised when editing the .settings.php file: a single error can easily cause the complete system to malfunction.
Settings parameters:
- enabled – enabling the SMTP server for the sender;
- debug – optional parameter (disabled by default). Enable it if you want to see the complete interaction process with SMTP servers;
- log_file – optional parameter. Can indicate a file for collection of logs, by entering full path to file (this file requires access). By default, project/site directory already contains the file mailer.log with all logs incoming logs.
SMTP connection setup
SMTP connection can be configured by two methods:
Are the settings described above connected with SMTP settings at the Bitrix24 Virtual Appliance?
SMTP server settings described in this lesson are not associated with [ds]BitrixVM SMTP settings[/ds][di]
Complete the following actions to configure SMTP client:
1. Go to the main menu 6. Configure pool sites > 4. Change e-mail settings on site and enter email host name
Learn more...[/di].
Example: BitrixVM site has a configured Gmail [ds]via BitrixVM menu[/ds][di]
This lesson contains some examples of email services employed at BitrixVM.
Learn more...[/di]. Add new SMTP connection for Yahoo.com via site administrative section. Now, the From field in a new email or email campaign has a selection of two email services: Gmail and Yahoo. Depending on which service is selected, system sends emails either using Bitrix Virtual Appliance or Bitrix24 core.
Can there be conflicting email duplication in case SMTP has been configured both locally and on the virtual appliance?
No, settings won't clash or emails won't duplicate. When sending an email the system checks, if there is an active smtp parameter in the file /bitrix/.settings.php
:
- if such such parameter exists, email will be sent via local SMTP;
- if such parameter doesn't exist, an email will be sent using server functionality via [ds]msmtp[/ds][di]For SMTP client setup, execute the following:
1. At the main menu, go to 6. Configure pool sites > 4. Change e-mail settings on site and enter the host name to configure email dispatch
Learn more...[/di].
Name Spaces
Name spaces
The notion of name spaces permits giving system elements clearer names, getting rid of numerous name prefixes, and also avoiding potential conflicts. All classes delivered in a standard installation package must be located in the Bitrix name space that does not overlap either with PHP or with partner implementations. Each standard module determines its subspace in the Bitrix name space which coincides with the module name. For example, for the forum module Bitrix\Forum will be the name space, and for the main module – Bitrix\Main.
Note. For partner classes, the namespace can be as follows:
namespace Asd\Metrika;
class CountersTable extends Entity\DataManager
{
....
This means that this class (in /lib/
) belongs to the module asd.metrika and it can be addressed as follows (after the indicated module is connected):
\Asd\Metrika\CountersTable::update();
The class itself is located in the file asd.metrika/lib/counters.php
.
If necessary, a module may organize subspaces inside its name space. For example, Bitrix\Main\IO, Bitrix\Forum\Somename\Somename2. But this option should be used only if it is justified for the organization of a correct architecture of this module.
Naming Rules
- Name spaces must be named in “UpperCamelCase.”
- Names cannot contain any symbols but for Latin letters.
- Class name must be a noun. Unnecessary acronyms and abbreviations should be avoided.
Examples:
namespace Bitrix\Main\Localization;
namespace Bitrix\Main\Entity\Validator;
Abbreviations that are not generally accepted (in Bitrix Framework) cannot be used.
Attention! The examples that are provided does do not mention name spaces. It make the text more readable and easier to comprehend. Prior using the examples provided in the documentation, a name space must be added.
It ca be done as follows:
- By using PHPdoc;
- By using IDE;
- additionally, the documentation context usually clarifies which class is described.
Full address line can be abridged. Instead \Bitrix\Main\Class::Function()
you can specify Main\Class::Function()
.
Synonyms also can be used instead of long name spaces. To do it, use
is inserted. For example, the following long structure is available:
\Bitrix\Main\Localization\Loc::getMessage("NAME");
To abridge it, declare a synonym at the start of the file and use an abridged variant of the call afterwards:
use \Bitrix\Main\Localization\Loc;
...
Loc::getMessage("NAME");
Related links:
Modules in D7
System modules (both standard and downloaded from Bitrix24 Marketplace) are located in the bitrix/modules
system folder. User modules can be located in the folder /local/modules
(Third-party developer modules may be located in different folders)]. Such difference allows developers to conveniently organize version control for their development products and maintain continuous updates using the standard update system.
Module's API (classes, logic) are located in the module's subfolder [dw]/lib[/dw][di]This folder is optional if your class doesn't have its own methods.[/di]
. For example, Main module path can be as follows: bitrix/modules/main/lib
. The same folder is used for connecting API when specific rules are satisfied:
- Class files must be titled in lower case, with class name equal to file name. For example:
/lib/myclass.php
.
- Files must contain a correct namespace. Example: when module is connected as follows:
Loader::includeModule('company.shotname');
the class must have a namespace: namespace Company\Shotname.
- Corresponding method must be connected where such module classes are used in the Control Panel.
Exceptions
The new core uses the mechanism of exceptions.
Exception case (when an exception may be given) is an atypical situation when it makes no sense to continue the performance of the basic algorithm.
Examples
If a user has sent a form with an empty Name field, it is not an exception case. It is a regular expected case that must be handled appropriately.
But if in case of API method invocation in order to change an infoblock element an empty element id was specified, it is an exception case. It is unexpected and it makes no sense to continue changing the element.
If the method is waiting for the user id and you transmit a line, it is an exception, because the method does not know what to do with the line in this case.
If the GetList method accepts the timestamp filter, and developer has written “tymestamp,” it will be an exception.
Exception Hierarchy
Available exceptions have a hierarchy. It handles exceptions, shows the activated exceptions, which allows to take appropriate actions. The general hierarchy logic is as follows:
\Exception
Bitrix\Main\SystemException - basic class of all system exceptions
Bitrix\Main\IO\IoException - basic class of all exceptions of file input-output
Bitrix\Main\IO\FileDeleteException - exception in case of file deletion
Bitrix\Main\IO\FileNotFoundException - absence of a required file
Bitrix\Main\IO\FileOpenException - exception in case of file opening
Bitrix\Main\IO\InvalidPathException - invalid path
Bitrix\Main\IO\FileNotOpenedException - file not opened
Bitrix\Main\Config\ConfigurationException - configuration error
Bitrix\Main\Security\SecurityException - security error
Bitrix\Main\Security\Sign\BadSignatureException - signature error exception.
Bitrix\Main\ArgumentException - basic class of exceptions associated with incoming parameters of methods
Bitrix\Main\ArgumentNullException - parameter must not be left empty
Bitrix\Main\ArgumentOutOfRangeException - parameter outside a permitted range
Bitrix\Main\ArgumentTypeException - inadmissible type parameter
Bitrix\Main\DB\DbException - basic class for database exceptions
Bitrix\Main\DB\ConnectionException - exception during connection
Bitrix\Main\DB\SqlException - exception during performance of a query
Bitrix\Main\NotSupportedException - is invoked if the functionality is not supported
Bitrix\Main\NotImplementedException - is invoked if the functionality must be supported but is not implemented so far
Bitrix\Main\ObjectPropertyException - is invoked when the object properties are invalid
Bitrix\Main\ObjectNotFoundException - is invoked when object does not exist
Bitrix\Main\ObjectException - is invoked if object cannot be created.
Bitrix\Main\LoaderException - exception in loader
Bitrix\Main\SystemException
is the basic class for all system exceptions; all the rest of exceptions are inherited from it. This class re-defines the constructor for the \Exception
system class. If the system class receives the message and error code on input:
<?php
public function __construct($message = null, $code = 0, Exception $previous = null);
the constructor Main\SystemException
additionally receives the file with a thrown exception and a number of string:
<?php
public function __construct($message = "", $code = 0, $file = "", $line = 0, \Exception $previous = null);
Thrown exception must have the most suitable type possible.
If your method creates an exception, it must be described in the method phpDoc.
In Bitrix Framework, it is done as follows:
/**
* Searches connection parameters (type, host, db, login and password) by connection name
*
* @param string $name Connection name
* @return array|null
* @throws \Bitrix\Main\ArgumentTypeException
* @throws \Bitrix\Main\ArgumentNullException
*/
protected function getConnectionParameters($name) {}
Ignoring exceptions
Sometimes, an occurred error must not interrupt execution of a script. Such situation can be exemplified by the operation of CDN module administration page.
If the CDN module is enabled, the traffic data is displayed at the top of the page. Its code looks as follows:
$cdn_config = CBitrixCloudCDNConfig::getInstance()->loadFromOptions();
$APPLICATION->SetTitle(GetMessage("BCL_TITLE"));
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_after.php");
if (is_object($message))
echo $message->Show();
if (CBitrixCloudCDN::IsActive())
{
try
{
if ($cdn_config->getQuota()->isExpired())
$cdn_config->updateQuota();
$cdn_quota = $cdn_config->getQuota();
if ($cdn_quota->getAllowedSize() > 0.0 || $cdn_quota->getTrafficSize() > 0.0)
{
CAdminMessage::ShowMessage(array(
"TYPE" => "PROGRESS",
"DETAILS" => ''.GetMessage("BCL_CDN_USAGE", array(
"#TRAFFIC#" => CFile::FormatSize($cdn_quota->getTrafficSize()),
"#ALLOWED#" => CFile::FormatSize($cdn_quota->getAllowedSize()),
)).'
#PROGRESS_BAR#',
"HTML" => true,
"PROGRESS_TOTAL" => $cdn_quota->getAllowedSize(),
"PROGRESS_VALUE" => $cdn_quota->getTrafficSize(),
));
}
}
catch (Exception $e)
{
CAdminMessage::ShowMessage($e->getMessage());
}
}
The code above shows that if CDN is active, the progress bar with the displayed traffic data is created. However, if an error occurs during execution of this code, an exception will be thrown. This exception will be caught, because all the code is located in try
and the branch will be executed after catch
, where the error message is displayed via the standard function. The script execution will not be interrupted.
Code Writing Rules
The quality of a program begins with the quality of the source code. The fundamental factor for quality of source code is its readability and clarity. Formalized rules are necessary to write code which will be readable and understandable.
The rules for formatting code must be uniform throughout the entire project. It is highly desirable for the rules to be similar from project to project.
Note: The code formatting rules for core
D7 are somewhat different from the code formatting rules for
old core.
1. Source Code Formatting
1.1. Text Structure Rules
1.1.1. Line Length
Avoid typing lines whose length exceeds 120 characters. If a line spans beyond that limit, use the line wrapping rules described below.
1.1.2. Line Wrapping Rules
If a line length exceeds 120 characters, the following wrapping rules apply:
- wrap lines after the comma or before the operator;
- the wrapped line must be indented by one tab;
- use UNIX line ends.
Example 1: The code
$arAuthResult = $USER->ChangePassword($USER_LOGIN, $USER_CHECKWORD, $USER_PASSWORD, $USER_CONFIRM_PASSWORD, $USER_LID);
needs to be wrapped as follows:
$arAuthResult = $USER->ChangePassword($USER_LOGIN, $USER_CHECKWORD,
$USER_PASSWORD, $USER_CONFIRM_PASSWORD, $USER_LID);
Example 2: The code
if(COption::GetOptionString("main", "new_user_registration", "N")=="Y" && $_SERVER['REQUEST_METHOD']=='POST' &&$TYPE=="REGISTRATION" && (!defined("ADMIN_SECTION") || ADMIN_SECTION!==true))
needs to be wrapped as follows
if (COption::GetOptionString("main", "new_user_registration", "N") == "Y")
&& $_SERVER['REQUEST_METHOD'] == 'POST' && $TYPE == "REGISTRATION"
&& (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true)
1.1.3. Spaces And Tabs
Use tabs for indentation. Using spaces for indentation is forbidden for the following reasons:
- with tabs, any developer can configure his or her text editor to show the desired tab length;
- using tabs makes file size smaller;
- if both tabs and spaces are used for indentation, the originally intended text formatting is likely to be damaged if a different tab size is used in a text editor.
1.1.4. Scopes And Code Blocks
The code block contents must be indented by one tab. The code block contents must not be on the same line as the controlling statement.
Example:
function func()
{
if (condition)
{
while (condition2)
{
}
}
}
1.1.5.Rules for placing braces
Opening braces must be place under a corresponding operator and on the same indent with it. Closing braces must be placed under the corresponding opening braces.
Example:
if ($condition)
{
...
}
1.1.6. Using the ternary operator "?:"
Conditions must be enclosed in parentheses, and thus separated from the rest of the code. As much as possible, actions that occur under these conditions should be simple functions. If an entire branched block reads poorly, then it is worth replacing it with if/else.
Example:
(condition ? funct1() : func2());
1.2. Expressions And Statements
1.2.1. Expressions
One line must contain only one expression.
Example. This expression is formatted incorrectly:
$a = $b; $b = $c; $c = $a;
Rewrite it like this:
$a = $b;
$b = $c;
$c = $a;
1.2.2. The statements if, else, while etc.
Use one of the following two formatting rules depending on the statement length.
if a controlled code block contains only one statement, use the following form:
if (expression)
statement 1;
else
statement 2;
if at least one controlled code block contains multiple statements, use braces:
if (expression)
{
statement 1;
}
else
{
statement 2;
statement 3;
}
The rule Scopes And Code Blocks must be obeyed when writing multiple statements: they must be indented by one tab off the controlling statement. The braces must exist on new lines on the same level as the controlling statement.
Example. This code:
if ($a == 0) $a = 10;
else{
$a = 5;
$b = 10;}
must be reformatted like this:
if ($a == 0)
{
$a = 10;
}
else
{
$a = 5;
$b = 10;
}
1.2.3. Compound Expressions
Compound expressions must be split in multiple lines according to rules described in “The statements if, else, while etc.”.
For example, consider the following code:
if(COption::GetOptionString("main", "new_user_registration", "N")=="Y" && $_SERVER['REQUEST_METHOD']=='POST' &&
$TYPE=="REGISTRATION" && (!defined("ADMIN_SECTION") || ADMIN_SECTION!==true))
Make it readable by sticking to the formatting rules:
if (COption::GetOptionString("main", "new_user_registration", "N") == "Y"
&& $_SERVER['REQUEST_METHOD'] == 'POST' && $TYPE == "REGISTRATION"
&& (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true))
{
}
It is recommended that you split an extremely complex expression into several simple lines of code.
For example, the code
if((!(defined("STATISTIC_ONLY") && STATISTIC_ONLY && substr($APPLICATION->GetCurPage(), 0,
strlen(BX_ROOT."/admin/"))!=BX_ROOT."/admin/")) && COption::GetOptionString("main", "include_charset", "Y")=="Y"
&& strlen(LANG_CHARSET)>0)
is definitely more readable when written like this:
$publicStatisticOnly = False;
if (defined("STATISTIC_ONLY")
&& STATISTIC_ONLY
&& substr($APPLICATION->GetCurPage(), 0, strlen(BX_ROOT."/admin/")) != BX_ROOT."/admin/")
{
$publicStatisticOnly = True;
}
if (!$publicStatisticOnly && strlen(LANG_CHARSET) > 0
&& COption::GetOptionString("main", "include_charset", "Y") == "Y")
{
}
or like this:
if (!defined("STATISTIC_ONLY") || ! STATISTIC_ONLY
|| substr($APPLICATION->GetCurPage(), 0, strlen(BX_ROOT."/admin/")) == BX_ROOT."/admin/")
{
if (strlen(LANG_CHARSET) > 0 && COption::GetOptionString("main", "include_charset", "Y") == "Y")
{
}
}
1.2.4. Arrays
The arrays consisting of multiple lines of code should be formatted like this:
$arFilter = array(
"key1" => "value1",
"key2" => array(
"key21" => "value21",
"key22" => "value22",
)
);
1.3. Empty Lines And Spaces
1.3.1. Empty Lines
Use empty lines to logically divide your source code. Use multiple empty lines to divide the source code into logical or functional sections in one file. Use a single empty line to separate methods, as well as expressions and statements within a method for better readability.
It is recommended to add a comment before a logical or functional section (see Comments).
1.3.2. Spaces
The comma must be followed by the space. The semicolon must be followed by the space unless it is the last character on the line (for example, in a complex “for” statement). No spaces before the comma or semicolon is allowed. Tabs must not be used instead of spaces in such cases.
Use Cases
-
The following example shows how a space is used after the commas, but not before the parenthesis:
TestMethod($a, $b, $c);
These two code fragments are formatted incorrectly:
TestMethod($a,$b,$c);
and
TestMethod( $a, $b, $c );
- The following example shows to use spaces to properly separate the operators:
$a = $b * $c / $d;
as opposed to the same code formatted incorrectly:
$a=$b*$c/$d;
- Use spaces to format the “for” statements:
for ($i = 0; $i < 10; $i++)
- Do not merge operators and expressions like this:
for($i=0;$i<10;$i++)
- Note that using tabs to format expressions inside statements is not allowed.
The following formatting should not be made a common practice:
$arArray = array(
"key1" => "value1",
"key2" => "value2",
);
There is no special rule regarding the use of the space, or lack thereof, after the “if” statement.
1.4. Other regulations
Use parentheses to group operators in complex expressions regardless of operator precedence for better readability.
$r = $a + ($b * $c);
2. Naming Conventions
2.1. General Provisions
Do not use underscores in the identifier names because it makes them longer and less readable. Use such names that can describe the identifier purpose and meaning in an unambiguous fashion.
At the same time, try to make your identifier shorter (but they still must be well readable).
If a name contains an abbreviation, it’s better to use a single capital letter for the first letter of the abbreviation than to use capitals for the entire abbreviation. So it would be better to assign a name like getHtmlStatistic than getHTMLStatistic.
2.2. Variable Names
Start with a lowercase character and use uppercase character as separators (camelCase). Variable names can have prefixes if there is a clear need to show the type of variable: ar – for arrays, db – for data sets from databases, etc.
For example: $testCounter, $userPassword.
2.3. Method Names
- Clarity in action names, which will execute a function or method.
- Use of prefixes: is (indicates a question), get (to get a variable), set (set value).
Example: isFileWriteable()
2.4. Class Names
- The name should signify an entity described by the class.
- The name should not consist of more than 3 words.
- The underscore symbol (‘_’) cannot be used.
- To separate words in the name, the first letter of each word can be capitalized.
Example: class SaleAffiliateAccount
3. Comments
Commentary must be in English and contain relevant information
3.1. PHPDoc
All classes and their public methods must be described in PHPDoc style.
Example:
/**
* Gets a value that indicates whether a directory exists in the file system
*
* @param string $path - Path to the directory
* @return bool - True if the directory exists, false - otherwise
*/
4. Other
4.1. Magic numbers
Code should not contain magic numbers. Here is an example of bad code:
$task->Update($taskId, array('STATUS' => 3), 1);
Proper code:
$task->Update($taskId, array('STATUS' => CTaskStatus::New), TASK_PERMISSIONS_WRITE);
4.2. Automatic formatting tools
4.2.1. php_beautifier
1. Install the php_beautifier (ubuntu) pack:
sudo aptitude install php_beautifier or sudo aptitude install php-pear и sudo pear install PHP_Beautifier-0.1.15
cd
hg clone http://hg.max/repos/Bitrix_php_beautifier
sudo ln -s Bitrix_php_beautifier/Bitrix.filter.php /usr/share/php/PHP/Beautifier/Filter/Bitrix.filter.php
Configure the editor:
- Select the menu point Settings - Configure Kate...
- Select the setting Plugins and place a check next to Text Filter
- Click OK
- Now the Tools menu will have a point called Filter Text
Use:
- Select a text excerpt (or Ctrl-A for all the text)
- Tools - Filter Text
- Enter (or select from history): php_beautifier -t -f - -l 'Lowercase Bitrix'
- Click OK.
Applications and Context
Application is an object responsible for core initialization.
Application is the entry base point (router) for query to (kernel) core global entities: connection with data sources, managed cache, etc. Also, the application contains global data that belongs to the site itself and do not depend on a specific hit. I.e. the application is an unaltered part not dependent on a specific hit.
Any specific class of application is a successor of the abstract class Bitrix\Main\Application.
The specific class Bitrix\Main\HttpApplication is responsible for a regular http hit on the site.
The application supports the Singleton template. I.e. as a part of the hit there is only one copy of a specific type of application. It can be obtained using the instruction:
$application = Application::getInstance();
Context is an object responsible for a specific hit. It contains a query of a current hit, response to it, and also the server parameters of the current hit. I.e. it is a variable part that depends on the current hit.
Any specific class of a context is a successor of the abstract class Bitrix\Main\Context. Two specific classes of the context are supported – Bitrix\Main\HttpContext and Bitrix\Main\CliContext. The specific class Bitrix\Main\HttpContext is responsible for a regular http hit on the site.
The following code may be used in order to obtain a context of the current hit execution:
$context = Application::getInstance()->getContext();
If the application of the Bitrix\Main\HttpApplication type was initiated, this call will bring an instance of the Bitrix\Main\HttpContext context type.
The context contains a query of the current hit. In order to receive the query the following code may be used:
$context = Application::getInstance()->getContext();
$request = $context->getRequest();
Note: In all the examples, a full form of record is used (sometimes without indicating name spaces) that permits you to obtain the result from any code point. However, short forms may exist for this specific code point in order to access the result.
A request is a class instance that is a successor of the Bitrix\Main\Request class. In case of a normal http request the request will be a class instance of Bitrix\Main\HttpRequest that extends Bitrix\Main\Request. This class is a dictionary providing access to “key-value” pairs of incoming parameters.
The following code may be used in order to access an incoming parameter transmitted by the GET or POST methods:
$value = $request->get("some_name");
$value = $request["some_name"];
Note: Code $value = $request["some_name"];
returns string processed by the security module filters. However, this doesn't signify about its security, all depends on how it will be used further.
Other useful query methods:
$value = $request->getQuery("some_name"); // receive GET parameter
$value = $request->getPost("some_name"); // receive POST parameter
$value = $request->getFile("some_name"); // receive downloaded file
$value = $request->getCookie("some_name"); // receive cookie value
$uri = $request->getRequestUri(); // receive an address requested
$method = $request->getRequestMethod(); // receive a request method
$flag = $request->isPost(); // true - POST-request, otherwise false
Errors
In the old core, API tried to interpret for a user/developer and was overlooking errors and inaccuracies. As a result, errors that are difficult to catch may occur. In addition, such an approach causes a lot of implicit agreements that must be taken into account.
For example, a user/developer selects entries for deletion using a filter. In this case, they incidentally make a writing error in the filter name. A typical API of the old core will ignore this filter and return all the entries. The next instruction will successfully delete all of these entries.
The method changes in the new core D7. API does not have to guess anything for a user. API must respond appropriately if it meets an unexpected situation, such as an unknown filter, no id transmitted, lack of value, excessive value, must not be invoked in this mode, etc.
When an error is displayed on the screen (if the debugging mode is available), connects the file /error.php (The file /error.php is located in site root. It can contain, for example, error message print).
Example of error.php
|
<table cellpadding="1" cellspacing="0" width="35%" bgcolor="#9C9A9C">
<tr>
<td><table cellpadding="5" cellspacing="0" width="100%">
<tr>
<td bgcolor="#FFFFFF" align="center"><FONT face="Verdana, Arial, Helvetica, sans-serif" size="-1">
<font color="#FF0000"><b><?echo "No such filter!"?></b></font><br>Check name and try again.</font></td>
</tr>
</table></td>
</tr>
</table>
|
You can print error in the site design inside this file, as well as specify HTTP status code (for example, "500 Internal Server Error").
API
Module API (classes) is not divided by databases. ORM takes care of all the twists and turns of work with a specific database.
No prefixes or suffixes shall be used in class names.
Each module API class may be located in a separate file with a name that coincides with the class name written in lower case. The classes located in the root of module namespace must be based in the files of the /lib
module folder root. The classes located in subspaces inside the module namespace must be based in the files of the relevant subfolders of the /lib
module folder.
For example, the Bitrix\Main\Application class must be located in the file /lib/application.php
in respect of the main module root folder; the Bitrix\Main\IO\File class must be located in the file /lib/io/file.php
in respect of the main module root folder; and the Bitrix\Forum\Message class must be located in the file /lib/message.php in respect of the root folder of the forum module.
If these naming rules are complied with, once a module is connected, its classes are uploaded automatically upon first call. No additional actions are needed for the registration and connection of files with classes.
Note: However, for performance reasons, additional registration and connection are recommended for classes that are used often.
Classes of the ORM entities (successors of the class Bitrix\Main\Entity\DataManager) constitute an exception from the class and file naming rules. The names of such classes are generated with the Table suffix. E.g., CultureTable, LanguageTable. But file names do not contain the table suffix. Such classes are also connected automatically.
Note: There is a possibility to register a class in the autoload system using the following method:
void Loader::registerAutoLoadClasses(
$moduleName,
array $arClasses
)
It can be used in order to merge small classes in one file.
Non-standard classes (partner’s classes) must be located in their own namespaces that coincide with the names of relevant partners. Each partner’s module determines its own subspace in the partner’s namespace that coincides with the module name without the partner’s name. E.g., for the module mycompany.catalog of the partner "Mycompany", the namespace will be MyCompany\Catalog. Other rules are the same as for standard modules.
The following instruction is used in order to connect a module in a new core:
mixed Loader::includeModule($moduleName);
Naming Rules
Classes
- Classes must be named in UpperCamelCase.
- Their name cannot contain any other symbols but for Latin letters.
- Class name must be a noun. Unnecessary acronyms and abbreviations should be avoided.
Examples:
class User;
class UserInformation;
Methods
- The methods, including class methods, must be named in lowerCamelCase.
- Their name cannot contain any other symbols but for Latin letters.
- Numbers may be used, provided that the name cannot be formed otherwise. E.g.: encodeBase64, getSha1Key.
- The method name must start with a verb.
- The name length must contain at least 3 characters.
Examples:
run();
setImage();
getName();
Pure data
- Pure data, including class pure data, must be written in UPPER_REGISTER_WITH_A_FLATWORM_SEPARATOR.
- May contain Latin letters, flatworm character, and numbers (except for the first position).
Examples:
DATE_TIME_FORMAT
LEVEL_7
Class Members, Method Parameters, and Variables
- Must be named in a lowerCamelCase.
- May not contain prefixes meaning membership in class, appurtenance to parameters, type, and other insignificant things. Example of unnecessary prefixes: $this->mAge, function setName($pName), $arrArray.
- May contain Latin letters and numbers (but for the first position).
Examples:
$firstName = '';
$counter = 0;
Generally Acceptable Abbreviations of the Names of Variables and Methods
- Abbreviations in the first position must be written in lower case letters, abbreviations in other positions must start with a capital letter, with all other letters being written in lowercase.
- Class names should start with a upper case letter, with all other letters being written in lower case.
Example:
$xmlDocument
$mainXmlDocument
HttpParser
Abbreviations that are not generally acceptable (in Bitrix) cannot be used.
Working with the Database
General architecture of API classes for working with databases:
- The connection pool Bitrix\Main\DB\ConnectionPool contains information about connections of applications. There are default connections (main connections) and there may be a set of named connections to connect elsewhere, including to another database of a different type.
- Specific connection classes inheriting the abstract class Bitrix\Main\DB\Connection ensure low-level operations with the database. They are divided into classes by databases.
- Specific classes of forming SQL queries inheriting Bitrix\Main\DB\SqlHelper that help to form a query without going into the syntax of a specific database.
- Specific classes for work with query execution result inheriting Bitrix\Main\DB\Result. They also vary according to databases.
These classes permit working with databases at a low level, but it is rarely required. Instead, working through ORM is recommended since ORM permits programming only at a business logic level.
Obtaining Database Connection, Named Connections
A connection may be obtained through applications and is, among other things, an entry point. From this entry point, instances of “star” objects for this application may be obtained that are needed for all (or almost all) pages or components of this application.
$connection = Main\Application::getConnection();
$sqlHelper = $connection->getSqlHelper();
$sql = "SELECT ID FROM b_user WHERE LOGIN = '".$sqlHelper->forSql($login, 50)."' ";
$recordset = $connection->query($sql);
while ($record = $recordset->fetch())
{
***
Various Forms of Query Execution Call
Accordingly, a call must be executed through the application in various forms: simple query, query with a limit for entries, scalar query, or a query “in general.”
$result1 = $connection->query($sql);
$result2 = $connection->query($sql, $limit);
$result3 = $connection->query($sql, $offset, $limit);
$cnt = $connection->queryScalar("SELECT COINT(ID) FROM table");
$connection->queryExecute("INSERT INTO table (NAME, SORT) VALES ('Name', 100)")
Obtaining Query Results:
$connection = Main\Application::getConnection();
$sqlHelper = $connection->getSqlHelper();
$sql = "SELECT ID FROM b_user WHERE LOGIN = '".$sqlHelper->forSql($login, 50)."' ";
$recordset = $connection->query($sql);
while ($record = $recordset->fetch())
{
***
Typed data are immediately returned as a type and not as lines or numbers.
Result Modification:
$connection = \Bitrix\Main\Application::getConnection();
$recordset = $connection->query("select * from b_iblock_element", 10);
$recordset->setFetchDataModifier(
function ($data)
{
$data["NAME"] .= "!";
return $data;
}
);
while ($record = $recordset->fetch(\Bitrix\Main\Text\Converter::getHtmlConverter()))
$data[] = $record
Result may be modified and conversed immediately, e.g. for preparation to XML displaying, etc.
Classes. Examples
Configuration
Located in the namespace \Bitrix\Main\Config. Consists of 2 classes: \Bitrix\Main\Config\Configuration и \Bitrix\Main\Config\Option.
Configuration
$realm = \Bitrix\Main\Config\Configuration::getValue("http_auth_realm");
if (is_null($realm))
$realm = "Bitrix Site Manager"
Class is responsible for the global settings of the entire application. (This feature is determined by pure data in the old core.) Class operates a single base of settings stored in the file /bitrix/.settings.php
. The data stored are random. For example, the entire data pool can be stored for named connections.
Option
$cookiePrefix = \Bitrix\Main\Config\Option::get('main', 'cookie_name', 'BITRIX_SM');
$cookieLogin = $request->getCookie($cookiePrefix.'_LOGIN');
$cookieMd5Pass = $request->getCookie($cookiePrefix.'_UIDN');
The option class is virtually the same as the COption class of the old core and works with the parameters of modules and sites stored in the database. It is controlled from the administrative section: settings of specific forms, setup, etc.
Files
Work with files is object-oriented. It is located in the Bitrix\Main\IO namespace and has 3 basic classes:
Some other classes, including abstract, for managing hierarchy are also available in addition to these classes.
Other classes
The folder bitrix/modules/main/lib
contains class library for implementing different frequent actions, included in the Main module and not spread among different modules. В том числе в соответствующих пространствах лежат файлы и API для работы:
Equivalent CUtil::jSPostUnescape() in Core D7
If you need to use HttpRequest in AJAX queries:
Application::getInstance()->getContext()->getRequest()->getPost('name')
you'll have to consider that CUtil::JSPostUnescape won't help in case of encoding win-1251.
You can use instead:
use Bitrix\Main\Web\PostDecodeFilter;
...
Application::getInstance()->getContext()->getRequest()->addFilter(new PostDecodeFilter)
After this, you can get decoded data via getPost.
Handling D7. Location example
Do not forget to connect sale module.
Types of locations
Adding a location type:
$res = \Bitrix\Sale\Location\TypeTable::add(array(
'CODE' => 'CITY',
'SORT' => '100', // nesting level
'DISPLAY_SORT' => '200', // display priority when searching
'NAME' => array( // language names
),
'en' => array(
'NAME' => 'City'
),
)
));
if($res->isSuccess())
{
print('Type added with ID = '.$res->getId());
}
Location type update
$res = \Bitrix\Sale\Location\TypeTable::update(21, array(
'SORT' => '300',
'NAME' => array(
'en' => array(
'NAME' => 'New City'
),
)
));
if($res->isSuccess())
{
print('Updated!');
}
Deleting location type
$res = \Bitrix\Sale\Location\TypeTable::delete(21);
if($res->isSuccess())
{
print('Deleted!');
}
Getting type of location by ID
$item = \Bitrix\Sale\Location\TypeTable::getById(14)->fetch();
print_r($item);
Getting list of types with names on the current language
$res = \Bitrix\Sale\Location\TypeTable::getList(array(
'select' => array('*', 'NAME_EN' => 'NAME.NAME'),
'filter' => array('=NAME.LANGUAGE_ID' => LANGUAGE_ID)
));
while($item = $res->fetch())
{
print_r($item);
}
Getting groups with account of hierarchy having this location
<?
\Bitrix\Main\Loader::includeModule('sale');
function getGroupsByLocation($locationId)
{
$res = \Bitrix\Sale\Location\LocationTable::getList([
'filter' => ['=ID' => $locationId],
'select' => [
'ID', 'LEFT_MARGIN', 'RIGHT_MARGIN'
]
]);
if(!$loc = $res->fetch())
{
return [];
}
$locations = [$locationId];
$res = \Bitrix\Sale\Location\LocationTable::getList([
'filter' => [
'<LEFT_MARGIN' => $loc['LEFT_MARGIN'],
'>RIGHT_MARGIN' => $loc['RIGHT_MARGIN'],
'NAME.LANGUAGE_ID' => LANGUAGE_ID,
],
'select' => [
'ID',
'LOCATION_NAME' => 'NAME.NAME'
]
]);
while($locParent = $res->fetch())
{
$locations[] = $locParent['ID'];
}
$res = \Bitrix\Sale\Location\GroupLocationTable::getList([
'filter' => ['=LOCATION_ID' => $locations]
]);
$groups = [];
while($groupLocation = $res->fetch())
{
$groups[] = $groupLocation['LOCATION_GROUP_ID'];
}
return $groups;
}
Locations
Adding
$res = \Bitrix\Sale\Location\LocationTable::add(array(
'CODE' => 'newly-created-location-code',
'SORT' => '100', // priority for showing in list
'PARENT_ID' => 1, // parent location ID
'TYPE_ID' => 14, // type ID
'NAME' => array( // language names
'en' => array(
'NAME' => 'New York'
),
),
'EXTERNAL' => array( // external service values
array(
'SERVICE_ID' => 1, // service ID
'XML_ID' => '163000' // value
),
array(
'SERVICE_ID' => 1,
'XML_ID' => '163061'
),
)
));
if($res->isSuccess())
{
print('Location added with ID = '.$res->getId());
}
else
{
print_r($res->getErrorMessages());
}
Update
$res = \Bitrix\Sale\Location\LocationTable::update(3156, array(
'PARENT_ID' => 33,
'NAME' => array(
'de' => array(
'NAME' => 'New York'
),
)
));
if($res->isSuccess())
{
print('Updated!');
}
Deleting
$res = \Bitrix\Sale\Location\LocationTable::delete(3156);
if($res->isSuccess())
{
print('Deleted!');
}
Getting location by ID
$item = \Bitrix\Sale\Location\LocationTable::getById(3159)->fetch();
print_r($item);
Getting location by CODE
, with optional filtering\field retrieval. In fact, this is a wrapper \Bitrix\Sale\Location\LocationTable::getList().
$item = \Bitrix\Sale\Location\LocationTable::getByCode('newly-created-location-code', array(
'filter' => array('=NAME.LANGUAGE_ID' => LANGUAGE_ID),
'select' => array('*', 'NAME_EN' => 'NAME.NAME')
))->fetch();
print_r($item);
Getting list of locations with names on the current language and type codes
$res = \Bitrix\Sale\Location\LocationTable::getList(array(
'filter' => array('=NAME.LANGUAGE_ID' => LANGUAGE_ID),
'select' => array('*', 'NAME_EN' => 'NAME.NAME', 'TYPE_CODE' => 'TYPE.CODE')
));
while($item = $res->fetch())
{
print_r($item);
}
Getting node direct descendants with ID=1 with names on the current language, codes and names for location types
$res = \Bitrix\Sale\Location\LocationTable::getList(array(
'filter' => array(
'=ID' => 1,
'=CHILDREN.NAME.LANGUAGE_ID' => LANGUAGE_ID,
'=CHILDREN.TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
),
'select' => array(
'CHILDREN.*',
'NAME_EN' => 'CHILDREN.NAME.NAME',
'TYPE_CODE' => 'CHILDREN.TYPE.CODE',
'TYPE_NAME_EN' => 'CHILDREN.TYPE.NAME.NAME'
)
));
while($item = $res->fetch())
{
print_r($item);
}
Getting parent nodes for three nodes
$res = \Bitrix\Sale\Location\LocationTable::getList(array(
'filter' => array(
'=ID' => array(3159, 85, 17),
'=PARENT.NAME.LANGUAGE_ID' => LANGUAGE_ID,
'=PARENT.TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
),
'select' => array(
'PARENT.*',
'NAME_EN' => 'PARENT.NAME.NAME',
'TYPE_CODE' => 'PARENT.TYPE.CODE',
'TYPE_NAME_EN' => 'PARENT.TYPE.NAME.NAME'
)
));
while($item = $res->fetch())
{
print_r($item);
}
Getting path from tree root to the current item
$res = \Bitrix\Sale\Location\LocationTable::getList(array(
'filter' => array(
'=ID' => 224,
'=PARENTS.NAME.LANGUAGE_ID' => LANGUAGE_ID,
'=PARENTS.TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
),
'select' => array(
'I_ID' => 'PARENTS.ID',
'I_NAME_EN' => 'PARENTS.NAME.NAME',
'I_TYPE_CODE' => 'PARENTS.TYPE.CODE',
'I_TYPE_NAME_EN' => 'PARENTS.TYPE.NAME.NAME'
),
'order' => array(
'PARENTS.DEPTH_LEVEL' => 'asc'
)
));
while($item = $res->fetch())
{
print_r($item);
}
Getting list of root nodes with indicated number of descendants
$res = \Bitrix\Sale\Location\LocationTable::getList(array(
'filter' => array(
'=PARENT_ID' => 0,
'=NAME.LANGUAGE_ID' => LANGUAGE_ID,
'=TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
),
'select' => array(
'ID',
'NAME_EN' => 'NAME.NAME',
'TYPE_CODE' => 'TYPE.CODE',
'TYPE_NAME_EN' => 'TYPE.NAME.NAME',
'CHILD_CNT'
)
));
while($item = $res->fetch())
{
print_r($item);
}
Getting external data for locations with indicated service code
$res = \Bitrix\Sale\Location\LocationTable::getList(array(
'filter' => array(
'CODE' => array('newly-created-location-code', '0000028090'),
),
'select' => array(
'EXTERNAL.*',
'EXTERNAL.SERVICE.CODE'
)
));
while($item = $res->fetch())
{
print_r($item);
}
Getting subtree of nodes on the current language
$res = \Bitrix\Sale\Location\LocationTable::getList(array(
'runtime' => array(
'SUB' => array(
'data_type' => '\Bitrix\Sale\Location\Location',
'reference' => array(
'>=ref.LEFT_MARGIN' => 'this.LEFT_MARGIN',
'<=ref.RIGHT_MARGIN' => 'this.RIGHT_MARGIN'
),
'join_type' => "inner"
)
),
'filter' => array(
'=CODE' => '0000028042',
'=SUB.NAME.LANGUAGE_ID' => LANGUAGE_ID
),
'select' => array(
'S_CODE' => 'SUB.CODE',
'S_NAME_EN' => 'SUB.NAME.NAME',
'S_TYPE_CODE' => 'SUB.TYPE.CODE'
)
));
while($item = $res->fetch())
{
print_r($item);
}
Getting locations included into the group without hierarchy.
\Bitrix\Main\Loader::includeModule('sale');
/* Group identifier */
$groupId = 1
/* Get locations included into group */
$res = \Bitrix\Sale\Location\GroupLocationTable::getConnectedLocations(1);
while($item = $res->fetch())
{
var_dump($item);
}
Service Locator
Description
Service locator - is a design pattern used for convenient handling of application services. You can find more details in this
article.
Idea of service patten is designed to use special object (service locator) that will be responsible for creating and locating services instead of creating specific services directly (via "new"). It's a kind of a register.
Class \Bitrix\Main\DI\ServiceLocator
implements the interface PSR-11. Available from main version 20.5.400.
Simple example of use:
$serviceLocator = \Bitrix\Main\DI\ServiceLocator::getInstance();
if ($serviceLocator->has('someService'))
{
$someService = $serviceLocator->get('someService');
//...$someService service use
}
Service registration
Service registration via bitrix/.settings.php files
Before querying the service, register it, using .settings.php
files. All the necessary services are listed in the services section.
[
'value' => [
'someServiceName' => [
'className' => \VendorName\Services\SomeService::class,
],
'someGoodServiceName' => [
'className' => \VendorName\Services\SecondService::class,
'constructorParams' => ['foo', 'bar'],
],
],
'readonly' => true,
],
//...
];
As a result, these services will be available immediately after kernel is initialized. Below you can read about the available service describing methods.
$serviceLocator = \Bitrix\Main\DI\ServiceLocator::getInstance();
$someGoodServiceName = $serviceLocator->get('someGoodServiceName');
$someServiceName = $serviceLocator->get('someServiceName');
Service registration has been performed via the {moduleName}/.settings.php module setting files
Module root directory can also contain the .settings.php file. It can contain service descriptions belonging to this module and are used in it. Semantics is similar to description in the global bitrix/.settings.php
and config description rules.
[
'value' => [
'someModule.someServiceName' => [
'className' => \VendorName\SomeModule\Services\SomeService::class,
],
'someModule.someAnotherServiceName' => [
'constructor' => static function () {
return new \VendorName\SomeModule\Services\SecondService('foo', 'bar');
},
],
'someModule.someGoodServiceName' => [
'className' => \VendorName\SomeModule\Services\SecondService::class,
'constructorParams' => static function (){
return ['foo', 'bar'];
},
],
],
'readonly' => true,
],
//...
];
Attention! These services will be registered only after module is connected. Also, it is recommended to name module services by using module name prefix, to avoid duplicate service codes, for example:
iblock.imageUploader
disk.urlManager
crm.entityManager
crm.urlManager
someModule.urlManager.
Registering service via API
Services can be registered via API as well. Use the class methods \Bitrix\Main\DI\ServiceLocator
Service configuration
Configuration is described as an array and prompts service locator a method for creating an object. Presently, there are three description methods:
- Indication of service class. Service locator creates the service by calling new $className.
'someModule.someServiceName' => [
'className' => \VendorName\SomeModule\Services\SomeService::class,
]
- Indicating service class and parameters to be passed into constructor. Service locator creates a service by calling new $className('foo', 'bar').
'someModule.someServiceName' => [
'className' => \VendorName\SomeModule\Services\SomeService::class,
'constructorParams' => ['foo', 'bar'],
]
'someModule.someServiceName' => [
'className' => \VendorName\SomeModule\Services\SomeService::class,
'constructorParams' => static function (){
return ['foo', 'bar'];
},
]
- Indicating closure-constructor that create and return service object.
'someModule.someAnotherServiceName' => [
'constructor' => static function () {
return new \VendorName\SomeModule\Services\SecondService('foo', 'bar');
},
]
Locator registration
Service registration
Service registration via the bitrix/.settings.php files
Before querying the service, it must be registered. It can be done via .settings.php
files. All the necessary services are listed in the services section.
[
'value' => [
'someServiceName' => [
'className' => \VendorName\Services\SomeService::class,
],
'someGoodServiceName' => [
'className' => \VendorName\Services\SecondService::class,
'constructorParams' => ['foo', 'bar'],
],
],
'readonly' => true,
],
//...
];
As the result, these services will be available immediately after core D7 initialization. Read below about which service description methods are presently available.
$serviceLocator = \Bitrix\Main\DI\ServiceLocator::getInstance();
$someGoodServiceName = $serviceLocator->get('someGoodServiceName');
$someServiceName = $serviceLocator->get('someServiceName');
Service registration using the module settings {moduleName}/.settings.php files
The module root can also have the .settings.php file. You can describe services that belong and are used in this module. Semantics is similar to the description in the global bitrix/.settings.php
files and config description rules.
[
'value' => [
'someModule.someServiceName' => [
'className' => \VendorName\SomeModule\Services\SomeService::class,
],
'someModule.someAnotherServiceName' => [
'constructor' => static function () {
return new \VendorName\SomeModule\Services\SecondService('foo', 'bar');
},
],
'someModule.someGoodServiceName' => [
'className' => \VendorName\SomeModule\Services\SecondService::class,
'constructorParams' => static function (){
return ['foo', 'bar'];
},
],
],
'readonly' => true,
],
//...
];
Attention! These services will be registered only after module is connected. Also, it is recommended to name the module services by using the module name prefix to avoid issues with service code duplicates, for example:
iblock.imageUploader
disk.urlManager
crm.entityManager
crm.urlManager
someModule.urlManager.
Service registration via API
Services can be registered via API as well. Use the class methods \Bitrix\Main\DI\ServiceLocator
Service configuration
Configuration is described as an array and prompts service locator a method for creating an object. Presently, there are three methods for description:
- Indicating a service class. Service locator creates a service by calling a new $className.
'someModule.someServiceName' => [
'className' => \VendorName\SomeModule\Services\SomeService::class,
]
- Indicating a service class and parameters to be passed into constructor. Service locator will create a service by calling a new $className('foo', 'bar').
'someModule.someServiceName' => [
'className' => \VendorName\SomeModule\Services\SomeService::class,
'constructorParams' => ['foo', 'bar'],
]
'someModule.someServiceName' => [
'className' => \VendorName\SomeModule\Services\SomeService::class,
'constructorParams' => static function (){
return ['foo', 'bar'];
},
]
- Indicating a closure-constructor that must create and return a service object.
'someModule.someAnotherServiceName' => [
'constructor' => static function () {
return new \VendorName\SomeModule\Services\SecondService('foo', 'bar');
},
]
Controllers
Terminology
- Action, or AJAX action is a responder method that implements final logic, executes operations and returns data.
- Controller - is a combination of AJAx-actions.
- configureActions() method regulates configuration of actions inside the controller, defining rules of access to actions.
- Pre-, post-filters are configuration items, that are action start and end event handers. Prefilter can block the start, postfilter can affect the result.
Agreements
Note: Main (main) module version 20.600.87 supports PSR-4 in ajax-controllers.
- Upon calling all parameter names are caseSEnsitive
- Upon calling all parameter names are caseSEnsitive
- Upon calling all action names are case-INsensitive
- Full action name is generated by the template
vendor:module.partOfNamespace0.partOfNamespace1.Controller.action
.
\Bitrix\Disk\Controller\Folder::getAction()
bitrix:disk.Controller.Folder.get
\Bitrix\Disk\Controller\Intergation\Dropbox::connectAction()
bitrix:disk.Controller.Intergation.Dropbox.connect
\Qsoft\Somedisk\Controller\SuperFolder::getAction()
qsoft:somedisk.Controller.SuperFolder.get
- When not indicating the vendor:, it means it is bitrix:
\Bitrix\Disk\Controller\Folder::getAction()
disk.Controller.Folder.get
- When indicating
defaultNamespace
in module settings, it can be omitted and not indicated in the action.
defaultNamespace = \Bitrix\Disk\Controller
\Bitrix\Disk\Controller\Folder::getAction()
disk.Folder.get
- When indicating alias in module settings, use it instead of abbreviated namespace.
\Bitrix\Disk\CloudIntegration\Controller => cloud
\Bitrix\Disk\CloudIntegration\Controller\File::getAction()
disk.cloud.File.get
- Upon calling the action from component, indicate full component name and action name (without
Action
suffix).
bitrix:list.example
showFormAction
BX.ajax.runComponentAction('bitrix:list.example', 'showForm', {
...
}).then(function (response) {});
- Time, date, links must be returned not in string format, but as objects.
\Bitrix\Main\Type\DateTime
\Bitrix\Main\Type\Date
\Bitrix\Main\Web\Uri
Controller
Controllers is the part of
MVC architecture, responsible for query processing and response generation.
Actions
Controllers consist of main operational actions, employed by user to get a result. Single controller can have one or several actions. For example, let's create controller with two actions item.add and item.view in the module example. Specifically for this example, controller must be located at /modules/vendor.example/lib/controller/item.php
.
First step - create file .settings.php
in the module root.
<?php
//modules/vendor.example/.settings.php
return [
'controllers' => [
'value' => [
'defaultNamespace' => '\\Vendor\\Example\\Controller',
],
'readonly' => true,
]
];
Next, create controller file (see details for available methods here):
namespace Vendor\Example\Controller;
use \Bitrix\Main\Error;
class Item extends \Bitrix\Main\Engine\Controller
{
public function addAction(array $fields):? array
{
$item = Item::add($fields);
if (!$item)
{
$this->addError(new Error('Could not create item.', {error_code}));
return null;
}
return $item->toArray();
}
public function viewAction($id):? array
{
$item = Item::getById($id);
if (!$item)
{
$this->addError(new Error('Could not find item.', {error_code}));
return null;
}
return $item->toArray();
}
}
First, the add action (using specific method Item::addAction
) attempts to create a specific Item passed by $fields
.
Note. Array $fields retrieved by automatic binding of method parameters and $_REQUEST
.
When an activity was failed due to specific reasons, return null and complete the controller by errors. In this case, kernel generates a response:
{
"status": "error", //please note that status has been automatically updated
"data": null,
"errors": [
{
"message": "Could not create item.",
"code": {code}
}
]
}
Otherwise, add an item and return it as an array $item->toArray()
from activity. This array, kernel generates the response:
{
"status": "success",
"data": {
"ID": 1,
"NAME": "Nobody",
//...element fields
},
"errors": null
}
Generally, activity can return not only scalars, but objects as well.
The view activity (using specific method Item::viewAction
) first attempts loading a specific Item object by specific passed parameter $id
. It's important to note, the $id will be automatically retrieved from $_POST['id']
or $_GET['id']
.
When this parameter is not found, kernel generates response with error:
{
"status": "error",
"data": null,
"errors": [
{
"message": "Could not find value for parameter {id}",
"code": 0
}
]
}
How execute controller action?
Specific AJAX-action requires action naming convention awareness. In our case: Item::addAction -> vendor:example.item.add
Item::viewAction -> vendor:example.item.view
.
vendor:example.item.add, vendor:example.item.view can be used for calling actions using BX.ajax.runAction:
BX.ajax.runAction('vendor:example.item.add', {
data: {
fields: {
ID: 1,
NAME: "test"
}
}
}).then(function (response) {
console.log(response);
/**
{
"status": "success",
"data": {
"ID": 1,
"NAME": "test"
},
"errors": []
}
**/
}, function (response) {
//receives all responses with status status !== 'success'
console.log(response);
/**
{
"status": "error",
"errors": [...]
}
**/
});
Or retrieve the link to action and send individual http-query.
/** @var \Bitrix\Main\Web\Uri $uri **/
$uri = \Bitrix\Main\Engine\UrlManager::getInstance()->create('vendor:example.item.view', ['id' => 1]);
echo $uri;
// /bitrix/services/main/ajax.php?action=vendor:example.item.view&id=1
// execute GET-query
Creating controllers and actions
Creating controllers
Controllers must be inherited from \Bitrix\Main\Engine\Controller or its descendants. Controllers can be located inside the module, or inside the component in the file ajax.php and be a controller for component.
Creating actions
Creating actions means creating methods in a specific controller. Method requires to be public and have an Action suffix.
namespace Vendor\Example\Controller;
class Item extends \Bitrix\Main\Engine\Controller
{
public function addAction(array $fields)
{
//...
}
}
Returns action value is response data that can be sent to client.
If action returns \Bitrix\Main\HttpResponse or its descendants, this object is sent to client. When action returns data, it must be converted to scalar or object format, to be converted to JSON for generating \Bitrix\Main\Engine\Response\AjaxJson
.
Cumulatively, the action can return both scalars but objects as well that implement the following interfaces:
\JsonSerializable
\Bitrix\Main\Type\Contract\Arrayable
\Bitrix\Main\Type\Contract\Jsonable
Or specific descendant \Bitrix\Main\HttpResponse:
Creating action-classes
There is an option to create action classes, inherited from \Bitrix\Main\Engine\Action
. This option may be required, when logic must be repeatedly used in several controllers. For example, for implementing the same exchange protocol in different modules (e. g. standard search, executing step-by-step actions with progress and etc.). You need to describe the method configureActions in the controller config map:
class Test extends \Bitrix\Main\Engine\Controller
{
public function configureActions()
{
return [
'testoPresto' => [
'class' => \TestAction::class,
'configure' => [
'who' => 'Me',
],
],
];
}
}
Here's the TestAction itself:
<?php
use \Bitrix\Main\Engine\Action;
class TestAction extends Action
{
protected $who;
//method for additional config from controller. When requires to set
//some values into internal status
public function configure($params)
{
parent::configure($params);
$this->who = $params['who']?: 'nobody';
}
//main operational method. Parameters are automatically associated the same way as in the method
//ajax-action
public function run($objectId = null)
{
return "Test action is here! Do you know object with id {$objectId}? Mr. {$this->who}";
}
}
To call this action, query testoPresto, as described in the config map. Class action supports pre- and post- filters and are no different from the standard action method. run() method reason is similar to other methods/ajax-actions.
Using controllers inside components
Preliminarily create and use controller classes, located in modules, as listed in this article, because this allows to organize better a repeated use of auxiliary code and business logic.
In simple cases, when component is self-sufficient and is not actively used with module's API, you can use controllers inside the component.
Controller life cycle
Application query create controller based on naming convention. Next, controller executes the following work:
- Controller::init() is called after controller was created and configured.
- Controller creates an action object
- When action creating is unsuccessful, throws an exception.
- Controller requests the parameter preparation method Controller::prepareParams.
- Controller call the method Controller::processBeforeAction(Action $action); when returns true, the execution continues.
- Controller throws main module event {full_name_controller_class}::onBeforeAction, when returns EventResult not equal to
EventResult::SUCCESS
, execution is blocked. Prefilters are executed in this event.
- Controller triggers the action
- Action parameters are matched with query data
- Controller throws the main module event {full_name_controller_class}::onAfterAction. Postfilters are executed in this event.
- Controller calls the method
Controller::processAfterAction(Action $action, $result)
.
- Application receives an action result and if result is data, creates
\Bitrix\Main\Engine\Response\AjaxJson
with this data, or sends response object from action.
- Application calls the method Controller::finalizeResponse($response), by passing final response variant, which will be sent to user after all events and preparations.
- Prints $response to user.
Several namespaces
Indicating several namespaces in the module.
You can indicate several namespaces in .settings.php, in addition to defaultNamespace. This can may be necessary, when controllers are located next to their workflow entities. For example, in a "Drive" module we have integration with clouds.
<?php
return [
'controllers' => [
'value' => [
'namespaces' => [
'\\Bitrix\\Disk\\CloudIntegration\\Controller' => 'cloud', //cloud - is an alias
],
'defaultNamespace' => '\\Bitrix\\Disk\\Controller',
],
'readonly' => true,
]
];
Now you have call-available controllers, located in both namespaces. Both of them support calling via action full name and via abbreviated record, due to available cloud alias.
Similar to:
disk.CloudIntegration.Controller.GoogleFile.get
disk.cloud.GoogleFile.get
disk.Controller.File.get
disk.File.get
Calling modular controller
Calling modular controller with signed component parameters
When you need to query the controller, implemented in the module, use the following method from component context by getting signed parameters:
BX.ajax.runAction('socialnetwork.api.user.stresslevel.get', {
signedParameters: this.signedParameters, // result $this->getComponent()->getSignedParameters()
data: {
from: myComponentName, // for exmple, 'bitrix:intranet.user.profile', which parameters we require
fields: {
//..
}
}
});
After this, inside this action code, use:
<?php
//...
public function getAction(array $fields)
{
//inside is unpacked, checked array with parameters
$parameters = $this->getUnsignedParameters();
return $parameters['level'] * 100;
}
Controllers and component
You can use the following two approaches for processing AJAX queries in component:
class.php
Query handler in component class (class.php file) allows to:
- Incapsulate complete code in a single class
- Repeatedly use component methods, data and parameters
- Use component phrases and templates
- Re-define standard behaviour in descendant-components
For the component class to process queries, it's necessary to:
- Implement the interface \Bitrix\Main\Engine\Contract\Controllerable
- Define action method with suffix Action
- Implement the method configureActions (usually returns an empty array === default configuration)
- When you need to add, process errors, you can implement \Bitrix\Main\Errorable
Note: Executing component in Ajax mode sequentially executes CBitrixComponent::onIncludeComponentLang, CBitrixComponent::onPrepareComponentParams and launching an action with filters.
Attention! Executing component in ajax-mode doesn't launch method CBitrixComponent::executeComponent().
Example
<?php
#components/bitrix/example/class.php
use Bitrix\Main\Error;
use Bitrix\Main\ErrorCollection;
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
class ExampleComponent extends \CBitrixComponent implements \Bitrix\Main\Engine\Contract\Controllerable, \Bitrix\Main\Errorable
{
/** @var ErrorCollection */
protected $errorCollection;
public function configureActions()
{
//when actions does not require configuration, write them as is. They will use default config
return [];
}
public function onPrepareComponentParams($arParams)
{
$this->errorCollection = new ErrorCollection();
//parameter preparation
//This code **will** be executed upon launching ajax-actions
}
public function executeComponent()
{
//Attention! This code **won't be** executed upon launching ajax-actions
}
//data from REQUEST will be inserted to parameter $person
public function greetAction($person = 'guest')
{
return "Hi {$person}!";
}
//example of error processing
public function showMeYourErrorAction():? string
{
if (rand(3, 43) === 42)
{
$this->errorCollection[] = new Error('You are so beautiful or so handsome');
//now response will contain errors and automatic 'error' response status.
return null;
}
return "Ok";
}
/**
* Getting array of errors.
* @return Error[]
*/
public function getErrors()
{
return $this->errorCollection->toArray();
}
/**
* Getting once error with the necessary code.
* @param string $code Code of error.
* @return Error
*/
public function getErrorByCode($code)
{
return $this->errorCollection->getErrorByCode($code);
}
}
ajax.php
Query controller handler in the ajax.php file allows creating lightweight ajax-query handler, by directly dedicating logic from component.
To implement this:
- Create ajax.php file in the component root
- Define action method with Action suffix
The controller logic is fully the same as module controller description.
<?php
#components/bitrix/example/ajax.php
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
class ExampleAjaxController extends \Bitrix\Main\Engine\Controller
{
#data from REQUEST is automatically inserted into the parameter $person
public function sayByeAction($person = 'guest')
{
return "Goodbye {$person}";
}
public function listUsersAction(array $filter)
{
$users = [];
//user list as per filter
//array data composition for response
return $users;
}
}
Practical hints for developers
For convenient error debugging in AJAX life cycle, enable debug => true
in .settings.php
, then you will be able to view error and exception traces.
When required:
- to pass the file, use the classes \Bitrix\Main\Engine\Response\File and \Bitrix\Main\Engine\Response\BFile:
class Controller extends Engine\Controller
{
public function downloadAction($orderId)
{
//... find attached fileId by $orderId
return \Bitrix\Main\Engine\Response\BFile::createByFileId($fileId);
}
public function downloadGeneratedTemplateAction()
{
//... generate file ... $generatedPath
return new \Bitrix\Main\Engine\Response\File(
$generatedPath,
'Test.pdf',
\Bitrix\Main\Web\MimeType::getByFileExtension('pdf')
);
}
public function showImageAction($orderId)
{
//... find attached imageId by $orderId
return \Bitrix\Main\Engine\Response\BFile::createByFileId($imageId)
->showInline(true)
;
}
}
- to pass resized image, use \Bitrix\Main\Engine\Response\ResizedImage.
Be advised, you cannot allow user to request arbitrary dimensions for resizing. Always sign the parameters or directly set dimensions inside code.
class Controller extends Engine\Controller
{
public function showAvatarAction($userId)
{
//... find attached imageId by $userId
return \Bitrix\Main\Engine\Response\ResizedImage::createByImageId($imageId, 100, 100);
}
}
- to generate link in controller for the action from the same controller, use \Bitrix\Main\Engine\Controller::getActionUri
public function getAction(File $file)
{
return [
'file' => [
'id' => $file->getId(),
'name' => $file->getName(),
'links' => [
'rename' => $this->getActionUri('rename', array('fileId' => $file->getId())),
]
]
];
}
public function renameAction(File $file)
{
...
}
- to generate link in controller for an action that will pass content, for example, to download file, then use \Bitrix\Main\Engine\Response\DataType\ContentUri. This is required for integration with REST module.
public function getAction(File $file)
{
return [
'file' => [
'id' => $file->getId(),
'name' => $file->getName(),
'links' => [
'download' => new ContentUri($this->getActionUri('download', array('fileId' => $file->getId()))),
]
]
];
}
public function downloadAction(File $file)
{
...
}
- to convert data
SNAKE_CASE
as per standard to camelCase
, then you can use auxiliary controller methods \Bitrix\Main\Engine\Controller::convertKeysToCamelCase, or direct config \Bitrix\Main\Engine\Response\Converter:
public function getAction(File $file)
{
return [
'file' => $this->convertKeysToCamelCase($fileData)
];
}
public function showInformationAction(File $file)
{
$converter = new \Bitrix\Main\Engine\Response\Converter(Converter::OUTPUT_JSON_FORMAT & ~Converter::RECURSIVE);
return $converter->process($data);
}
Practical advice: interaction with controllers from Javascript
How to use component parameters in AJAX-action?
Frequently, AJAX-query must retrieve the same parameters from component that were used for its page display.
- You need to describe parameters to be used in the method listKeysSignedParameters
class ExampleComponent extends \CBitrixComponent implements \Bitrix\Main\Engine\Contract\Controllerable
{
protected function listKeysSignedParameters()
{
//list parameter names to be used in AJAX-actions
return [
'STORAGE_ID',
'PATH_TO_SOME_ENTITY',
];
}
- Get signed template parameters and, for example, pass into your into your component JS class
<!--template.php-->
<script type="text/javascript">
new BX.ExampleComponent({
signedParameters: '<?= $this->getComponent()->getSignedParameters() ?>',
componentName: '<?= $this->getComponent()->getName() ?>'
});
</script>
- Call BX.ajax.runComponentAction (as in examples) with parameter signedParameters.
BX.ajax.runComponentAction(this.componentName, action, {
mode: 'class',
signedParameters: this.signedParameters, //here is the way to pass parameters to component.
data: data
}).then(function (response) {
//some work
});
As the result, your can use the parameters STORAGE_ID, PATH_TO_SOME_ENTITY in your AJAX-action. These parameters are signed and integrity is controlled by kernel.
When you need to work with signed parameters inside ajax.php, use the method Controller::getUnsignedParameters() inside the controller action; it will contain array with unpacked data.
Additional information
Practical advice: page navigation
You can organize page navigation in AJAX-action by implementing in the method parameters \Bitrix\Main\UI\PageNavigation and return \Bitrix\Main\Engine\Response\DataType\Page.
Example:
use \Bitrix\Main\Engine\Response;
use \Bitrix\Main\UI\PageNavigation;
public function listChildrenAction(Folder $folder, PageNavigation $pageNavigation)
{
$children = $folder->getChildren([
'limit' => $pageNavigation->getLimit(),
'offset' => $pageNavigation->getOffset(),
]);
return new Response\DataType\Page('files', $children, function() use ($folder) {
//lazy evaluation of total records as per filter
return $folder->countChildren();
});
}
To pass page number in JS API, take note of navigation.
BX.ajax.runAction('vendor:someController.listChildren', {
data: {
folderId: 12
},
navigation: {
page: 3
}
});
Attention! Response\DataType\Page($id, $items, $totalCount)
$totalCount can be both an integer and \Closure, which can be a lazy evaluation. It's done for improved performance.
For example, in case of REST, calculation of total records is always required, but for standard AJAX it's optional. More performance and convenient is to use a separate AJAX-action for getting total records as per specific filter.
Practical advice: integration with REST module
You can make controllers, programmed inside a module, available for REST module. This is very convenient, because we re-use already written code.
You need to correct .settings.php
module config.
Important! This is a new method that requires dependency from REST 18.5.1).
<?php
return [
'controllers' => [
'value' => [
'defaultNamespace' => '\\Bitrix\\Disk\\Controller',
'restIntegration' => [
'enabled' => true,
],
],
'readonly' => true,
]
];
How to use \CRestServer in AJAX-action
In case AJAX-action must use \CRestServer for a specific task, it can be done by declaring \CRestServer as one of parameters.
Example:
public function getStorageForAppAction($clientName, \CRestServer $restServer)
{
$clientId = $restServer->getClientId();
...
}
Please, be advised, the example above cannot work via standard AJAX, because \CRestServer $restServer
is not available in it and cannot be implemented. It can be available only for the REST module. If you declare it as optional, everything will work.
public function getStorageForAppAction($clientName, \CRestServer $restServer = null)
{
if ($restServer)
{
$clientId = $restServer->getClientId();
}
...
}
How to understand, if actions are called in REST or AJAX environment?
It can happen that you need to distinguish in which context the action is presently executed: in REST or AJAX? You need to ask the controller:
\Bitrix\Main\Engine\Controller::getScope()
//possible variants
\Bitrix\Main\Engine\Controller::SCOPE_REST
\Bitrix\Main\Engine\Controller::SCOPE_AJAX
\Bitrix\Main\Engine\Controller::SCOPE_CLI
Practical advice: dependency integration
Scalar and nonscalar parameters
Let's overview example of AJAX-action with parameters:
public function renameUserAction($userId, $newName = 'guest', array $groups = array(2))
{
$user = User::getById($userId);
...
$user->rename($newName);
return $user;
}
How to retrieve method parameters?
Scalar parameters $userId
, $newName
, $groups
will be retrieved automatically from REQUEST
.
- Parameter association is case-sensitive.
- When search is unsuccessful, uses default value, if available.
- First searches in
$_POST
, then in $_GET
.
Failed search without matching results means that action won't be launched; server will send response with error message informing about missing parameter.
How to integrate objects (nonscalar parameters)?
By default, you can integrate:
The parameter name can be arbitrary. Uses class for association:
public function listChildrenAction(Folder $folder, PageNavigation $pageNavigation);
public function listChildrenAction(Folder $folder, PageNavigation $navigation);
public function listChildrenAction(Folder $folder, PageNavigation $nav, \CRestServer $restServer);
Integrating custom types
Let's start with example:
class Folder extends Controller
{
public function renameAction($folderId)
{
$folder = Folder::getById($folderId);
if (!$folder)
{
return null;
}
...
}
public function downloadAction($folderId)
{
$folder = Folder::getById($folderId);
...
}
public function deleteAction($folderId)
{
$folder = Folder::getById($folderId);
...
}
}
We have a standard AJAX-controller for a specific Folder
. However, all actions are eventually performed for an object, with an attempt to load folder and etc. It's preferable to receive Folder $folder
on input.
class Folder extends Controller
{
public function renameAction(Folder $folder);
public function downloadAction(Folder $folder);
public function deleteAction(Folder $folder);
}
And now it is possible:
class Folder extends Controller
{
public function getPrimaryAutoWiredParameter()
{
return new ExactParameter(
Folder::class, //full name for subclass to be created
'folder', //specific parameter name to be integrated
function($className, $id){ //function that creates object for integration. Retrieves specific class and $id
return Folder::loadById($id);
}
);
}
}
In JS call:
BX.ajax.runAction('folder.rename', {
data: {
id: 1
}
});
It's important that closure after $className
can indicate unlimited number of parameters needed for created object. Such parameters will be associated with data from $_REQUEST
in the same way as scalars in standard action methods.
class Folder extends Controller
{
public function getPrimaryAutoWiredParameter()
{
return new ExactParameter(
Folder::class,
'folder',
function($className, $entityId, $entityType){
return $className::buildByEntity($entityId, $entityType);
}
);
}
public function workAction(Folder $folder);
}
In JS call:
BX.ajax.runAction('folder.work', {
data: {
entityId: 1,
entityType: 'folder-type'
}
});
When you need to describe several parameters to be created:
class Folder extends Controller
{
/**
* @return Parameter[]
*/
public function getAutoWiredParameters()
{
return [
new ExactParameter(
Folder::class,
'folder',
function($className, $id){
return $className::loadById($id);
}
),
new ExactParameter(
File::class,
'file',
function($className, $fileId){
return $className::loadById($fileId);
}
),
];
}
public function workAction(Folder $folder, File $file);
}
There is one more general method for describing the integration:
new \Bitrix\Main\Engine\AutoWire\Parameter(
Folder::class,
function($className, $mappedId){
return $className::buildById($mappedId);
}
);
In detail: first, we have declared class name which subclasses we will attempt to create when encounter them in AJAX-actions. Anonymous function will created an action instance.
$className
- specific class name, specified type-hinting.
$mappedId
- value, retrieved from $_REQUEST
. Accordingly, searches folderId
in this $_REQUEST
. Parameter name that we will search in $_REQUEST
by default will be created as {variable name} + Id
.
Folder $folder => folderId
Folder $nene => neneId
File $targetFile => targetFileId
As the result, you can described the type, if the module has class, for example, Model
, from which all entities are inherited:
new \Bitrix\Main\Engine\AutoWire\Parameter(
Model::class,
function($className, $mappedId){
/** @var Model $className */
return $className::buildById($mappedId);
}
);
Subsequently, you can easily use type-hinting in your AJAX-actions, directly handling the entities.
Routing
Available starting from the Main version 21.400.0. Presently, Bitrix24 doesn't support custom modules employing their own routes in module folder.
Launch
To launch a new system of routing, you need to forward processing of 404 errors to the file routing_index.php inside the file .htaccess:
#RewriteCond %{REQUEST_FILENAME} !/bitrix/urlrewrite.php$
#RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L]
RewriteCond %{REQUEST_FILENAME} !/bitrix/routing_index.php$
RewriteRule ^(.*)$ /bitrix/routing_index.php [L]
Note: Starting from version 23.500.0, the router is connected via the method \Bitrix\Main\Application::getRouter()
.
Configuration
Files with route configuration are located in the folders /bitrix/routes/
and /local/routes/
. Connected file must be described in its [ds].settings.php[/ds][di]Bitrix Framework has several specific kernel settings without visual edit UI. This is done to prevent errors and setting update easily causing system crashes (database connection settings, caching settings and etc.).
Learn more ...[/di] in routing section:
'routing' => ['value' => [
'config' => ['web.php', 'api.php']
]],
// connects the following files:
// /bitrix/routes/web.php, /local/routes/web.php,
// /bitrix/routes/api.php, /local/routes/api.php
File format provides for returned closure, passed to the routing config object:
<?php
use Bitrix\Main\Routing\RoutingConfigurator;
return function (RoutingConfigurator $routes) {
// routes
};
Searches for matches in the same sequence, as described routes in the config.
Routes
Queries
Route description starts from defining a query method. There are 3 combinations of methods available:
$routes->get('/countries', function () {
// triggered only by GET query
});
$routes->post('/countries', function () {
// triggered only by POST query
});
$routes->any('/countries', function () {
// triggered only by any query type
});
Use the method methods for specifying arbitrary set of methods:
$routes->any('/countries', function () {
// triggers any type of query type
})->methods(['GET', 'POST', 'OPTIONS']);
Parameters
Use curly brackets to define parameter in the address:
$routes->get('/countries/{country}', function ($country) {
return "country {$country} response";
});
By default, [^/]+
pattern is used for parameters. The where
route method is used for indicating your own criteria:
$routes->get('/countries/{country}', function ($country) {
return "country {$country} response";
})->where('country', '[a-zA-Z]+');
If parameter value can contain /, use the pattern .*
:
$routes->get('/search/{search}', function ($search) {
return "search {$search} response";
})->where('search', '.*');
Parameters can have default values; in this case their presence is optional in the address:
$routes->get('/countries/{country}', function ($country) {
return "country {$country} response";
})->default('country', 'Australia');
// route will be selected when querying /countries/
// the country parameter will have a specified value
For convenience, you can set parameters that don't participate in generating the address at all:
$this->routes->get('/countries/hidden', function ($viewMode) {
return 'countries response {$viewMode}';
})->default('viewMode', 'custom');
Access to route parameter values is gained via controller parameters or current route object:
$routes->get('/countries/{country}', function ($country) {
return "country {$country} response";
});
...
$app = \Bitrix\Main\Application::getInstance();
$country = $app->getCurrentRoute()->getParameterValue('country');
Names
Assign this route a unique identifier - a name - for convenience and route lists systematization:
$routes->get('/path/with/name', function () {
return 'path with name';
})->name('some_name');
Subsequently, this will allow to query the route when generating links.
Controllers
Routing supports several types of controllers:
- Controllers Bitrix\Main\Engine\Controller:
$routes->get('/countries', [SomeController::class, 'view']);
// launches action SomeController::viewAction()
- Separate controller actions Bitrix\Main\Engine\Contract\RoutableAction:
$routes->get('/countries', SomeAction::class);
- Closures:
$routes->get('/countries/', function () {
return "countries response";
});
Arguments can be a query object Bitrix\Main\HttpRequest, current route object Bitrix\Main\Routing\Route, as well as route named parameters in any combinations:
use Bitrix\Main\HttpRequest;
use Bitrix\Main\Routing\Route;
$routes->get('/countries/{country}', function ($country, HttpRequest $request) {
return "country {$country} response";
});
$routes->get('/countries/{country}', function (Route $route) {
return "country {$route->getParameterValue('country')} response";
});
- The class Bitrix\Main\Routing\Controllers\PublicPageController is available for purposes of backward compatibility with public pages:
$routes->get('/countries/', new PublicPageController('/countries.php'));
Groups
Merging in groups
In case of similar attributes for several routes, it's recommended to merge them into groups:
$routes->group(function (RoutingConfigurator $routes) {
$routes->get('/path1, function () {});
$routes->get('/path2, function () {});
$routes->get('/path3, function () {});
});
Merging itself doesn't affect the system behaviour and is useful in case of shared attributes: parameters, prefix or name, that will be overviewed below.
Group parameters
When several routes have a shared parameter, it makes sense to move it up to a group level. This will allow avoid describing this parameter separately for each route:
$routes
->where('serviceCode', '[a-z0-9]+')
->group(function (RoutingConfigurator $routes) {
$routes->get('/{serviceCode}/info', [ServicesController::class, 'info']);
$routes->get('/{serviceCode}/stats', [ServicesController::class, 'stats']);
});
Group prefix
If the address start matches for several routes, move it as general for the group:
$routes->prefix('about')->group(function (RoutingConfigurator $routes) {
$routes->get('company', function () {});
$routes->get('personal', function () {});
$routes->get('contact', function () {});
});
The example above shows route addresses as /about/company, /about/personal and /about/contact, to avoid duplicating the general portion.
Group name
General portion of route name is generated in the similar manner as the prefix:
$routes
->prefix('about')
->name('about_')
->group(function (RoutingConfigurator $routes) {
$routes->name('company')->get('company', function () {});
$routes->name('personal')->get('personal', function () {});
$routes->name('contact')->get('contact', function () {});
})
;
The example above shows route names as about_company, about_personal and about_contact.
Generating links
Routes with name
When describing a route, set a unique name for it:
$routes->get('/countries/{country}', function () {
return 'some output';
})->name('country_detail');
And use this name for generating a link:
$router = \Bitrix\Main\Application::getInstance()->getRouter();
$url = $router->route('country_detail', ['country' => 'Australia']);
// $url: /countries/Australia
Names act as unique identifiers. When link format must be changed, i. e. its static portion:
- $routes->get('/countries/{country}', function () {
+ $routes->get('/countries/{country}', function () {
return 'some output';
})->name('country_detail');
In this case, you don't have to change all links to this route, because they use specifically name for forwarding.
Routes without name
When unique name is not specified for the route, its permissible to indicate its address in the link. Helper \Bitrix\Main\Routing\Router::url() may be useful for an available GET parameters:
$country = 'Australia';
$router = \Bitrix\Main\Application::getInstance()->getRouter();
$url = $router->url("/contries/{$country}", [
'showMoreDetails' => 1
]);
// $url: /contries/Australia?showMoreDetails=1
Loggers
Introducton
Core now have available loggers, implementing the PSR-3 interface:
- base abstract class \Bitrix\Main\Diag\Logger, implementing the PSR-3 interface;
- file logger: \Bitrix\Main\Diag\FileLogger;
- syslog logger: \Bitrix\Main\Diag\SysLogger.
Loggers are used by the log formatter \Bitrix\Main\Diag\LogFormatter that replaces placeholders as per PSR-3.
Note: Library is available starting from main version 21.900.0.
Logger Interface
\Psr\Log\LoggerInterface interface is quite simple, representing a set of logging features that support levels of logging. Levels are set by constants \Psr\Log\LogLevel::*
.
interface LoggerInterface
{
public function emergency($message, array $context = array());
public function alert($message, array $context = array());
public function critical($message, array $context = array());
public function error($message, array $context = array());
public function warning($message, array $context = array());
public function notice($message, array $context = array());
public function info($message, array $context = array());
public function debug($message, array $context = array());
public function log($level, $message, array $context = array());
}
Message can contain {placeholders}
, replaced by data from the $context associative array.
Also, a useful interface \Psr\Log\LoggerAwareInterface is available if you want to notify that your object is ready to accept the PSR-3 logger:
interface LoggerAwareInterface
{
public function setLogger(LoggerInterface $logger);
}
Loggers in Bitrix24
While implementing PSR-3, loggers in Bitrix24 have expanded functionality. Now you can:
- set a minimum logging level, below which logger doesn't show anything,
- set formatter.
File logger \Bitrix\Main\Diag\FileLogger can write messages into file, specified in constructor. When log size exceeds the set maximum, system performs a single-time log file rotation. Null - no rotation is needed. Default size: 1 Mb.
$logger = new Diag\FileLogger($logFile, $maxLogSize);
$logget->setLevel(\Psr\Log\LogLevel::ERROR);
// prints into log
$logger->error($message, $context);
// Doesn't print into log
$logger->debug($message, $context);
Syslog logger \Bitrix\Main\Diag\SysLogger is an addin to the function php syslog function. Constructor receives parameters, used by the function openlog.
$logger = new Diag\SysLogger('Bitrix WAF', LOG_ODELAY, $facility);
$logger->warning($message);
File logger uses the function AddMessage2Log and class \Bitrix\Main\Diag\FileExceptionHandlerLog, as well as logging in the Proactive protection module (security).
Starting from version 23.500.0 you can define the following in the settings .settings.php.
- specify logger for AddMessage2Log from version 23.500.0;
[ICO_NEW data-adding-timestamp="1703579666"]- redefine default logger for the File converter (transformer) module. Logger ID – transformer.Default. Starting from transformer module version 23.0.0.
[/ICO_NEW]
Message formatting
Message formatter can be set into the logger. By default, uses the formatter \Bitrix\Main\Diag\LogFormatter, implementing the interface \Bitrix\Main\Diag\LogFormatterInterface:
interface LogFormatterInterface
{
public function format($message, array $context = []): string;
}
Formatter constructor receives parameters $showArguments = false, $argMaxChars = 30
(show argument values in trace, maximum argument length).
$logger = new Main\Diag\FileLogger(LOG_FILENAME, 0);
$formatter = new Main\Diag\LogFormatter($showArgs);
$logger->setFormatter($formatter);
Formatter main task is to insert values into message placeholders from context array. Formatter can process specific placeholders:
{date}
- current time * ;
{host}
- HTTP host * ;
{exception}
- exception object (\Throwable);
{trace}
- backtrace array;
{delimiter}
- message delimiter * .
* - optional to pass in context array, substituted automatically.
$logger->debug(
"{date} - {host}\n{trace}{delimiter}\n",
[
'trace' => Diag\Helper::getBackTrace(6, DEBUG_BACKTRACE_IGNORE_ARGS, 3)
]
);
Formatter formats values from context array into convenient format depending on the value type. Accepts strings, arrays, objects.
Use
Standard object format can get a logger that supports the interface \Psr\Log\LoggerAwareInterface. Can use a corresponding trait:
use Bitrix\Main\Diag;
use Psr\Log;
class MyClass implements Log\LoggerAwareInterface
{
use Log\LoggerAwareTrait;
public function doSomething()
{
if ($this->logger)
{
$this->logger->error('Error!');
}
}
}
$object = new MyClass();
$logger = new Diag\FileLogger("/var/log/php/error.log");
$object->setLogger($logger);
$object->doSomething();
However, it's not convenient to change the code at the operational project to pass logger a desired object. Logger class provides an individual factory for this. The factory receives a logger string ID:
use Bitrix\Main\Diag;
use Psr\Log;
class MyClass implements Log\LoggerAwareInterface
{
use Log\LoggerAwareTrait;
public function doSomething()
{
if ($logger = $this->getLogger())
{
$logger->error('Error!');
}
}
protected function getLogger()
{
if ($this->logger === null)
{
$logger = Diag\Logger::create('myClassLogger', [$this]);
$this->setLogger($logger);
}
return $this->logger;
}
}
Configuration
Root section for the .settings.php file indicates loggers in the loggers key. Description syntax matches with ServiceLocator settings. The difference is that service locator is a register and this file contains the factory config.
Additional parameters can be passed to constructor closure via the Diag\Logger::create('logger.id', [$this])
factory's second parameter. Parameters allows to flexibly enable logging depending on passed parameters, including calling the methods of the object itself.
Additionally, you can indicate minimal level of logging (level) and formatter. Formatter is searched in service locator by its ID.
// /bitrix/.settings.php
return [
//...
'services' => [
'value' => [
//...
'formatter.Arguments' => [
'className' => '\Bitrix\Main\Diag\LogFormatter',
'constructorParams' => [true],
],
],
'readonly' => true,
]
'loggers' => [
'value' => [
//...
'main.HttpClient' => [
// 'className' => '\\Bitrix\\Main\\Diag\\FileLogger',
// 'constructorParams' => ['/home/bitrix/www/log.txt'],
// 'constructorParams' => function () { return ['/home/bitrix/www/log.txt']; },
'constructor' => function (\Bitrix\Main\Web\HttpClient $http, $method, $url) {
$http->setDebugLevel(\Bitrix\Main\Web\HttpDebug::ALL);
return new \Bitrix\Main\Diag\FileLogger('/home/bitrix/www/log.txt');
},
'level' => \Psr\Log\LogLevel::DEBUG,
'formatter' => 'formatter.Arguments',
],
],
'readonly' => true,
],
//...
];
Upon indicating closing constructor, its preferable to use the .settings_extra.php file, to avoid loosing code when saving settings from API.
There is \Psr\Log\NullLogger that can be installed to avoid writing if($logger)
each time before calling logger. However, you need to consider, if extra message and context formatting work is worth it.
Classes
List of classes supporting logger factory:
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.
Design template may be edited using a visual editor or working with a source code. The latter option is preferable.
The possibility of the visual editing of a template is determined by Kernel module settings (option Use WYSIWYG editor for Site Templates).
If this option is selected in the template editing form the option Use visual editor will become available.
Attention! Site design template will be edited correctly in a visual mode, if HTML tag attributes do not contain php code and also, for example, if table lines and cells are not interrupted with php code while a table is formed. If site design template code has such particulars, it should be edited only in the code mode. In addition, template editing is not recommended in case of a complex layout.
When editing a template in a visual editor, consolidated top and bottom parts of the site design are displayed. Components and functions written in the PHP programming language are inserted into HTML code and ensure the display of various types of information: metadata, page header, cascading style sheets, administrative panel, and subsequent control of this information using visual tools.
Attention! The use of composite components in site design template is strongly discouraged. Please note that
#WORK_AREA#
separator is available in the template code which is used to indicate the boundary between the top and bottom part of the design. Here, the work area of the site page will be connected. In the visual mode of the template editing work area is indicated with
. The template cannot be saved without this separator.
Template export
The special system interface features allow to export templates used in the system in the <file_name>.tar.gz
format. The Downoadcontext menu item is used.
Template import
A ready-made template must be exported as a set of files using file manager or special interface of the system. A special Load template button of the context panel is located on the page containing the list of templates.
If we click the button, the following form opens:
- Specify the file containing template for upload using the Browse… button.
In case of upload by default the template will be unpacked and placed into a folder with a name that corresponds to the name of the uploaded file (/bitrix/templates/<template_identifier>/
). For example, if the name of the uploaded file is template1.tar.gz
, the template will be automatically placed into the folder .../template1/
, and the identifier (ID) template1 will be assigned to the template itself.
- In order to assign another identifier to a template and have the template itself placed in a folder with the appropriate name, the necessary template code should be indicated in the Template code field.
- Also, an uploaded template may be linked as a default template for the selected site using the relevant option.
Template creation
A new site template can be created directly in the system with use of the New template form. To go to this form use the Add template button located on the page context panel.
Creating the template via the system interface you can:
- assign template ID (must be unique);
- assign template name;
- input template description for showing in template list;
- input template source code;
- assign used in template CSS:
- The bookmark Site styles serves for description of cascading style sheets (CSS) used on the site pages. Style description is stored in the file styles.css in the site template folder.
- The bookmark Template styles serves for description of cascading style sheets (CSS) used in the template. Style description is stored in the file template_styles.css in the site template folder.
- define set of used in template pictures and include areas.
The new template is stored in the /bitrix/templates/<template_ID>
directory (this directory is created automatically).
It is recommended to store all graphic files used in the template in the /bitrix/templates/<template_ID>/images/
directory.
Note: It is recommended to deactivate caching during template creation.
Page templates developing
The Bitrix Framework allows creating and utilizing templates for work (main) and include page areas. Templates utilizing makes work with page having a complex structure (layout) easier and faster.
All page and include area templates are stored in the /page_templates/
directory located in the corresponding template folder or in the folder .default
(if these page templates are used for all site templates).
Creating a new page in HTML editor mode you can just choose necessary page template in the list and than add page content.
List of available page templates is created with use of .content.php
file. This file is also stored in the /page_templates/
directory in the corresponding site template folder. This file contains associative array of page templates and their names intended for displaying in the list.
<?
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
$TEMPLATE["standard.php"] = Array("name"=>"Standard page (Default template)", "sort"=>1);
$TEMPLATE["page_inc.php"] = Array("name"=>"Include area for page", "sort"=>2);
$TEMPLATE["sect_inc.php"] = Array("name"=>"Include area for section", "sort"=>3);
?>
Using message files
The Bitrix Framework allows to use one site template for several sites on different languages. This possibility is realized by means of message files usage:
- in the template HTML prototype are defined areas intended for text displaying (for example, titles, ALT attributes, controls text etc.);
- then in these areas should be placed special source code that calls language files containing necessary text messages on corresponding language.
More detailed information on language files usage is given in the Message files section.
Examples of work and problem solutions
Simple examples of template application depending on various conditions
If the phone
section property is equal to Y
$APPLICATION->GetDirProperty("phone")=="Y"
If the current section is equal to /en/catalog/phone/
$APPLICATION->GetCurDir()=="/en/catalog/phone/"
If a current user is the administrator
$USER->IsAdmin()
If the template must be linked to a dynamic page (in the example, to a detailed page (card) of the goods)
preg_match("#/catalog/\?SECTION_ID=\d+&ELEMENT_ID=\d+#i",$_SERVER['REQUEST_URI']);
Connection of Favicon.ico for Various Templates
In order to have different symbols in different site templates a call of a symbol from the template must be added to header.php of the template:
<link rel="icon" type="image/x-icon"
href="<?=SITE_TEMPLATE_PATH;?>/images/favicon.ico" />
Separate Template for the Home Page of the Site
In addition to the main template for all site pages, a required template must be connected through the condition For folder or file indicating /index.php
and taking into account the application sorting index of the template.
Change of the Site Template Depending on the Time Parameter
Task: How to implement a site template change depending on the time parameter (two templates in total, but they must be changed every hour)?
For this, the current template must be changed according to the condition PHP expression.
First Option
On odd hours: ((date("H")+6)%2) == 1
On even hours: ((date("H")+6)%2) == 0
where +6
indicates the time zone.
Second Option
date("H")%2
or
!date("H")%2
Application of the Template Using Both Conditions Simultaneously
Task: How to set a template according to 2 conditions simultaneously (for a group of users and for both folder and file)?
For this, the current template must be changed according to the condition PHP expression:
in_array($groupID, $USER->GetUserGroupArray()) || strpos($APPLICATION->GetCurDir(), "/dir/")!==false || $APPLICATION->GetCurPage()=="/dir/file.php"
Template Application Only to the Files with a Specific Extension
Task: Which PHP expression should be used so that the template applies to all pages ended in .php but not .html?
Solution: change of the template according to the condition PHP expression:
substr($APPLICATION->GetCurPage(true), -4) == ".php"
Similarly for the files with the html extension:
substr($APPLICATION->GetCurPage(true), -5) == ".html"
Change of Site Header Design for Different Sections
Task: The site is divided into several sections. The idea is that each section must have its own header in the design. There are no other changes in the design.
The component Insert include area is connected to the template:
<div id="header">
<?$APPLICATION->IncludeComponent("bitrix:main.include", ".default", array(
"AREA_FILE_SHOW" => "sect",
"AREA_FILE_SUFFIX" => "headerinc",
"AREA_FILE_RECURSIVE" => "Y",
"EDIT_TEMPLATE" => "sect_headerinc.php"
),
false
);?>
</div>
The header code of each section will be stored in the file sect_headerinc.php. The parameter "AREA_FILE_RECURSIVE" => "Y"
means that the same header will appear in all subsections of this section, unless the parental sect_headerinc.php is specifically shut off in any underlying sections.
Specific of working with AJAX
Using the ajax mode has its specifics. For the navigation string to have its name at the page opened via ajax, the template must have the element with id="navigation"
. The div
is optional: it can be span
, h1
, p
and so on.
Similarly, the header must have the id="pagetitle"
element available.
Using message files for localization
Language communication is a group of phrases in different languages having the same meaning.
The Bitrix Framework allows using the same template for several sites on different languages. This opportunity is realized by means of the message files mechanism:
- areas intended for text displaying are defined in the template HTML prototype (for example, titles, ALT attributes, controls text etc.);
- then, special source code should be placed in these areas that calls language files containing necessary text messages on corresponding language.
For example, table headers, button inscriptions, component messages, etc. may be created using language communications.
Note: language communication feature is also used to support multi-language interface of the administrative section of the system.
Related links:
Implementation
-
The
/lang/
folder is created in the a site template directory:
/bitrix/templates/<>/lang/
- The
/lang/
folder has the folders with the utilized languages identifiers: /en/
, /de/
, /fr/
, and etc. For example:
/bitrix/templates/<>/lang/fr/
- The languages identifiers corresponding to message files are stored within the folders. These files are characterized by the following properties:
-
the message file name is equal to the file name where this message file is called. For example, if a message file call is implemented in the template header (file
header.php
) then this message file must have name header.php
.
- message list in the file is stored the following way:
<?
$MESS ['COMPANY_NAME'] = "Company Name";
$MESS ['MAIN_PAGE'] = "Home page";
$MESS ['PRINT'] = "Print version";
$MESS ['AUTH_LOGIN'] = "Authorization";
$MESS ['RATES_HEADER'] = "Currency rates";
$MESS ['SEARCH'] = "Site search";
$MESS ['SUBSCR'] = "Subscription";
?>
-
At the beginning of file, where the message file call is implemented, the following function is added:
<?
IncludeTemplateLangFile(__FILE__);
?>
The IncludeTemplateLangFile(__FILE__)
connects a message file for the current language.
-
All text in template is replaced with the function calling the corresponding messages:
<font class="search"><?echo GetMessage("SEARCH");?></font>
The code (ID) of calling message is used as the GetMessage()
parameter. The function verifies if the connected message file contains necessary message. If the necessary message exists then it is shown to page visitors.
Localization
The message files localization can be implemented the following ways:
- manually (when the site administrator searches and translates necessary message files);
- with use of the Localization module tools.
The Localization module provides users with a handy interface for text messages search and translation. The Localization module allows:
- to look through the message files distribution among the system files;
- define the number of untranslated text messages for every file;
- proceed to the necessary text messages translation.
In order to view distribution of language communications by files, go to the page Interface localization (Control Panel > Settings > Localization > Browse Files) and click the button Show differences on the context panel:
The number of untranslated phrases used in a file or directory for each language is marked in red.
Note: For big projects, it is not recommended to search for differences in all the product files; it should be done in stages, for each folder. Otherwise, the error 504 Gateway Timeout may occur.
In order to translate language phrases, a file for which language communications are used should be selected, and a translation of the communication to a relevant language should be entered:
Note: This form shows translations into the languages of the same encoding.
I.e. if the ISO-8859-1 encoding is set for the English and Spanish language settings and Windows-1251 for the Bulgarian language, the form will contain fields to introduce the translation of the message into English and Spanish. To translate the phrases into Bulgarian, the interface must be switched to Bulgarian.
You can quickly start translating the missing messages directly from a page on which these messages are used (both in the public section and Control Panel). To activate the quick translation mode, use the Show Language File button (it is visible if the corresponding option is checked in the Localization module settings)
or add the parameter show_lang_files=Y to the page address:
As the result, list of localization files used on the page will be displayed at the bottom of the page:
File names are shown as links, which can be clicked to open the language file for editing. In the end of the list you will find a search field. You can use it to find a required message (exact match).
Information: All localized phrases can be collected with use of the special tool. Collected files can be placed in the
Update system for downloading by other users. Please refer to the
Localization Guide for more details.
Change of Phrases in Components and Modules
Sometimes during developing, it’s necessary to change some words or phrases in the system components or modules.
The main aim of this technology is to allow the developer to modify language phrases after connecting with the language file.
A list of changes is kept in the file: /bitrix/php_interface/user_lang/<language_code>/lang.php
In this file, the elements of the array $MESS
must be determined as $MESS['language file']['code of the phrase'] = 'new phrase'
', for example:
<?
$MESS["/bitrix/components/bitrix/system.auth.form/templates/.default/lang/en/template.php"]["AUTH_PROFILE"] = "My Profile";
$MESS["/bitrix/modules/main/lang/en/public/top_panel.php"]['top_panel_tab_view'] = "View";
$MESS["/bitrix/modules/main/lang/en/interface/index.php"]['admin_index_sec'] = "Pro+Pro";
?>
The first line changes the text of the link in the authorization form component; the second line changes the name of the tab of the public panel; and the third line changes the line for index page of the control panel.
Theoretically, there can be problems with maintenance of this file, if the code of the phrase or arrangement of the files are changed, but in practice we try not to do that, because it will be additional work for localizers.
Localization Archive Import/Export
Importing and exporting into CSV file
CSV file Import/Export feature is used to handle localization files manually.
- Go to the required folder (for example, activity) to the Export files tab (Settings > Localization > Browse files) select entirely replace language files to export all localization strings of this folder (module), or export only new messages to export only translated strings into CSV file.
- Click Export and the created localization CSV file window ill appear.
By default, the name of the CSV file will consist from the folder (or module) names, included into the localization. Otherwise, the file will have the name bitrix_activities.csv
- Edit the required strings and save all changes in the same CSV file.
- In the similar manner, you can select the way of adding localization into the system via Browse files: import only new messages or entirely replace language files, select CSV file and click Import:
The module has been translated.
Collect localization strings
After the system localization archive was created, a full localization package for each system language can be created.
- Go to the page Collect strings (Settings > Localization > Export and Import).
- Select the required options in the Collect strings tab:
- Select language - indicate which language phrases are to be exported;
- Localization version date - date format YYYYMMDD for the localization archive;
- Convert To UTF-8 - select the option to convert into a national encoding (UTF-8) for all localization files (by default, the files will be collected in the current site encoding otherwise);
- Pack Files (tar.gz) - option to compress files into the archive of tar.gz format;
- To launch the collection of localization package, click the Build Localization button.
After the localization archive is collected, a link with the archive location is provided (if the option to build archive with tar.gz format is selected) or to files of the complete archive.
To import the localization archive into the system:
- Go to the page Browse files (Settings > Localization > Export and Import):
- Select the required options in the Collect Strings:
indicate the language phrases of which language are to be imported
- Localization archive (with tar.gz format);
- Select Language (the required language can be already installed in the system, or can be created via the Add link);
- Convert from UTF-8 - select this option if your project is in national encoding and the localization archive to be imported is in UTF-8;
- To launch the localization package import into the system, click the Import Localization button.
Related Links:
Bitrix Framework Localization Guide.
Editable areas
Editable area is a specially designated area on the site page that may be edited separately from the main contents of the page.
Include areas serve to place reference information, forms (subscription, voting, surveys, etc.), news, and any other static and dynamic information. Also the areas indicating copyrights, graphic links, contact information, company logo, etc. may be performed as an include area.
The Bitrix Framework allows creating different types of editable areas:
- editable area of the certain file – displayed only when viewing the current document;
- editable area of the certain section – displayed for all documents of the given section;
- editable area of site template – displayed in predefined areas of site design template.
The number of editable areas can be extended. In this case you need to implement the corresponding modifications in site template. For example you may need to include php code calling the additional editable areas.
Besides that, editable areas can be displayed according to any program conditions. For example, editable areas can be shown only for exact user groups or only for authorized users, and etc.
You can turn on the special mode that enables to view the include areas by clicking the Edit Mode button in the administrative toolbar. As the result all editable areas will be spotlighted as separate blocks. Links allowing to proceed to area editing will be displayed in top left conner of each block.
Managing editable areas
The contents of include areas are stored in separate PHP or HTML files. Areas for pages or sections are saved with a suffix. E.g., in the product files supplied the suffix _inc is used as a designation of the include area for a page, and the include area for a site section is stored in the sect file with a suffix added (e.g., sect_inc.php
).
Important! The file with include area must be saved in the same directory with the page for which it was created. Include area for a section must be saved in the folder of this section.
Areas in the site design template should be connected using the component Insert include area or using the function of IncludeFile()
.
The suffix used for the designation of include areas is determined by the option having the same name in the settings of the component Insert include area. The component may be located not only in the design template but also on the site pages, provided that file suffix must be set as different from that used in the template.
Note: The type of the include area is determined by the parameter
Show include area of the component
Insert include area.
If the component is located in the site design template, the information
from file will be displayed on the entire site. Parameter setup is available only for users with the operation
Edit PHP code (
edit_php) at the kernel module
access level.
Include Area Layout
In order to place the include area proceed as follows:
- Open a site template or a page in a visual editor for editing.
- Add the component Insert include area (bitrix:main.include) to the site template (or page body) and set up its parameters.
Using the editable areas when integrating into the site design has limitations. They are related with the size, assigned for a slot in which the component is allocated. If a text, an image or any other object has the size larger than the slot, assigned for the the component, the site design will become misaligned.
Using the editable areas allows to manage not only the text. An image instead of a text can be placed in such an area (or a Random photo component) and receive an custom appearance for each section. With this, the design variation will be "dynamic" and can be modified.
Creating and Editing the Include Area
Include areas may be created:
- From the administrative section in Site Explorer (Control Panel > Content > Site Explorer > Files and Folders) by creating a file with the relevant name;
- From the public section of the site in the edit mode. In the places where include areas are supposed to be displayed, the icons will be shown for quick access to creation of these areas.
Note: A file of the include area will be created and named in accordance with the suffix indicated in the
component settings – for the option
for section, or a file name – for the option
from file.
After the command of Add Area is selected, visual editor will be launched to create the contents of the include area. If the command of Edit Area as PHP is selected, it will become possible to add an area in the PHP code mode.
Likewise, it is possible to switch to editing of include areas as follows:
- Directly from the public section of the site in the edit mode
- Or from the administrative section by opening relevant file in Site Explorer for editing.
Attention! If the option
from file is used as include area, you have to check that the file is connected from the system and not accessed directly. It should be done using the following line:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
.
Example of contents of included file:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
<div class="bx-main-title">World Book</div>
<span class="bx-main-subtitle">Books</span>
Templates of Include Areas
Editable areas are created on base of templates stored in folders with the names /page_templates/:
/bitrix/templates/.default/page_templates/
- in case if this editable area template is utilized for all site templates;
/bitrix/templates/<template_ID>/page_templates/
- in case if for this site template are used individual editable areas templates.
If you want the editable areas to be added to the list of available templates of WYSIWYG editor, add the editable area templates manes in the file .content.php
.
The file .content.php is stored in the folder /page_templates/
located in the corresponding template directory.
Example of file contents:
<?
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
IncludeTemplateLangFile(__FILE__);
$TEMPLATE["standard.php"] = Array("name"=>GetMessage("standart"), "sort"=>1);
$TEMPLATE["page_inc.php"] = Array("name"=>GetMessage("page_inc"), "sort"=>2);
$TEMPLATE["sect_inc.php"] = Array("name"=>GetMessage("sect_inc"), "sort"=>3);
?>
Also the name of utilized template can be defined with use of special parameter while assigning editable area to the site. (see the code spotlighted with blue color in the example below).
Assigning editable areas to site templates is implemented with use of the function IncludeFile(), places in the necessary areas of site template:
<?
$APPLICATION->IncludeFile(substr($APPLICATION->GetCurPage(),
0, strlen($APPLICATION->GetCurPage())-4)."_inc.php", Array(),
Array("MODE"=>"html", "NAME"=>GetMessage("PAGE_INC"), "TEMPLATE"=>"page_inc.php"));
?>
<?
$APPLICATION->
IncludeFile($APPLICATION->GetCurDir()."sect_inc.php", Array(), Array("MODE"=>"html",
"NAME"=>GetMessage("SECT_INC"), "TEMPLATE"=>"sect_inc.php"));
?>
Note: If no variable is indicated, the system will use a default template, which is the page template.
Deleting Include Areas from the Demo Template
The line responsible for connecting an include area must be commented out or deleted in the template code. Normally these are files ending with _inc.php.
The line may look like the following:
<?
$APPLICATION->IncludeFile(
substr($APPLICATION->GetCurPage(), 0, strlen($APPLICATION->GetCurPage())-4)."_inc.php",
Array(),
Array("MODE"=>"html", "NAME"=>GetMessage("PAGE_INC"), "TEMPLATE"=>"page_inc.php"));
?>
Example of Using Include Areas
Task: The site is divided into several sections. The idea is that each section must have its own header in the design. The design has no other changes. What is the best way to implement the change of section headers?
Solution: The component of the "Include Area (for section)" is connected to the template:
<div id="header">
<?$APPLICATION->IncludeComponent("bitrix:main.include", ".default", array(
"AREA_FILE_SHOW" => "sect",
"AREA_FILE_SUFFIX" => "headerinc",
"AREA_FILE_RECURSIVE" => "Y",
"EDIT_TEMPLATE" => "sect_headerinc.php"
),
false
);?>
</div>
Header code for each section will be stored in the file sect_headerinc.php. The parameter of "AREA_FILE_RECURSIVE" => "Y" means that the same "header" will appear on all subsections of this section, if the parental sect_headerinc.php is not specifically shut off at a lower section level.
Navigation tools
Navigation tools shall be included in the site template. The use of special components is recommended for their implementation.
Site Map
Site map is one of the navigation elements on the site that is a standard element of the modern site. As a rule, the page headers contained in the list constitute links to these pages.
Creation of a Site Map
The component of Site map (bitrix:main.map) is used to create the site map. The component is located in the folder Content > Site map of the panel Components 2.0 of the visual editor.
The map is built up based on the menus used in the system. The menu types are specified in the settings of the Kernel module in the Site Map section (Control Panel > Settings > System settings > Module settings).
Note: Several types separated by a comma may be used simultaneously as a basis for building up the reference level and branches of the site map.
- Create a separate page and place the component of the Site map.
- Setup the component parameters:
- Maximum number of nested levels - is the depth of the section nesting that must be reflected in the menu. If the number in this field is more than 4, the site structure should be thought over again.
- Number of columns – indicate how many columns will be used to create the site map image. This number largely depends on the site design, nesting level used, and site structure. Allocation of map points in the columns is based on the first level menu options. I.e., if one of the menu options has a slightly deeper structure compared to other options, then in case a deep nesting level is chosen on the site map you can obtain a long column for this point and short columns for other points.
- Show descriptions – descriptions of sections (physical folders) will be displayed. A description of information block sections is not displayed.
Note: If a site folder is specified incorrectly (e.g., there is no closing slash) in the site settings (Control Panel > Settings > System settings > Websites > Websites > _ site _), the component will not be able to create the site map.
Examples of Work
How to include site sections that are not specified in the menu into the site map?
Normally, the customization of a component template is required to do so, but there is also another solution. The simplest one is to prepare their own menu type for these sections and add it, separated by a comma, in the settings of the main module in the menu types option of the site map menu.
Menu
Menu is a design element that constitutes the main site navigation tool.
This section contains description of the main principles of creating and managing site menu, including description of file with menu data and menu template structure. Also the possibility of dynamic menu usage is mentioned.
Menu Types
Menu types are the menu organization principle. By default, two menu types are used in the installation package – Top and Left.
Several menu types are possible, depending on the site function: top, left, and bottom, etc. In each menu component, two menu types may apply: one as the main menu, and another as an additional menu, provided that multi-level templates are used.
In the most general case, the site has one “main” menu corresponding with the top most hierarchy level and is reflected in all the site sections. In addition, the system often uses a “secondary” menu (or a second-level menu) that includes links to the subsections and documents of the current section.
The menu in the system is hereditary. This means that if a certain menu type is selected for a menu component in the template, this menu will be translated downwards to all site sections and pages with this template, unless a proper menu was created for these sections and pages. This mechanism is convenient for the site main menu; normally it is assigned the Top type.
Note: If a higher menu must not be reflected in the lower level section, just create a menu in the relevant section without menu options.
As a rule, a section menu shall be created for each section and translated to all the pages of the section. If necessary, a proper menu can be created for each subsection and a proper type applied thereto.
Types of used on a site menu are defined in the administrative section on the Site Explorer module page.
For example, let us assume that the system uses two menu types:
- Left menu – “left” type;
- Top (main) menu – “top” type.
A menu type (defined on this page) will be utilized as prefix both for file with the menu template (e.g., top.menu_template.php) and for file with section menu items (e.g., .top.menu.php
). Besides the menu type is used calling necessary menu in site template.
Note: Menu types can be defined for every site individually.
The menu types are assigned arbitrarily. But to make work with menu more convenient it is recommended to use meaningful designations for menu types. For example, top
, left
, bottom
.
Menu Building and Display
Generally, the task of menu creation consists of the following:
- marking HTML elements to build up the menu;
- creating menu template (creation of the Menu component template);
- including a menu display function (Menu component call) in the general template (“prologue” and “epilogue”);
- completing menu in accordance with the site structure.
Menu Structure
Any menu on the site consists of two parts:
- $aMenuLinks data array determining menu composition which gives names and links for all menu options. Data array is managed through an administrative interface;
- template of an external display of the menu. The menu template is a PHP code determining the external aspects of the menu (Menu component template). The menu template processes the data array giving an HTML code in the output.
Menu Data Array
Data for each menu type are stored in a separate file. Its name has the following format: .<menu type>.menu.php
. For example, to store the data of the left type menu, the file .left.menu.php
will be used, and to store the menu data of the top type – the .top.menu.php file
.
The menu is inherited hierarchically. The menu files are located in the folders of the site sections where the display of the relevant menu types is required. If no relevant menu file is created for this section, the system searches for the file in the catalog of the upper level.
For example, since the main menu (it is a top type menu in the product demo version) must be displayed in all the sections, the file of this menu shall be located only in the root catalog of the site.
Accordingly, the second level menu (it is a left menu in the product demo version) is displayed separately for each section of the site. That is why a proper file for this menu type must be created in the folder of each section.
Another example: a visitor is in the section /en/company/about/
. In order to display a left type menu, the menu file will be searched in the following order:
/en/company/about/.left.menu.php
/en/company/.left.menu.php
/en/left.menu.php
/.left.menu.php
If the menu is found in one of the catalogs, the search will be stopped and the next catalogs will not be searched.
The Bitrix Framework system also permits you to create a dynamic type menu. I.e. the data array of such a menu is generated automatically based on certain data obtained using the source code. This code must be stored in the folder of a relevant site section in a file named .<menu type>.menu_ext.php.
.
The principal task of these files is to manipulate a $aMenuLinks array. These files cannot be edited visually in the Site Explorer module, which is why they will not be edited accidentally during the visual editing of the menu. Use the Menu items (bitrix:menu.sections) component to create this file.
Note: The paragraph above describes only the adding names of information block sections to the created menu. This variant is not suitable, for example, for adding forum names to the menu.
Attention! If catalog sections without computer numerical control are used as menu options, it is necessary to indicate variables in significant variables of a query.
A drop-down menu of the Products section offered in the demo version of the product may be a good example of such a menu. Here, upper menu options are created normally, and the remaining options (Sofas & Loveseats, Office Desks and Chairs etc.) are formed dynamically.
In this case, the names of the groups of the Products catalog created based on the information blocks are used as menu options.
The source code used to generate the menu is stored in the file .left.menu_ext.php
in the folder /products/
.
The following standard variables may be used in the files .<menu type>.menu.php
:
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
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');
if( 'Y' != $APPLICATION->GetPageProperty('hide_menu') ){
ob_start();
echo '<b>verification of the deferred menu!</b>';
// ....here, the menu is displayed using a component or otherwise.... //
$APPLICATION->SetPageProperty('menu', ob_get_clean() );
}
The menu itself is “displayed” in the footer if the hide_menu property value is not set to Y. It will not be actually displayed in the footer. Rather, it will go to the menu property in which the display can be “deferred” “higher” up the code using ShowProperty. If the menu is blocked, the property value of the menu will be empty, and the template will not display anything. If the menu is not blocked, the phrase “verification of the deferred menu!” will be displayed for this example (where $APPLICATION->ShowProperty('menu')
is specified).
Menu and cache
Quote: I have ran out of the disk space completely. Took a look and it turned out, that the bitrix/managed_cache/MYSQL/menu
folder's size is 16Gb! There are 2 menu at the site: horizontal - pagewise navigation and vertical - surfing via the catalog sections.
Menu cache depends on the page URL. If the are many pages, the cache can be quite large as well. In this case, the more effective solution would be to disable cache in the component menu.
Navigation chain
Navigation chain (breadcrumbs) is a sequential list of links to site sections and pages that shows the level of “immersion” of the current page into the site structure.
A navigation chain helps to display the nesting level of the current page, site section or goods catalog, starting from the home page to the current document. Values indicated in the navigation chain can be specified for each section or document individually.
Navigation chain provides visitors with tools for easy orientation on site. It allows going to the main site page or going up on one or more levels in site hierarchy.
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:
- 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.
- 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> > </span></li>';
$title = htmlspecialcharsex($arResult[$index]["TITLE"]);
if($arResult[$index]["LINK"] <> "")
$strReturn .= '<li><a href="'.$arResult[$index]["LINK"].'" title="'.$title.'">'.$title.'</a></li>';
else
$strReturn .= '<li>'.$title.'</li>';
}
$strReturn .= '</ul>';
return $strReturn;
?>
Note: When connecting the navigation chain using the function
ShowNavChain(), its template may be additionally set for a separate site section.
To do so, the variable $sChainTemplate must be determined directly in the file .section.php where the complete path to the navigation chain display template is set. Example:
$sChainTemplate = "/bitrix/templates/demo/chain_template.php"
The navigation chain template may also be set when using the function ShowNavChain() as one of the parameters of the function.
$APPLICATION->ShowNavChain("/bitrix/templates/.default/chain_template_bottom.php")
Examples
How to Add Own Point to the Navigation Chain?
Use the method AddChainItem() :
<?
$APPLICATION->AddChainItem("About us", "/about/index.php");
?>
Only infoblock names are shown in the navigation chain. In addition, page headers (e.g., contacts) and “Home” header are not displayed. What is the problem?
The value N
must be set in the page properties in the field NOT_SHOW_NAV_CHAIN
of the page properties.
For the remaining pages, it is necessary to check if the headers are set in the section properties. It is section headers that are used to create points of the navigation chain.
The home page can also fail to display due to the incorrectly set option Ordinal of item from which to build the chain in the parameters of the Breadcrumb component: 0 (default value) means that the navigation chain will be built starting from the site root. If the field Path for which the navigation chain is to be built is completed, the point number is included in the indicated path.
The navigation chain has the catalog name repeated twice: Home/Catalog/Dresses/Dresses.
The first link to the catalog looks as follows: /catalog/dresses/
and the second:
/catalog/dresses/section.php?SECTION_ID=0
The first name is taken from the properties of the dresses directory (file .section.php) and the second is determined by the component located on the page (in this case, the address of the page section.php).
Note: For example, the parameters of the News (bitrix:news) component contain the relevant options: Include information block into navigation chain and Add Section name to breadcrumb navigation.
The element repetition in the navigation chain can also be caused by the presence of several components on the pages that are set to add their points to the chain.
How to ensure that the navigation chain has only a physical section present?
The complex component News is used to display a section from the infoblock. Apart from the physical section, an unclickable name of the section of the infoblock proper appears in the navigation chain. Neither section nor infoblock is included in the navigation chain settings.
Add the following line in the template:
"ADD_SECTIONS_CHAIN" => $arParams["ADD_SECTIONS_CHAIN"],
Advertisement
Several different types of advertising may be shown on the site. It may be both standard banner advertising or written advertising areas. Advertising may be permanent or shown with a certain probability set by the administrator. An advertising show may be regulated by specific site sections etc. Advertising is created and managed using tools of the Advertising module.
Advertising is shown in specifically allocated areas of the site design template – advertising areas.
A banner may be connected to the advertising area using the Banner component (bitrix:advertising.banner). The component displays the banner of a set type.
Note: This component does not take into account the targeting by keywords. If the targeting must be taken into account, the function
$APPLICATION->ShowBanner()
should be used. The code used to connect a banner in the advertising area using PHP function of
ShowBanner()
is as follows:
<?
//--- Example of placing an advetising area in the left part of the design.
//--- Any other type can be selected similarly:
//--- TOP, BOTTOM, COUNTER,… (first parameter of the function)
//--- Both predefined and user-defined types can be used.
//--- Other two optional parameters can be used to specify the HTML code
//--- that is to wrap the advertising area.
$APPLICATION->ShowBanner("LEFT", '<div align="center">', '<br></div><br>');
?>
The type of advertising available for display in this area must be determined for each advertising area. Advertising banners of the type LEFT1
, LEFT2
, and TOP
are used on the image shown above.
Banner types
Advertisement type is a conventional name for a group of advertising banners that share a common feature. For example:
- Show place – all banners must be displayed in a specific place on the site page.
- Subject matter (for example, the same products are advertised).
- Advertiser (advertising for a specific company).
- etc.
The name of the advertising block type is set at the discretion of the administrator. For example, the TOP
, LEFT
, BOTTOM
, etc. type may be set for the advertising set at the top of the page.
Important! Banners from the same group should have the same size. It will permit you to prevent page deformation during display of the advertising.
Advertisement types are managed through the administrative interface of the Advertising module (Control Panel > Services > Advertising > Banner types):
An advertising banner or a list of banners of a selected advertising area can be managed directly from the public part of the site. For this, switch to the Edit mode and use one of the advertising area control buttons:
Controlling advertising shows using keywords
One of the methods that can be applied to control the banner shows and to target the advertising precisely is using keywords. The distinctive advantage of this method is that it allows to drive the advertising campaign aimed to reach the well-defined target group among your visitors.
Using keywords, it is possible to:
- Organize the display of advertising aimed at a specific group of site users. I.e. display advertising on the pages visited primarily by these users or pages that the subject matter may be of interest for this group of users.
- Restrict the advertising shown on a site page, for example, to the extent the advertising content is connected to the information shown on the page.
Control Mechanism
Advertising on the site pages is controlled using desired and mandatory keywords of the site page and a set of keywords of the advertising banner. Two types of special keywords are used to manage the advertising shown on pages:
- banner keywords;
- page keywords:
- desired: if a site page is assigned the desired keywords, all the banners that have at least one matching keyword in their keyword sets can be shown on that page.
If no banners with the matching keywords can be found, then the page will show banners that are not assigned any keywords. In this situation, the system uses own standard algorithm to select banners to be displayed.
- required: if a site page is assigned the required keywords, all the banners that have all keywords in their keyword sets can be shown on that page.
If no such banners can be found, then the page will show banners that are not assigned any keywords. In this situation, the system uses own standard algorithm to select banners to be displayed.
If the system fails to find banners satisfying any of the keywords, the page will show banners for which no keywords are set. These banners are selected and displayed based on a standard system algorithm (permitted/forbidden pages of contracts and banners, user groups, banner types, etc.).
The general procedure for banner display on specific pages is as follows:
- Determine the advertising available for display on specific site pages.
- According to the tasks at hand, sets of specific keywords are determined for site pages.
- A required set of keywords is set in the settings of advertising banners.
Banner keywords are set in the Keywords field on the page of banner creation/editing, Targeting bookmark (Control Panel > Services > Advertising > Banners).
Desired keywords of the page are managed using a special property adv_desired_target_keywords
. Desired keywords of the page may be set using the function of SetDesiredKeywords.
Please note! By default, if page code does not provide for any keywords for advertising, the function of SetDesiredKeywords
is deemed using properties of the page adv_desired_target_keywords
as a value parameter. If it is not set, the value of the keywords property is used as a function parameter.
The SetDesiredKeywords
method is called automatically at the time of page assembly; it must not be additionally called in the file header.php if there is no need to redefine the keywords for the advertising shown.
The required keywords of the page are set using a preset function of SetRequiredKeywords of the system.
Managing Template Service Data
Editing Service Areas
If a visual editor is used for creating (editing) a template, the service areas can be managed in a special form. This form can be accessed using the button Edit template areas located in the editor’s panel.
Area editing form consists of two bookmarks: Top Area and Bottom Area.
The top part of the template down to the <body>
tag is edited in the first bookmark. You can set the contents of the area with default values. To do so, press the button to insert a set of standard functions into the form, such as page encoding, header and page metadata display, connection of style files, etc. Similarly, the bottom part of the template is edited in the Low part bookmark in which the contents can also be set using the default values.
Attention! The use of the functions of ShowMeta()
, ShowTitle()
, ShowCSS()
etc. permits you to initialize separate elements directly from a page script or from a component. E.g., page header may be added after displaying script output. Thus, if in earlier version’s page header initialization was required prior to connecting the main design, now the page header can be set directly from code in the page working area.
Managing Page Encoding
Support of any number of languages is one of important features of Bitrix Framework. The system permits:
- Using a multi-language interface in the administrative section.
- Creating any number of sites (depending on the license in different languages within one system.
Note: The number of languages used in the system does not depend on the number of sites.
This section contains information about the use of encoding for the correct display of information on the site pages. Having studied the section you will get an idea about the main principles of use and also about the ways to setup and connect different encodings.
Use of Encodings
In order to display national characters correctly, the appropriate encodings are used. When showing a page, the browser recognizes the encoding used and displays the characters accordingly.
The list of code tables used to display the characters of the English, and German languages is provided below:
Language | Encoding |
English (en) | windows-1252, iso-8859-1, latin1 |
German (de) | windows-1252, iso-8859-1, latin1 |
|
The complete list of encodings used for different languages is provided in the product documentation.
Note: Starting from version 7.0 the product supports a universal UTF-8 encoding. Using this encoding, the site contents may be simultaneously displayed in different languages.
If UTF-8 is not used but the system requires a combination of various languages, a code table must be determined for each language that will be used to display text messages.
Attention! Page encoding and database table encoding must be the same.
Encoding shall be set separately for the administrative and public sections:
- The encoding used in the public section shall be set up for each site (Control Panel > Settings > System settings > Websites > Websites):
The encoding is set based on the language used on the site. In addition, when setting the language parameters, the time and data format can be set, which will permit you to display these data in the public section correctly (for example, during display of news, catalog of goods, etc.).
- The encoding for the administrative section of the site is set up using the form of language parameters used in the system (Control Panel > Settings > System settings > Language Parameters > Regional Options and Culture).
In addition, the time and data format may be determined when setting the language parameters.
The indicated format will be used for the display of the date and time in the administrative section of the site.
Determining the Current Encoding
The current encoding used in the public section of the site is determined using the php constant LANG_CHARSET
substituted to the site template header area.
When applying a template to the site, the value of encoding parameter set in the site settings is requested. The constant LANG_CHARSET
is given a value equal to the value of the encoding parameter.
Example of the code used to establish page encoding is provided below:
< head >
…
< meta http-equiv="Content-Type" content="text/html; charset=< ?echo LANG_CHARSET? >" >
…
< head >
Managing Document Header
The use of header helps to draw users’ attention to a site page and also to give a general idea about its contents and purpose. Additional page headers should be set to reflect as a header of the browser window so that the user can accurately determine the window where the page they need is open.
Page header and web browser window may be created and changed both using the tools of the administrative and public section.
Note: Additional header for web browser window can be set using the
title
property reserved in the product.
For convenient use of the property its name must be set (for example, Additional header (web browser window header)) in the Site Explorer module setting.
Please refer to the page Work examples for more information about setting up several headers.
Note: Some components may independently set a header and it should be taken into account.
Managing Header in the Code
Setting up and Displaying Page Header
Page header may be set up as follows.
- When editing the document with the help of the incorporated editor (in the Text, PHP, or HTML mode). In this case, the page header is set by way of substituting the following function in the code:
<?
$APPLICATION->SetTitle("About Company");
?>
- Document header can be set dynamically by one of the components located on the page. In the example below, the name of the current information block is used as a page header:
<?
$arIBlock = GetIBlock($ID, "news")
$APPLICATION->SetTitle($arIBlock["NAME"]);
…
?>
The document header is displayed by placing the function of ShowTitle()
at the place of page header display:
<H1><?$APPLICATION->ShowTitle()?></H1>
If the function of ShowTitle()
uses the parameter false for page header display, it means that there is no need to check the value of the title property to set the page header (e.g., Additional header (web browser window header)).
<H1><?$APPLICATION->ShowTitle(false)?></H1>
I.e. the header set by the SetTitle()
function will be used as a page header.
Note: For more information about the ShowTitle(false) function, please refer to the page
Work examples.
Set Up and Display of Web Browser Window Header
Web browser window header is displayed using the ShowTitle()
code located in the <head>
area of the site design template.
<head><title><?$APPLICATION->ShowTitle()?></title></head>
There are various ways to set web browser window header. By default, the header is set in the title property of the page (e.g., Additional header (web browser window header)). If the value of this property is not indicated the browser window header will be set as equal to the current page header.
Note: Browser window header can also be set using the function of
SetPageProperty()
or in the public part of the site. For more details, please refer to the page
Work examples.
Attention! If several identical header setting functions or components are located on the page, the header set in the last (the bottommost on the page) function/component will be used.
Work Examples
In this lesson we will review, stage by stage, the procedure for creating a page with different headers of the browser window and the page itself. Site template may require changes during this work.
- Set a page header from the interface or using the function of
$APPLICATION->SetTitle(“Page title”)
:
<? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
$APPLICATION->SetTitle("Page title"); ?>
....
<? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php"); ?>
- Add additional header through interface or using the function of
$APPLICATION->SetPageProperty('title','Browser title')
:
<? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
$APPLICATION->SetTitle("Page title");
$APPLICATION->SetPageProperty('title','Browser title'); ?>
...
<? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php"); ?>
The function of $APPLICATION->SetPageProperty()
in the system has a priority over the function of $APPLICATION->SetTitle()
. That is why the browser and page headers will display the contents of the function exactly:
- If different headers are to be used for page and web browser window, check the parameters of the CMain::ShowTitle() method displaying page header in the site template.
Replace:
$APPLICATION->ShowTitle()
with:
$APPLICATION->ShowTitle(false)
In this case, the value of page property SetPageProperty('title','Browser title')
will be ignored, and the header set by the function of SetTitle()
will be used as a page header instead.
Let us review the difference in operation of the function $APPLICATION->ShowTitle()
with the false parameter using the following example, without changing the template code:
<? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
$APPLICATION->SetTitle("Page title");
$APPLICATION->SetPageProperty('title','Browser title');
$APPLICATION->ShowTitle();
<br>
$APPLICATION->ShowTitle(false); ?>
....
<?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
Managing Metadata Values
As a rule, the main purpose of using metadata is the optimization of site for search systems. Search systems use metadata for indexation of site documents.
Keyword mechanism and description of site pages and sections are good examples of product metadata management. By default, the installation package of the product includes the management of exactly these two types of metadata (similarly, the list of possible options may be extended).
Managing Metadata Values through Visual Interface
In order to have a possibility to manage metadata values, the relevant properties must be created in the settings of the Site Explorer module (Control Panel > Settings > System settings > Module settings):
Important! The names of property types used for managing page metadata must coincide with the names of meta tags in HTML. E.g., keywords and description property types must be the same as names (name) of the relevant meta tags: keywords and description.
Note: A set of properties can be set separately for each site that works under control of the system.
Attention! The values of properties set for a folder will be used by default for all pages and subsections of the relevant site section (if no proper values of these properties are set for them).
More detailed information about properties management is available in the Properties of pages and folders section.
Managing Metadata in a Code
In order to display the metadata values in the page code the function of ShowMeta()
located in the prologue of the site design template must be used:
< head >
…
< ?$APPLICATION->ShowMeta("keywords")?>
< ?$APPLICATION->ShowMeta("description")?>
…
</head>
Let us assume that the following values are set for keywords and description properties of a page:
The function of SetPageProperty()
will apply the values of these properties to a page:
<?
$APPLICATION->SetPageProperty("keywords", "sofas, chairs, office furniture, kitchen furniture, nursery furniture");
$APPLICATION->SetPageProperty("description", "We use only the high quality equipment to manufacture our furniture to achieve the superior quality.");
?>
Note: Section properties can be set up using the function of
SetDirProperty()
(e.g., in the code of the file
.section.php):
< ?
…
$APPLICATION->SetDirProperty("keywords", "design, web, site");
$APPLICATION->SetDirProperty("description", "Site Manager System");
…
?>
In this case, the following HTML code will be introduced into the page code as a result of the function ShowMeta()
.
<meta name="keywords" content="design, web, site">
<meta name="description" content="Site Manager System">
If no property value is set for the page itself, the value of the property of a higher section (recursively to the site root) will be selected. If the property value is not determined, the value of the relevant meta tag will remain undetermined. Page properties may be set dynamically from the code of the components located on the page. For example, for pages showing catalog or news information, the page properties may be set in accordance with determined properties of infoblock elements:
$APPLICATION->SetPageProperty("description",$arIBlockElement["PROPERTIES"][$META_DESCRIPTION]["VALUE"]);
In this case, the value of the property of an information block element with meta_description
code will be used as a value for the description
page property. Thus, the properties of keywords
and description
may be created for catalog elements and dynamically substituted to the page code.
CSS management
Generally CSS is a group of rules defining layout of some page elements. The CSS technology allows to store all information about page layout, utilized fonts and colors, menu layout styles and etc. in one or several exact files.
Using the CSS simplifies the page designing. Moreover, if you change a site design you do not need to modify each site page. It may be enough to make necessary modifications in corresponding CSS files.
For example, using the CSS you can modify a forum layout (in this example forum CSS files are stored separately from the site template CSS).
|
.forumborder {background-color:#B8B8B8;}
.forumhead {background-color:#DDDDDD;}
.forumbody {background-color:#F5F5F5;}
.forumbodytext {
font-family: Tahoma, Arial, Verdana, sans-serif;
font-size: 80%;
color:#000000;
}
.forumheadtext {
font-family: Tahoma, Arial, Verdana, sans-serif;
font-size: 80%;
color:#011B46;
…
|
|
|
.forumborder {background-color:#96C0FA;}
.forumhead {background-color:#A9CAF7;}
.forumbody {background-color:#D7E6FB;}
.forumbodytext {
font-family: Tahoma, Arial, Verdana, sans-serif;
font-size: 80%;
color:#042A69;
}
.forumheadtext {
font-family: Tahoma, Arial, Verdana, sans-serif;
font-size: 80%;
color:#011B46;
…
|
Stylesheets are uniquely customized for each site template in the system; each stylesheet set is stored in the folder of the corresponding template, as well as other files comprising the interface of the template. Stylesheets used in site templates are stored in the styles.css
files. Additional stylesheets used for exact site elements (for example, for forum layout) a stored in files with such names as forum.css
.
Separate CSS files storage
Bitrix software products features mechanism that allows to perform a separate storage of CCS files:
- Styles, used in the site template are stored separately in the
template_styles.css.
file. It is the main CSS file of a template.
- Styles, used when customizing the page content (site styles) are stored in the
styles.css
file. Styles that are stored in this file are displayed in the styles drop-down list when editing pages in the visual editor.
Designation of these files can be divided arbitrarily, depending on the context. Specifically, the styles that are responsible for content design not only for a template, but for the visual editor, can be stored in the styles.css file.
For example: corresponding display styles are defined for all headers on the site, i. e. they are used both to define page content and design of information block content, that is located outside the #WORK_AREA#. As a result, these header styles can be placed in both the template_styles.css and styles.css file. But because both of these files are connected to the template, all the header design styles must be placed only into the styles.css file, because it is connected to the page as well.
If headers must be modified when editing a page in the visual editor, then correspondingly the styles that are responsible for the site template design must be placed in the template_styles.css file, and for the visual editor - into the styles.css file.
Splitting styles between those two files must be done carefully. For example, the site background must be done in grey color, and the background color in the visual editor must be red - the background-color:#ccc;
must be specified for the body tag inside the template_styles.css file; and for the same tag - background-color:#ff0000;
must be defined in the styles.css file.
Files stored on the page site are connected as follows:
- styles.css
- template_styles.css
As a result, the body background will become grey at the site, because the style in the last connected template_styles.css file will supersede the style, defined in the styles.css file. The background in the visual editor will become red, because the visual editor content is the iframe that connects styles only from the styles.css file, and they are inserted directly into the head area via the <style>
tag.
If to add the increase of priority via !important
to the styles.css file, the style from this file will supersede the style, defined in the template_styles.css; the site background will become red as well, despite the fact that the file of template styles is connected last.
Site styles
The site stylesheet (the styles.css
file) is customized at the design template edit page (Control Panel > Settings > System settings > Websites > Site templates) at the Site Styles tab. The important element when creating the stylesheet is the style name. The names must be created only for those styles that are planned to be used when editing pages in the visual HTML editor ((Format) section).
The styles will be available in the visual editor from the drop-down list under the names, defined in this form. The name that are specified here, will be stored in the /.styles.php
file (file with style names).
Creation of design template stylesheet (the template_styles.css
file) is performed in the Template styles tab of the site template edit form
Managing CCS styles in the visual HTML editor
Style names are important elements when customizing a stylesheet. Style names can be added directly into the .styles.php
file. This file located in the site template folder and has the following format:
<?
$arStyles = Array(
"information-block-head" => "Information block header",
"content-block-head" => "Header with padding",
"information-block-body" => "Information block element text",
"tablehead" => "Table - header",
"tablebody" => "Table - body",
);
return $arStyles;
?>
Additionally, the style names can be added in the template edit form at the Site styles tab. In this case, the .styles.php
file is created automatically.
Style names will be displayed in the drop-down list in the visual editor.
If the Permit the display of styles without names in the HTML visual editor option is selected the Visual editor tab in the module Structure configuration settings form, then all available styles for the page will be presented in the list, including those styles, for which the names are not specified. The name, defined when creating the style: information-block
, content-block
and etc. will be used to designate the style with unspecified name.
Also, the styles, applicable to specific html-elements of the page (for examples, table borders, cells, images and etc.) can be created in the stylesheet. When selecting such elements, the list will show the styles available for them.
Example of Site Template Stylesheet
Styles for left menu:
.leftmenu, .leftmenuact {font-family: Arial, Helvetica, sans-serif;
font-size:12px; font-weight: bold; text-decoration: none;}
.leftmenu {color: #3F3F3F;}
.leftmenuact {color:#FF5235;}
Styles for top menu:
.topmenu {font-family: Arial, Helvetica, sans-serif; font-size:12px;
font-weight: bold; color: #FFFFFF; text-decoration: none;}
.topmenuact {font-family: Arial, Helvetica, sans-serif; font-size:12px;
font-weight: bold; color: #FAC535; text-decoration: none;}
Styles for table:
.tableborder {background-color:#9C9A9C;}
.tablehead {background-color:#D8D9DA;}
.tablebody {background-color:#F8F8F8;}
.tablebodytext, .tableheadtext {font-family: Arial, Helvetica, sans-serif;
font-size:12px;}
.tablebodytext {color:#000000;}
.tableheadtext {color:#000066;}
Connecting CSS
Stylesheets are connected to the site template in the prolog area via the ShowCSS()
function.
<?
$APPLICATION->ShowCSS();
?>
The ShowCSS()
function connects CSS file from the current site template, as well as all additional styles, defined for the this page by the function SetAdditionalCSS()
.
<?
$APPLICATION->SetAdditionalCSS("/bitrix/templates/demo/additional.css");
?>
Additional styles can be used, for example, to customize design elements of forum, web forms, tables, certain menu types, etc.
When using the ShowCSS()
function without parameters, styles will be connected as the link to the CSS file:
<LINK href="/bitrix/templates/demo/styles.css" type="text/css" rel="STYLESHEET">
The styles connected via the SetAdditionalCSS()
, will be included into the page code using the PHP function require()
(i. e. such styles will be fully included into the final page code).
When using the ShowCSS()
function with the parameter false
, the CCS file for the current design will be included into the page code via the require()
:
<?
$APPLICATION->ShowCSS(false);
?>
Setup of External Appearance of Additional Elements of the Site Design
Setup of Error Messages
It is often necessary to setup error messages to have an error message clearly shown in the site design.
In order to set up the design of a database connection error message, the file /bitrix/php_interface/dbconn_error.php should be edited.
In order to set up the design of a database query error message, the file /bitrix/php_interface/dbquery_error.php should be edited.
Setup of a File to be Connected when Closing the Site
In order to set up the design of a file to be connected when closing the public part of the site the file /bitrix/modules/main/include/site_closed.php should be copied and placed into /bitrix/php_interface/<language>/ or into /bitrix/php_interface/include/.
Setup of a Page by Page Navigation Design
A page by page information display is organized using the PHP function of NavPrint()
, a function for displaying links for a page by page navigation. The following parameters may be used to manage a page by page navigation design: NavPrint($title, $show_allways=false, $StyleText="text", $template_path)
where:
$title
– is the name of displayed elements;
$show_allways
– if the value of the parameter is false, the function will not display navigation links should all entries fit on one page. If true, the links for a page by page navigation will always be displayed;
$StyleText
– CSS class of a font to display navigation links;
$template_path
– a path to the template for showing navigation links.
Information Blocks
Information Blocks - is a module that permits making catalogs and managing different types (blocks) of homogeneous information. Different types of transient content may be posted using information blocks: goods catalogs, news blocks, reference books, etc.
Information blocks is a key point of Bitrix Framework. Almost everything that is being done in the system is more or less connected with this module, even if it is not displayed directly.
Information blocks constitute the next level of abstraction over DBMS regular tables; it is a kind of "database within a database"; That is why all of the rules used during database design partially apply to information blocks.
Infoblocks is an entity creating 4 tables in the database’s physical structure. These 4 tables remain unchanged during changes of data structure – object types, object instances, object properties, and object property values.
This approach has the following advantages:
- Convenient control over the structure data from the data application,
- Versatility of methods,
- Common data structure for any project,
- Possibility to change data types for fields many times without eliminating data themselves.
Disadvantages:
- Higher performance requirements,
- Nontransparent direct data access.
Specifics of the Arrangement of Elements by Sections
Arrangement of the elements of infoblocks by sections may greatly facilitate the navigation around an infoblock in the administrative interface. Faceted arrangement makes navigation even more convenient.
In addition, an infoblock with sections already contains a description of how it should be displayed to the user. I.e. it is sufficient to bypass it using the very common tree-avoidance algorithm in order to display it in the most convenient form.
The work specifics consist in a certain inconvenience when it comes to working precisely with sections, one by one. Thus, for example, if in addition to the Articles infoblock there is a Books infoblock, it is highly possible that its elements will also require classification by publication date and by subjects. In this case, you will have to create the same section structure once again. Furthermore, it will not be very easy to show, for example, the list of all of the materials (articles and books) on the same subjects by arranging them by publishing date. Moreover, it will be difficult to display the common index of subjects in the website menu.
In this case, a separate infoblock Subjects should be formed adding a reference type property Subject to the Books and Articles infoblocks, and a Publication Date property of the Date type. Then, it will be easier to navigate in the administrative interface using these property filters.
Working with Infoblocks Using Standard Means
Work Order
When creating a web section using information blocks, a certain work order should be followed. This order may differ depending on the degree of project readiness and a complexity of a specific assignment. Almost in every case you will have to:
- Think through the entire structure of infoblocks carefully.
- Create the necessary infoblock types and set up their parameters.
- Create infoblocks and set up their parameters.
- Create structure inside an infoblock.
- Create infoblock elements.
- Create a physical page (in case a complex component is used) or pages (if simple components are used), locate a component (components) on it, and then set up its parameters.
- Adjust the component operation according to the assignment and website requirements (component template customization, use of the result_modifier.php or component_epilog, customization of the component itself).
Standard Capabilities
Built-in tools of the Information Blocks module are extensive enough. There is no limit either to infoblock types, infoblock number, number of properties of each infoblock, or number of sections or elements.
Infoblock Properties
Elements of each infoblock have a set of system properties that may be extended with user properties. The infoblock properties differ according to their types:
- String - the property value is set as a text line;
- Number - the property value is set as a number;
- List - the property value is selected from the list;
- File - a file is used as the property value;
- Link to section - using this property it is possible to link an element of this infoblock and sections of another information block;
- Link to elements - setting links among information block elements one by one;
- HTML/text - the property value is set as a text with HTML tags;
- Link to elements by XML_ID - the link is stored as a string, and the value is XML_ID of the linked element;
- Bind to Google Maps - setting a link between an infoblock element and a Google Map component;
- Bind to Yandex Maps - setting a link between an infoblock element and a Yandex.Map component;
- Counter - is a substitute to autoincrement for databases. The value will increase by one each time an infoblock element is added. The starting value is set at random. This feature can be used for incoming document registers etc. where the continuous numbering of documents is needed.
- Link to user - a link between an element of this infoblock and system users can be set using this property
- Date/Time - the property value is set as a date/time;
- Video - setting a link between a list element and a media file;
- Link to elements (drop-down list) - setting a link between elements using a list;
- Link to forum topic - using this property, a link can be set between an element of this infoblock and forum subjects;
- Link to file (on server) - using this property a link can be set between an infoblock element and a file on a remote server;
- Link to elements autocomplete text box - setting a link to autocomplete elements;
- Link to product items (SKU) - setting a link to products (SKU).
Each type of properties has its own set of parameters established in relevant forms. Properties may be multiple and of mandatory completion.
Note. It is recommended that only the characteristics that are to be used as filters should be converted into separate properties. The remaining characteristics should be incorporated in product description as text.
Properties of Infoblock Sections
It is possible to set the user properties for infoblock sections. The code of user fields must always contain the prefix UF_. The list of field types is somewhat smaller than that for the infoblock proper:
- Video
- Text
- Integer
- Number
- Date/Time
- True/False
- File
- List
- Link to information block sections
- Bind To Information Block Elements
- Template
- Poll
Similarly to the properties of the infoblock itself, section properties may be multiple and mandatory. In addition, it is possible to establish whether the property will participate in search and sorting, whether the user can edit the property value, and whether the property will be displayed in the general list of properties.
Export-Import
Adding a big number of infoblock elements manually is a very laborious task
Data import/export can be applied using different file formats in order to make the information adding process easier. The following formats are supported:
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:
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:
The option Set these settings for all users by default permits you to establish form settings for all users as default settings.
Save the changes made. Now, you have a changed infoblock element editing form with no unnecessary fields where the remaining fields are grouped up in a necessary order.
Once set up is completed, the form looks like this:
This form will also be used to create/edit elements in the public section.
Note: The settings of the external appearance of the element creating/editing form can be reverted in the administrative part of the website using the command
Disable custom form settings from the menu of the
Settings button:
Setting Up Prompts
It is rather unusual to find a highly qualified employee working as a content manager in website support. Moreover, in case of a large number of created properties of an information block, even a highly qualified employee may experience difficulties when it comes to deciding which value should be entered in any other property field. A website administrator can create prompts to make it easier for a content manager to complete forms. The prompts look as follows in the form itself:
Just use the field Info caption for '?' sign in the properties form of the information block.
Storage Types for Infoblocks
When creating information blocks, infoblock properties should be stored in a separate table, and all of the property values of each element shall be stored in the same string. This technique is called Information Blocks 2.0 and permits you to significantly speed up system operation and also to lift a number of restrictions of the previous version of infoblocks. For example, now there is no need to make the additional request CIBlockElement::GetProperty when selecting property values with the function CIBlockElement::GetList.
Infoblock 2.0 performance options:
- When selecting elements, property values can be obtained at once, because the number of attached tables in the request is not increased with each property and is always equal to 1.
- Sorting by property values is processed similarly to infoblocks 1.0 (except for multiple properties).
- Selection of multiple property values does not lead to a Cartesian product of a query result; property values are transferred as an array.
- For combined filters by non-multiple (single) properties, now there is an option to create a composite database index manually to speed up sampling operations.
- “Straight through” element sampling is not possible for infoblocks 2.0 when an infoblock type and a symbol code of a property is indicated in the filter. Instead, IBLOCK_ID must be specified in the filter.
Full compatibility with API is important. I.e. the technique for using infoblocks, properties, elements, and their values is the same for both versions of infoblocks.
Connection between Infoblocks
Bitrix Framework permits creating interconnections between information blocks using properties of the type Link to elements, Link to section, Link to elements (drop-down list), Link to elementы autocomplete text box and Link to product items (SKU).
Infoblocks 2.0
When creating information blocks, infoblock properties should be stored in a separate table, and all property values of each element are stored in the same string. This technique is called Information Blocks 2.0 and permits you to significantly speed up system operation and also to lift a number of restrictions of the previous version of infoblocks. For example, now there is no need to make the additional request CIBlockElement::GetProperty when selecting property values with the function CIBlockElement::GetList.
Note. Documentation to Bitrix Framework, forum messages on the company’s website, and other places may contain the former name of the technique: infoblocks +.
Infoblock 2.0 performance options:
- When selecting elements, property values can be obtained at once, because the number of attached tables in the request is not increased with each property and is always equal to 1.
- Sorting by property values is processed similarly to infoblocks 1.0 (except for multiple properties).
- Selection of multiple property values does not lead to a Cartesian product of a query result; property values are transferred as an array.
- For combined filters by non-multiple (single) properties, now there is an option to create a composite database index manually to speed up sampling operations.
- “Straight through” element sampling is not possible for infoblocks 2.0 when an infoblock type and a symbol code of a property is indicated in the filter. Instead, IBLOCK_ID must be specified in the filter.
Full compatibility with API is important. I.e. the technique for using infoblocks, properties, elements, and their values is the same for both versions of infoblocks.
Storing properties in one common table and managing them using metadata from IBLOCK_PROPERTY_ID. is a very convenient feature for a developer because any metadata can be adjusted at any time without affecting any other information. This drawback is common for regular infoblocks.
If information is stored in infoblocks 2.0 and a property changes its type, e.g. from Number to Line,the storage type in the database itself will also change.
From the point of view of performance, infoblocks 2.0 scores better for small reference tables with a small number (20-30) of rarely changed properties. It makes no sense to shift a newsfeed to this type of infoblocks. You will gain in terms of the number of queries, but lose in terms of the query performance time.
Infoblock 2.0 databases have a physical limit to the number of infoblock properties. At this time, it is not controlled in the system because it depends on a number of unpredictable factors: property types, MySQL configuration, and others. When this physical limit is exceeded, you will obtain a MySQL error that is unusual for Bitrix Framework. However, no data will be lost in this case.
A big advantage of infoblocks 2.0 is the possibility to use composite indexes. However, the situation when sorting is made by = and by several fields simultaneously is quite unusual.
Information Block Level
Information block has a VERSION, attribute that determines whether an information block property value will be stored in a common or an allocated storage when creating a new infoblock. If an allocated storage for a specific infoblock is selected, the database creates two additional tables wherein the names will contain the infoblock indicator. One of the tables will store multiple properties and another one single and cached values of multiple properties.
There is a link to a “converter” between storage types at the editing stage of an infoblock. It should be used with utmost care because the duration of the conversion process depends on the total volume of infoblock property values. Throughout the entire conversion, the infoblock is in an inconsistent state (only a part of values is transferred). In a test configuration for a MySQL version the conversion speed is approximately 1,500 elements per a 20 second step.
The Fetch method is redefined in the CIBlockResult class.The method is responsible for caching values of multiple properties of an element that participate in sampling. For the same properties of the list type, the pairs ID=>VALUE are selected from the reference table.
API provides for a VERSION parameter in the field array $arFields of the method CIBlock::Add. Its values are: 1 – for general storage and 2 – for allocated (new) storage.
Information Block Level of Properties
During the editing of properties (change of a multiplicity attribute or a property type), additional table management operations are performed for the properties stored in the allocated storage, such as delete/add columns, insert/update, or delete a big number of entries. It is best to avoid doing this unless it is absolutely necessary. It is recommended to change the type or multiplicity of one property at a time. Moreover, for single properties, it is recommended to convert them first to multiple properties and then change the type, and the other way around for multiple properties – first comes the type, and then conversion to a single property.
Level of Elements of an Information Block
When adding an element, a relevant entry is made into the table that stores the property values of the element.
Level of Property Values of Information Block Elements
The values of single properties of an infoblock with the allocated ID storage are composite and consist of an element ID and a property ID separated by a colon. When updating multiple properties, the cache of these values resets.
Property values are stored in 2 tables (description of tables and their structure is provided for reference only and is subject to change in later versions):
- b_iblock_element_prop_mNN - for multiple. It has the same structure as b_iblock_element_property;
- b_iblock_element_prop_sNN - for single. It has the field IBLOCK_ELEMENT_ID - infoblock element ID to which the properties:
- PROPERTY_XX - stores values of a single property XX or cache of values for a multiple property;
- DESCRIPTION_XX - stores description for a single property.
How to achieve the best performance using Infoblocks 2.0?
In order to take advantage of the data storage structure used in new infoblocks, component behavior must be modified to a certain extent.
For example: if the code template was more or less like this:
<?
//Determine the array of necessary fields of an element
$arSelect = array(
"ID",
"IBLOCK_ID",
"IBLOCK_SECTION_ID",
"NAME",
"PREVIEW_PICTURE",
"DETAIL_PICTURE",
"DETAIL_PAGE_URL",
);
//Obtain the list of elements. (+1 query)
if($rsElements = GetIBlockElementListEx($IBLOCK_TYPE, false, false, array($ELEMENT_SORT_FIELD => $ELEMENT_SORT_ORDER),
$ELEMENT_COUNT, $arFilter, $arSelect))
{
//Initialization of a page by page display.
$rsElements->NavStart($ELEMENT_COUNT);
$count = intval($rsElements->SelectedRowsCount());
if ($count>0)
{
//For each element:
while ($obElement = $rsElements->GetNextElement())
{
$arElement = $obElement->GetFields();
//Obtain its properties. (+1 query)
$arProperty = $obElement->GetProperties();
//Below property values can be used.
//For example:
echo $arProperty["SOURCE"],"
";
//etc.
}
}
}
?>
Now, after conversion to a new storage type, it is possible to avoid queries in the cycle:
<?
//Determine the array of necessary fields of an element
$arSelect = array(
"ID",
"IBLOCK_ID",
"IBLOCK_SECTION_ID",
"NAME",
"PREVIEW_PICTURE",
"DETAIL_PICTURE",
"DETAIL_PAGE_URL",
"PROPERTY_SOURCE", //Select a property we need
// And all other which may be needed
//directly in the list
);
//Obtain the list of elements. (+1 query)
if($rsElements = GetIBlockElementListEx($IBLOCK_TYPE, false, false, array($ELEMENT_SORT_FIELD => $ELEMENT_SORT_ORDER),
Array("nPageSize"=>$ELEMENT_COUNT), $arFilter, $arSelect))
{
//Initialization of a page by page display.
$rsElements->NavStart($ELEMENT_COUNT);
if($obElement = $rsElements->GetNextElement())
{
//For each element:
do
{
$arElement = $obElement->GetFields();
//Below property values can be used.
//For example:
echo $arElement["PROPERTY_SOURCE"],"
";
//etc.
}
while ($obElement = $rsElements->GetNextElement())
}
}
?>
Action Plan in Case of Problems
Should any problems occur in project operation, we recommend that the following algorithm be used in order to eliminate the problems:
- Establish a specific goal. The optimization and improvement has no limits. For example: each page with a catalog of your goods must open within a set amount of time, say, 0.2 second. Only establishing these specific goals and achieving them can be efficient, unlike the vague and formalized request “to make it work better”.
- Find and remove irrelevant queries using the tool Performance monitor (Settings > Performance > SQL Queries).
An irrelevant query is:
- A query in a cycle (should be removed from the cycle to a variable).
- Queries that collect additional data in a cycle. (It is better to collect data in a filter, then display data in a single query, and after that introduce an additional cycle – breaking the data down; in this case, queries have no linear dependence on the number of elements).
- Remove unused data from queries ($arSelect). Specify the fields to be used. It drastically improves performance because such a specific indication of data to be used means a lesser volume of sorting for the database. The database performs such sorting not in the hard drive but in the RAM.
- Assess the possibility of using PROPERTY_* in combination with infoblocks 2.0. Infoblocks 2.0 store their properties in a separate table. This table is attached at the selection stage when PROPERTY_* is mentioned. And no properties are attached until values of these properties are selected.
When can we not use it? For example, in case of a small selection from a big number of elements (10 pieces of news out of several thousands of entries). It is not a straightforward aspect, and it depends very little on the developer. The reason is that sampling from infoblocks and infoblocks 2.0 leads to a different result. In simple infoblock entries start reproducing when selected. And infoblocks 2.0 return the array of property values. If a template code does not provide for this situation, the change of regular infoblocks to infoblocks 2.0 will destruct the template.
- Review the execution plan of the most heavy queries and add/delete indexes.
Filtering
Sometimes caching is bad. If we choose to cache a catalog with a multivariate filter in the conditions of dense website traffic, cache generation may exceed 200 MB per minute. Any disc quota limit will be filled fast enough. Problems with cleaning this cache may also occur. Deactivating such cache will reduce the number of writing operations.
Do not be afraid of creating indexes. It is impossible to say which indexes must be created in each particular case by default; each specific situation must be examined without fail. The tool Analyze indexes helps you do that (Settings > Performance > Database indexes > Analyze indexes).
One of the indexes most frequently used is the index by the list-based property. This index is intended for simple infoblocks. b_iblock_element_property - in this table property values are stored. So we index it: property value, its ID, and an element ID. When the list-based property filter is in place, creating such an index actually makes the query of MySQL to said table unnecessary because all of the query fields are available in the index.
Indexes are needed for specific sampling in specific projects. Depending on project architecture and logics, slow queries receive their own indexes, and they need such own indexes; often such indexes are composite.
Type of filtration
In the majority of functions and methods of the module of information blocks with list selection, filters admit different sorting types. Sorting type symbols shall be indicated directly before the name of the field to be sorted.
Types:
- (empty) – for string fields means mask search (in mask: "%" - an arbitrary number of any symbols, "_" - one arbitrary symbol); for non-string fields the search is "equal".
<?
// find elements in which the name begins with "#"
$res = CIBlockElement::GetList(Array(), Array("NAME"=>"%#"));
// find elements with identifier 100
$res = CIBlockElement::GetList(Array(), Array("ID"=>"100"));
?>
- "!" - for strings – an expression that does not fall within a mask, or unequal (for other types of fields).
<?
// find elements in which the name does not begin with "#"
$res = CIBlockElement::GetList(Array(), Array("!NAME"=>"#%"));
?>
- "?" - using the logics, works only for string properties.
<?
// find elements in which the name contains "One" or "Two"
$res = CIBlockElement::GetList(Array(), Array("?NAME"=>"One | Two"));
?>
- "<" - less;
- "<=" - less or equal;
- ">" - more;
- ">=" - more or equal.
<?
// find elements in which the name begins with "A"
$res = CIBlockElement::GetList(Array(), Array(">=NAME"=>"A", "<NAME"=>"B"));
// find elements with an identifier of more than 100
$res = CIBlockElement::GetList(Array(), Array(">ID"=>"100"));
?>
- "=" - equal;
- "!=" - non-equal.
<?
// find elements in which the name is equal to "ELEMENT%1"
$res = CIBlockElement::GetList(Array(), Array("=NAME"=>"ELEMENT%1"));
?>
- "%" - substring;
- "!%" - not a substring.
// find elements in which the name contains the sequence "123"
$res = CIBlockElement::GetList(Array(), Array("%NAME"=>"123"));
- "><" - between;
- "!><" - not between.
As an argument, these types of filters admit an array ("value FROM", "value TO")
<?
// ind elements in which the name begins between "A" and "B" or between "D" and "E"
$res = CIBlockElement::GetList(Array(), Array("><NAME"=>Array(Array("A", "B"), Array("D", "E"))));
// find elements in which the activity start date is outside the year 2003
$res = CIBlockElement::GetList(Array(),
Array("!><DATE_ACTIVE_FROM"=>
Array(date($DB->DateFormatToPHP(CLang::GetDateFormat("FULL")),
mktime(0,0,0,1,1,2003)),
date($DB->DateFormatToPHP(CLang::GetDateFormat("FULL")),
mktime(0,0,0,1,1,2004)))));
?>
Some Specific Cases of Sorting
$arFilter = array("PROPERTY_CML2_SCAN_CODE"=>false ) - is used to select all elements with an empty property;
$arFilter = array("PROPERTY_CML2_SCAN_CODE"=>"" ) - is used to select all elements;
$arFilter = array("PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the exact match of a property with the preset string is checked;
$arFilter = array("?PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the availability of preset substring in a property is checked;
$arFilter = array("!PROPERTY_CML2_SCAN_CODE"=>false ) - is used to select only elements with a property filled in;
$arFilter = array("!PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the absence of the exact match with the preset string is checked;
$arFilter = array("!?PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the absence of the preset substring in a property is checked.
Sorting Set Up to Display Related Elements
Task: Let us assume that there are 2 infoblocks that are interrelated by one of the properties. How should we set up the sorting so that among the infoblock elements only related elements can be displayed?
Solution:
<?
$arrFilter = array();
$arrFilter['!PROPERTY_<property_code>'] = false;
?>
Sorting Set Up Using a "Date/Time" Type of Property
A "Date/time" type of property is stored as string in the format YYYY-MM-DD HH:MI:SS, That is why the value for sorting is formed as follows:
$cat_filter[">"."PROPERTY_available"] = date("Y-m-d");
Filtering iblock elements without filter component
If simple components are used to publish an information block, elements can be sorted without using the Filter component and without customizing the component used to display the list of elements. Such sorting is based on the use of the parameter Name of the array with values used to filter elements (FILTER_NAME) and is available in the following components: bitrix:catalog.section, bitrix:catalog.sections.top and bitrix:news.list.
Filter array can be defined directly on the page before connecting a list component. However, in this case, you will have to create several pages placing a component and determining the filter array on each page. There is a simpler way: variables of the filter array may be submitted in a link.
In our example, variables for the filter will be submitted in a link using the GETmethod, and the filter $arrFilter will be determined from the array $_GET. Infoblock element publishing will be performed using the list component Section elements Section elements (bitrix:catalog.section).
Let us assume that we have the Products infoblock, and we will sort its elements using the property Manufacturer (MANUFACTURER):
Let us create a start page with a set of links (in our case, it will be a list of manufacturer countries):
The page code will be as follows::
<?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");?>
<p>Filter by manufacturer:</p>
<ul>
<li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Germany">Germany</a></li>
<li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Italy">Italy</a></li>
<li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Holland">Holland</a></li>
<li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Ukraine">Ukraine</a></li>
<li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Austria">Austria</a></li>
<li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Sweden">Sweden</a></li>
</ul>
<?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
Now let us create the page filter.php and place the component (bitrix:catalog.section) there. Then, we should set a necessary infoblock in the component settings and fill in the field Name of the array with values used to filter elements with the value arrFilter.
Before connecting the component, let us add the following code:
$manufacturer = $_GET["MANUFACTURER"];
$arrFilter=array("PROPERTY"=>array("MANUFACTURER"=>"$manufacturer"));
As a result, when passing from the home page (e.g., following the link Italy), the list of goods of the section with identifier 2 manufactured in Italy will open:
Handling iblocks via API
The needs of website owners are very diverse. The standard functionality of Bitrix Framework cannot resolve all tasks that may be set for the developer when it comes to creating Internet project. API must be used to implement non-standard tasks. API of infoblocks should be studied with special attention. They are most widely used in programming.
Attention! Direct database queries are strongly discouraged. In this case, the operation of system basic functions is not guaranteed. In addition, it may lead to data disintegration.
API of a module consists of several high-level functions to select data in the public section of a website and a set of classes with low level methods for a more specialized work.
Before using the module, please make sure it is installed and activate it using the following structure:
<?
if(CModule::IncludeModule("iblock"))
{
//here module functions and classes can be used
}
?>
Functions with simple parameters and preset filters may be used to obtain data during display in the public section of the website. These functions select by default the values that are suitable for the sorting location, namely only those active, related to the current website, suitable in terms of access rights, etc.
All work with dates through API (insert, selection, filters, etc.) is done in the format of the current website or, if in the administrative part, in the format of the current language.
Some API functions are available at all times, i.e. described in the main module, and some functions depend on the module used and can be present or absence in different versions of the product.
For most classes of Bitrix Framework the following functions are available:
- Data selection (
GetList
).
- Adding a new element (
Add
).
- Updating and deleting an element (
Update
).
- Deleting an element (
Delete
).
- And other functions.
For most modules, a specialized class structure, event mechanism, and additional functions are available. In particular, for the module of Information blocks the description is provided for:
- All tables used in the database, including table fields.
- Classes for work with infoblock types, infoblocks, elements, sections, and fields.
- Events occurring when adding, changing, and deleting module objects.
- Functions that extend kernel possibilities.
- Means to create the user’s forms for editing and the user’s data types.
- Other information.
The lessons of this chapter will address some examples of using the API of information blocks.
Work with the User Properties of Infoblocks
Examples of tasks that may occur while working with elements, sections, and infoblock properties.
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:
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.
- Use the method getValues (it can be found in infoblock components) in order to display data calculated during selection according to set templates.
- The method clearValues permits you to reset the cached values and recalculate.
Templates
Templates are built irrespective of the storage mechanism, and thus dynamic forms may be used. The following components are used to build a template:
- First, it is just a text to be calculated into the same simple text.
- Second, the substitutions which start inside curly brackets with an equal sign (e.g., {=this.Name}). This pseudoobjective syntaxis permits you to implement an economic model with pending data queries. The template may use the following areas: this, parent, sections, iblock, property or catalog. Fields can be very different: name, code, previewtext, detailtext, property_CODE, etc. (See files with classes in the folder /bitrix/modules/iblock/lib). The number of DB queries directly depends on the number of areas used in the template.
- Third, the functions (e.g., {=concat " \ " "!" iblock.name sections.name this.name}). There is a set of built-in functions (upper, lower, translit, concat, limit, contrast, min, max и distinct) and the event OnTemplateGetFunctionClass, which permits you to write an own function.
Templates can have modifiers: lower casing (/l) and transliteration (/t-). They are displayed in separate checkboxes in the SEO tab interface.
Furthermore, all templates support nesting. For example:
//For an element, a preview and detailed descriptions of its section are selected, then
//first 50 words are selected. After that, they joined with the first 50 words of the element preview.
//Out of them, 20 of the most contrasted words are selected, and all of them are converted to lower case.
{=lower {=contrast 20 " .,?!" {=limit 50 " .,?!" this.previewtext} {=limit 50 " .,?!" parent.previewtext parent.detailtext}}}
Let us have a look at the template code:
use Bitrix\Iblock\Template;
//Connect the infoblock module.
if (\Bitrix\Main\Loader::includeModule('iblock'))
{
//Set a template.
$template = "Name: {=this.Name}. Code:{=this.code}";
//We will take the source data from the element.
$entity = new Template\Entity\Element($ELEMENT_ID);
//Do not forget about safety.
echo \Bitrix\Main\Text\String::htmlEncode(
//Calculate value according to the template.
Template\Engine::process($entity, $template)
);
}
The entity object must be created. Parsing and template calculation are covered with the process, static method to which entity and template are submitted. The method process can also be used in the cycle with one entity and different template, and in this case the data will be «reused», i.e. there will be no odd queries. In addition, pay attention to the method htmlEncode, that is used to form safe html.
Highload blocks
The Information block module is considered as "heavily resource-demanding" for creating "lightweight" directories or storage of large data volumes (when module can behave sub-optimally). Now an option to create analog to iblocks (Highload blocks module), but a lot simpler and "lightweight" is available. New module is available from product version 14.0 and is written on the new core D7. Data structure for this module provides for use of high-load projects.
Note: Highload blocks and traditional information blocks are conceptually two different things. That's why there is no option to convert traditional iblocks into highload blocks. Technically, a mirroring structure can be created and data moved, however it makes sense only for a specific project, if required.
Highload blocks
Highload blocks - are fast listing directories, without hierarchy support, with limited property support. They could query the database, including via HandlerSocket and handle large volumes of data. Highload blocks store elements in their tables and use their own indexes.
Note: with exception to module Highload blocks, core D7 can query database via HandlerSocket for all ORM entities.
Previously, data was stored using the module that closed tables via Table Module design pattern. Highload blocks have additional layer called Table Data Gateway that supports all techniques for handling tables, with business logic located in expanding class.
Highload blocks is a logic for handling data, nothing more. For specific data use, developer must envisage implementation of business logic by the application itself. Accordingly, Highloadblock class must be expanded to implement this logic:
use Bitrix\Highloadblock as HL;
$hlblock = HL\HighloadBlockTable::getById( # )->fetch();
$entity = HL\HighloadBlockTable::compileEntity( $hlblock ); //class generation
$entityClass = $entity->getDataClass();
class MyDomainObjectTable extends #entityClass# {
…//our project business logic, overview the contents of $entityClass and write it into #entityClass#
}
Highload blocks advantages
- Low overhead cost (PHP, less SQL queries).
- Low risk of blockages in database: because data is are stored in respective tables, there is no unified, global table that can be blocked upon high loads (data retrieval and simultaneous import).
- Thousands, millions entities, directories.
- Reduce load on the database, hosting.
About performance and resources
Highloadblock is a middleware between user and ORM. OOP (object-oriented programming) leads to an increased use of memory and CPU, offering a more convenient and effective development process. Highloadblocks are intended for high-loads experienced by the system. Their advantages are in the area of architecture, allowing easier scalability, management and risk assessment.
Database handling is the specific advantage database handling. Developer receives:
- separate tables for entities (can be noticeable when handling large volumes),
- possibility to easy index assignment for necessary selections,
- possibility to transparently use handlersocket that significantly reduces load to DBMS.
Module architecture
Description
In contrast to Information blocks module, Highload blocks module can store each entity in their own individual tables, without associating with any module. Entity fields are [ds]core's user properties. [/ds][di]It is necessary to differentiate User fields in system modules and properties used within information blocks, although system forms (forms for creating/editing an iblock section and others) use a "user property" term[/di]
Entities are generated in admin interface, without additional programming.
Access to data is provided based on ORM.
Data allocation can be performed both to a single database and to different databases in general. This way a project's scaling out becomes possible, when some objects are located at one server, and some data - on another.
The Highload blocks module supports NoSQL concept based on MySQL database. Database connection is provided with both handlersocket extension for MySQL, as well as without it. Handlersocket use requires additional settings.
For example, when project supports NoSQL, with all required installed drivers for MySQL and core/kernel configuration had been established connection via these drivers. As result, Highload blocks module allows creating entities and building project in a manner that data retrieval from database was performed via the driver by primary key.
The following actions must be performed to use ORM entity for Highload blocks:
- Initialize entity in the system:
//first, select entity data from the database
$hldata = Bitrix\Highloadblock\HighloadBlockTable::getById($ID)->fetch();
//then initialize entity class
$hlentity = Bitrix\Highloadblock\HighloadBlockTable::compileEntity($hldata);
- Next, creates queries with this entity:
$query = new Entity\Query($hlentity);
or use via DataManager interface:
$hlDataClass = $hldata['NAME'].'Table';
$hlDataClass::getList();
All further handling of Highload blocks is governed by the ORM rules, because entity, created in the Highload blocks is an ORM entity.
Module contains two components: list of entity records (highloadblock.list) and entity detail view (highloadblock.view).
List association
Association of directory to Highload block is performed via the USER_TYPE_SETTINGS
properties. They contain table name.
Association of property values uses UF_XML_ID
(completing the field is required), otherwise element's "list" property values are not saved.
Highloadblock and handlersocket
Traditional ACID Databases generally complicate project implementation for specific tasks. Such frameworks as NoSQL and HandlerSocket were designed for such tasks (in a form of plugin to standard MySQL).
HandlerSocket allows customer application to connect directly to MySQL data engine to reduce excessive load, specific to traditional queries via the SQL interface and unacceptable to highly loaded databases.
Example comparison for number of queries, permitted by different database handling methods:
Query type | Number of queries per second | CPU load |
MySQL via SQL client | 105 000 | User processes: 60% System processes: 28% |
memcached | 420 000 | User processes: 8% System processes: 88% |
MySQL via client's HandlerSocket | 750 000 | User processes: 45% System processes: 53% |
|
The difference between querying via MySQL client and via HandlerSocket is that in the second case, parsing, table openings, execution schedule optimization are skipped. It means that querying is performed directly and the load to MySQL decreases significantly. Queries are not executed quicker, but reduces server load.
Connecting HandlerSocket is specified in the file Core parameter settings.
You can install the plugin either by directly downloading MySQL source files and assembling the plugin, or by installing PerconaServer or MariaDB, with plugin enabled by default.
HS API is called when querying the Highloadblock ($obj = $entityClass::getById( $arData["ID"] )->fetch();
) via HandlerSocket (query open_index
and find
in MySQL) with call result processing by the application.
HandlerSocket launches a thread pool inside the database, operating in asynchronous mode. (NGINX operates in a similar manner) In standard cases MySQL operates with a single thread for each client connection (thread) and works inside it. However, in case of HandlerSocket, it uses a pool of threads with multiplex system pool/select calls. That's why, for example, 5 threads can process hundreds of thousands queries.
Highload blocks handling examples
<?
//Preparation:
if (CModule::IncludeModule('highloadblock')) {
$arHLBlock = Bitrix\Highloadblock\HighloadBlockTable::getById(1)->fetch();
$obEntity = Bitrix\Highloadblock\HighloadBlockTable::compileEntity($arHLBlock);
$strEntityDataClass = $obEntity->getDataClass();
}
//Adding:
if (CModule::IncludeModule('highloadblock')) {
$arElementFields = array(
'UF_NAME' => $arPost['name'],
'UF_MESSAGE' => $arPost['message'],
'UF_DATETIME' => new \Bitrix\Main\Type\DateTime
);
$obResult = $strEntityDataClass::add($arElementFields);
$ID = $obResult->getID();
$bSuccess = $obResult->isSuccess();
}
//Retrieving the list:
if (CModule::IncludeModule('highloadblock')) {
$rsData = $strEntityDataClass::getList(array(
'select' => array('ID','UF_NAME','UF_MESSAGE','UF_DATETIME'),
'order' => array('ID' => 'ASC'),
'limit' => '50',
));
while ($arItem = $rsData->Fetch()) {
$arItems[] = $arItem;
}
}
?>
Arbitrary value selection:
$q = new Entity\Query($entity);
$q->setSelect(array('*'));
$q->setFilter($arFilter);
$q->setLimit(1);
$q->registerRuntimeField(
'RAND', array('data_type' => 'float', 'expression' => array('RAND()'))
);
$q->addOrder("RAND", "ASC");
$result = $q->exec();
User Forms for Element Editing
The form of adding/changing elements of information blocks is one of those most frequently used, and this form is definitely one of the most popular in the administrative section of online stores or information editions. Although the form’s interface and fields change depending on the information block settings, the interface of the element editing form can be set up using standard tools of the system; however, these tools may prove insufficient for certain specific tasks.
In this case, one or two (depending on the task) additional files should be created in /bitrix/php_interface/include/
:
After that, the paths to these files shall be set:
File with the Element Editing Form
Let us create, for example, a file my_iblock_element_edit.php in the folder /bitrix/php_interface/include/
, and copy a code from the file /bitrix/modules/iblock/admin/iblock_element_edit.php
into it from the string:
//////////////////////////
//START of the custom form
//////////////////////////
up to the string:
//////////////////////////
//END of the custom form
//////////////////////////
Now we can start editing the file, i.e. changing the external appearance of the editing form of the infoblock element according to our needs:
- You can remove the infoblock fields that you do not need. The following structures are used in order to display form fields:
<?
$tabControl->BeginCustomField("ACTIVE_TO", GetMessage("IBLOCK_FIELD_ACTIVE_PERIOD_TO"), $arIBlock["FIELDS"]["ACTIVE_TO"]["IS_REQUIRED"] === "Y");
?>
<tr id="tr_ACTIVE_TO">
<td><?echo $tabControl->GetCustomLabelHTML()?>:</td>
<td><?echo CAdminCalendar::CalendarDate("ACTIVE_TO", $str_ACTIVE_TO, 19, true)?></td>
</tr>
<?
$tabControl->EndCustomField("ACTIVE_TO", '<input type="hidden" id="ACTIVE_TO" name="ACTIVE_TO" value="'.$str_ACTIVE_TO.'">');
?>
- The function _ShowPropertyField() is used to display infoblock elements in the property form:
<?
$prop_code = "AUTHOR";
$prop_fields = $PROP[$prop_code];
$prop_values = $prop_fields["VALUE"];
$tabControl->BeginCustomField("PROPERTY_".$prop_fields["ID"],
$prop_fields["NAME"],
$prop_fields["IS_REQUIRED"]==="Y");
?>
<tr id="tr_PROPERTY_<?echo $prop_fields["ID"];?>">
<td class="adm-detail-valign-top" width="40%"><?if($prop_fields["HINT"]!=""):
?><span id="hint_<?echo $prop_fields["ID"];?>"></span>
<script>BX.hint_replace(BX('hint_<?echo $prop_fields["ID"];?>'), '<?echo CUtil::JSEscape($prop_fields["HINT"])?>');</script>
<?endif;?><?echo $tabControl->GetCustomLabelHTML();?>:</td>
<td width="60%"><?_ShowPropertyField('PROP['.$prop_fields["ID"].']',
$prop_fields,
$prop_fields["VALUE"],
(($historyId <= 0) && (!$bVarsFromForm) && ($ID<=0)),
$bVarsFromForm, 50000,
$tabControl->GetFormName(),
$bCopy);?></td>
</tr>
<?
$tabControl->EndCustomField("PROPERTY_".$prop_fields["ID"], $hidden);
?>
When an own form on the element editing page is used, the Settings button disappears. This button permits you to sort and set up the display of the form fields of an element.
The following code shall be added to the file in order to avoid adding a field sorting mechanism to my_iblock_element_edit.php and to maintain the standard function:
<?
// "Settings" button
$aMenu = array();
if (false == ((true == defined('BT_UT_AUTOCOMPLETE')) && (1 == BT_UT_AUTOCOMPLETE)))
{
$link = DeleteParam(array("mode"));
$link = $GLOBALS["APPLICATION"]->GetCurPage()."?mode=settings".($link <> ""? "&".$link:"");
$aMenu[] = array(
"TEXT"=>GetMessage("IBEL_E_SETTINGS"),
"TITLE"=>GetMessage("IBEL_E_SETTINGS_TITLE"),
"LINK"=>"javascript:".$tabControl->GetName().".ShowSettings('".urlencode($link)."')",
"ICON"=>"btn_settings",
);
$context = new CAdminContextMenu($aMenu);
$context->Show();
}
?>
Important! Do not forget to indicate the path to this file in the infoblock settings.
The file responsible for processing the element fields before saving the element
In order to change the fields that are to be saved, homonymous fields in the arrays $_POST
and $_FILES
, must be modified, and the values of all of the properties must be modified in the array $PROP
.
For example, let us create a file before_iblock_element_edit.php in /bitrix/php_interface/include/
.
Let us use the following condition to make sure that the detailed description of the element is entered:
if (strlen($_POST['DETAIL_TEXT'])<=0)
$error = new _CIBlockError(2, 'DESCRIPTION_REQUIRED', 'Add article text');
The _CIBlockError object constructor admits 3 parameters: error severity level, arbitrary identifier, and the error message. If the value of this object is set in the variable $error on the editing page, the changes made will not be saved. To prevent a loss of the values that come from the form, you have to initialize the variable $bVarsFromForm=true after initializing the variable $error. It is the variable $bVarsFromForm that indicates that the values to be displayed in the fields must be those that came from the form.
Let us use the function BXIBlockAfterSave to automatically create a small picture based on the larger one. If we define it before saving the element, it will be retrieved automatically once the element is saved successfully. Let us define it in the beginning of the file /bitrix/php_interface/include/before_iblock_element_edit.php
:
<?
function BXIBlockAfterSave($arFields)
{
$dbr = CIBlockElement::GetByID($arFields['ID']);
if(($ar = $dbr->Fetch()) && $ar['DETAIL_PICTURE']>0)
{
$img_path = $_SERVER['DOCUMENT_ROOT'].CFile::GetPath($ar['DETAIL_PICTURE']);
$width = 200;
$height = 200;
list($width_orig, $height_orig) = getimagesize($img_path);
if($width && ($width_orig < $height_orig))
$width = ($height / $height_orig) * $width_orig;
else
$height = ($width / $width_orig) * $height_orig;
$image_p = imagecreatetruecolor($width, $height);
$image = imagecreatefromjpeg($img_path);
imagecopyresized($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
$new_img_path = tempnam("/tmp", "FOO").".jpg";
imagejpeg($image_p, $new_img_path);
$be = new CIBlockElement();
$be->Update($arFields['ID'], Array('PREVIEW_PICTURE' => CFile::MakeFileArray($new_img_path)), false);
@unlink($new_img_path);
}
}
?>
Note: In the aforementioned script, a preview picture will be created based on the Detailed picture, and thus the created picture will be substituted in the field Preview picture. The example works only with JPG images.
Let us look at the complete code of the page /bitrix/php_interface/include/before_block_element_edit.php
:
<?
if($REQUEST_METHOD=="POST" && strlen($Update)>0 && $view!="Y" && (!$error) && empty($dontsave) && strlen($_POST['DETAIL_TEXT'])<=0)
$error = new _CIBlockError(2, "DESCRIPTION_REQUIRED", "Add article text");
function BXIBlockAfterSave($arFields)
{
$dbr = CIBlockElement::GetByID($arFields['ID']);
if(($ar = $dbr->Fetch()) && $ar['DETAIL_PICTURE']>0)
{
$img_path = $_SERVER['DOCUMENT_ROOT'].CFile::GetPath($ar['DETAIL_PICTURE']);
$width = 200;
$height = 200;
list($width_orig, $height_orig) = getimagesize($img_path);
if($width && ($width_orig < $height_orig))
$width = ($height / $height_orig) * $width_orig;
else
$height = ($width / $width_orig) * $height_orig;
$image_p = imagecreatetruecolor($width, $height);
$image = imagecreatefromjpeg($img_path);
imagecopyresized($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
$new_img_path = tempnam("/tmp", "FOO").".jpg";
imagejpeg($image_p, $new_img_path);
$be = new CIBlockElement();
$be->Update($arFields['ID'], Array('PREVIEW_PICTURE' => CFile::MakeFileArray($new_img_path)), false);
@unlink($new_img_path);
}
}
?>
Important! Do not forget to specify the path to this file in the infoblock settings.
Sometimes completely different changes must be made, for example the form of entry and change of several pictures simultaneously. In this case, we just have to create a new page and add it to the administrative menu.
Errors When Working with Infoblocks
Error of the Type:
Fatal error: Class 'CIBlockElement' not found in /hosting/site.com/www/index.php on line XX
The script uses the method of the Information Blocks module, but the module itself is not connected. The module should be connected:
CModule::IncludeModule("iblock");
Error of the Type:
Fatal error: Call to a member function GetNextElement() on a non-object in /hosting/site.com/www/index.php on line XX
You are likely to have submitted wrong parameters to a method. For example:
$res = CIBlockElement::GetList(array(), $arFilter, array(), array(), $arSelect);
While the third parameter must be true/false, and not array.
Examples
This chapter provides for examples on using the Information Blocks module.
Handling custom iblock properties
Examples for solving objectives, occurring when handling iblock elements, sections and properties.
Objective 1:
Get all property values for an element with a known ID.
1 <? $db_props = CIBlockElement::GetProperty(IBLOCK_ID, ELEMENT_ID, "sort", "asc", array());
2 $PROPS = array();
3 while($ar_props = $db_props->GetNext())
4 $PROPS[$ar_props['CODE']] = $ar_props['VALUE'];?>
Now character ID for a property is the key for an associative array $PROPS
, if you need property value with price code, it will be stored in $PROPS['price']
.
Objective 2:
Getting property values via the method CIBlockElement::GetList
1 <? $arSelect = array("ID", "NAME", "PROPERTY_prop_code_1", "PROPERTY_prop_code_2");
2 $res = CIBlockElement::GetList(array(), array(), false, array(), $arSelect);?>
Next, use the cycle and get properties with codes prop_code_1 and prop_code_2.
Objective 3:
Add property type TEXT/html for the element.
In case the property is not a multiple type:
01 <? $element = new CIBlockElement;
02 $PROP = array();
03 $PROP['property character code']['VALUE']['TYPE'] = 'text'; // or html
04 $PROP['property character code']['VALUE']['TEXT'] = 'value, to enter';
05 $arLoadArray = array(
06 "IBLOCK_ID" => IBLOCK_ID,
07 "PROPERTY_VALUES"=> $PROP,
08 "NAME" => "Element name"
09 );
10 $element->Add($arLoadArray);?>
In case the property is a multiple type:
01 <? // В $ITEMS stores multiple properties to enter
02 foreach($ITEMS as $item)
03 {
04 $VALUES[]['VALUE'] = array(
05 'TYPE' => 'text', // ir html
06 'TEXT' => $item,
07 );
08 }
09 $element = new CIBlockElement;
10 $PROPS = array();
11 $PROPS['property character code'] = $VALUES;
12 $arLoadArray = array(
13 "IBLOCK_ID" => IBLOCK_ID,
14 "PROPERTY_VALUES"=> $PROPS,
15 "NAME" => "Element name"
16 );
17 $element->Add($arLoadArray);?>
Objective 4:
Complete File multiple property. Quite frequently, when adding an element to the iblock you may need to associate several files to it. For this purpose, it's useful to create a File multiple property and store files within it. Example for completing the property:
01 <?
02 $arFiles = array();
03 for($i = 1; $i < 10; $i++)
04 {
05 if(file_exists($_SERVER['DOCUMENT_ROOT'].'/images/image_'.$i.'.jpg'))
06 {
07 $arFiles[] = array('VALUE' => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"].'/images/image_'.$i.'.jpg'), 'DESCRIPTION' => '');
08 }
09 }
10 ?>
After this, the array $arFiles
passes as property value when adding an element.
Objective 5:
Complete the List multiple property with checkmark flags display. In this case, each element of the value list has its own ID. You can browse them, by entering into property detailed edit. The property is completed as follows:
1 <?
2 if($first_condition == true) $values[] = array('VALUE' => 1);
3 if($second_condition == true) $values[] = array('VALUE' => 2);
4 CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array('property_code' => $values));
5 ?>
In this case, when executing first and second conditions we are marking flags the items from the list with ID =1 and ID=2 accordingly. You need to replace $ELEMENT_ID
, $IBLOCK_ID
and property_code
with desired values.
Objective 6:
Get a custom section property
1 <? $section_props = CIBlockSection::GetList(array(), array('IBLOCK_ID' => IBLOCK_ID, 'ID' => SECTION_ID), true, array("UF_ADDITIONAL_PRICE"));
2 $props_array = $section_props->GetNext(); ?>
Now the $props_array['UF_ADDITIONAL_PRICE']
contains the property UF_ADDITIONAL_PRICE for iblock section.
| Advice from developers.
When handling iblocks, its more convenient for all property ID to be named with capital letters. In this case you can avoid small inconsistencies in your work.
For example, property with the foto code when handling components is most frequently accessible via [PROPERTIES][foto][VALUE]? , and you can get PROPERTY_FOTO_VALUE when using the method GetList. |
Example of creating your own custom data type for a custom property
Let's add am image with preview as the property value. For example, it can be photos of a hotel at the tourist website or something similar. This objective will be achieved within this context.
One of variant for implementation: store images in a an individual iblock and show as association to an element. Example of code:
AddEventHandler("iblock", "OnIBlockPropertyBuildList", array("CIBlockPropertyPicture", "GetUserTypeDescription"));
AddEventHandler("iblock", "OnBeforeIBlockElementDelete", array("CIBlockPropertyPicture", "OnBeforeIBlockElementDelete"));
class CIBlockPropertyPicture
{
function GetUserTypeDescription()
{
return array(
"PROPERTY_TYPE" =>"E",
"USER_TYPE" =>"Picture",
"DESCRIPTION" =>"Hotel photo",
"GetPropertyFieldHtml" =>array("CIBlockPropertyPicture", "GetPropertyFieldHtml"),
"GetPublicViewHTML" =>array("CIBlockPropertyPicture", "GetPublicViewHTML"),
"ConvertToDB" =>array("CIBlockPropertyPicture", "ConvertToDB"),
//"GetPublicEditHTML" =>array("CIBlockPropertyPicture","GetPublicEditHTML"),
//"GetAdminListViewHTML" =>array("CIBlockPropertyPicture","GetAdminListViewHTML"),
//"CheckFields" =>array("CIBlockPropertyPicture","CheckFields"),
//"ConvertFromDB" =>array("CIBlockPropertyPicture","ConvertFromDB"),
//"GetLength" =>array("CIBlockPropertyPicture","GetLength"),
);
}
function GetPropertyFieldHtml($arProperty, $value, $strHTMLControlName)
{
$LINK_IBLOCK_ID = intval($arProperty["LINK_IBLOCK_ID"]);
if($LINK_IBLOCK_ID)
{
$ELEMENT_ID = intval($value["VALUE"]);
if($ELEMENT_ID)
{
$rsElement = CIBlockElement::GetList(array(), array("IBLOCK_ID" => $arProperty["LINK_IBLOCK_ID"], "ID" => $value["VALUE"]), false, false, array("ID", "PREVIEW_PICTURE", "DETAIL_PICTURE"));
$arElement = $rsElement->Fetch();
if(is_array($arElement))
$file_id = $arElement["DETAIL_PICTURE"];
else
$file_id = 0;
}
else
{
$file_id = 0;
}
if($file_id)
{
$db_img = CFile::GetByID($file_id);
$db_img_arr = $db_img->Fetch();
if($db_img_arr)
{
$strImageStorePath = COption::GetOptionString("main", "upload_dir", "upload");
$sImagePath = "/".$strImageStorePath."/".$db_img_arr["SUBDIR"]."/".$db_img_arr["FILE_NAME"];
return '<label><input name="'.$strHTMLControlName["VALUE"].'[del]" value="Y" type="checkbox">Удалить файл '.$sImagePath.'</label>.'<input name="'.$strHTMLControlName["VALUE"].'[old]" value="'.$ELEMENT_ID.'" type="hidden">';
}
}
return '<input type="file" size="'.$arProperty["COL_COUNT"].'" name="'.$strHTMLControlName["VALUE"].'"/>';
}
else
{
return "Property settings error. Specify iblock to store images.";
}
}
function GetPublicViewHTML($arProperty, $value, $strHTMLControlName)
{
$LINK_IBLOCK_ID = intval($arProperty["LINK_IBLOCK_ID"]);
if($LINK_IBLOCK_ID)
{
$ELEMENT_ID = intval($value["VALUE"]);
if($ELEMENT_ID)
{
$rsElement = CIBlockElement::GetList(array(), array("IBLOCK_ID" => $arProperty["LINK_IBLOCK_ID"], "ID" => $value["VALUE"]), false, false, array("ID", "PREVIEW_PICTURE", "DETAIL_PICTURE"));
$arElement = $rsElement->Fetch();
if(is_array($arElement))
return CFile::Show2Images($arElement["PREVIEW_PICTURE"], $arElement["DETAIL_PICTURE"]);
}
}
return "";
}
function ConvertToDB($arProperty, $value)
{
$arResult = array("VALUE" => "", "DESCRIPTION" => "");
$LINK_IBLOCK_ID = intval($arProperty["LINK_IBLOCK_ID"]);
if($LINK_IBLOCK_ID)
{
if(
is_array($value["VALUE"])
&& is_array($value["VALUE"]["error"])
&& $value["VALUE"]["error"]["VALUE"] == 0
&& $value["VALUE"]["size"]["VALUE"] > 0
)
{
$arDetailPicture = array(
"name" => $value["VALUE"]["name"]["VALUE"],
"type" => $value["VALUE"]["type"]["VALUE"],
"tmp_name" => $value["VALUE"]["tmp_name"]["VALUE"],
"error" => $value["VALUE"]["error"]["VALUE"],
"size" => $value["VALUE"]["size"]["VALUE"],
);
$obElement = new CIBlockElement;
$arResult["VALUE"] = $obElement->Add(array(
"IBLOCK_ID" => $LINK_IBLOCK_ID,
"NAME" => $arDetailPicture["name"],
"DETAIL_PICTURE" => $arDetailPicture,
), false, false, true);
}
elseif(
is_array($value["VALUE"])
&& isset($value["VALUE"]["size"])
&& !is_array($value["VALUE"]["size"])
&& $value["VALUE"]["size"] > 0
)
{
$arDetailPicture = array(
"name" => $value["VALUE"]["name"],
"type" => $value["VALUE"]["type"],
"tmp_name" => $value["VALUE"]["tmp_name"],
"error" => intval($value["VALUE"]["error"]),
"size" => $value["VALUE"]["size"],
);
$obElement = new CIBlockElement;
$arResult["VALUE"] = $obElement->Add(array(
"IBLOCK_ID" => $LINK_IBLOCK_ID,
"NAME" => $arDetailPicture["name"],
"DETAIL_PICTURE" => $arDetailPicture,
), false, false, true);
}
elseif($value["VALUE"]["del"])
{
$obElement = new CIBlockElement;
$obElement->Delete($value["VALUE"]["old"]);
}
elseif($value["VALUE"]["old"])
{
$arResult["VALUE"] = $value["VALUE"]["old"];
}
elseif(!is_array($value["VALUE"]) && intval($value["VALUE"]))
{
$arResult["VALUE"] = $value["VALUE"];
}
}
return $arResult;
}
function OnBeforeIBlockElementDelete($ELEMENT_ID)
{
$arProperties = array();
$rsElement = CIBlockElement::GetList(array(), array("ID" => $ELEMENT_ID), false, false, array("ID", "IBLOCK_ID"));
$arElement = $rsElement->Fetch();
if($arElement)
{
$rsProperties = CIBlockProperty::GetList(array(), array("IBLOCK_ID" => $arElement["IBLOCK_ID"], "USER_TYPE" => "Picture"));
while($arProperty = $rsProperties->Fetch())
$arProperties[] = $arProperty;
}
$arElements = array();
foreach($arProperties as $arProperty)
{
$rsPropValues = CIBlockElement::GetProperty($arElement["IBLOCK_ID"], $arElement["ID"], array(), array(
"EMPTY" => "N",
"ID" => $arProperty["ID"],
));
while($arPropValue = $rsPropValues->Fetch())
{
$ID = intval($arPropValue["VALUE"]);
if($ID > 0)
$arElements[$ID] = $ID;
}
}
foreach($arElements as $to_delete)
{
CIBlockElement::Delete($to_delete);
}
}
}
As the result we have the following:
- Element edit interface with an option to add and delete images.
- When deleting an element, information associated with it, is deleted.
- Component support in the public section.
Manual:
- Place this code in the file
/bitrix/php_interface/init.php
.
- Create an information block for image storage and indicate in its settings the parameters for preview image generation from details (in the Fields tab).
- Add a Picture proeprty in the Hotels iblock and indicate an iblock, created at the first step, in the additional settings for this property. Do not forget to specify a character ID code for the property.
- Create an element and try to change up different values for this property.
- For example, select this property in the list config parameters inside the public section's news component.
How to delete a file inside iblock property
Update any property using the following methods:
When using any method, the update array key receives property ID and a new value. You need to pass the following simple array to delete the file:
array('MY_FILE' => array('XXX' => array('del' => 'Y')));
This method is universal and suits for both iblocks and iblocks 2.0 and document processing as well. MY_FILE
- is the code for your File property. What is ХХХ? It contains property value ID. It means specifically value ID, not the property ID.
CModule::IncludeModule('iblock');
$IB = 24;
$ID = 220304;
$CODE = 'ONE_FL';
if ($arProp = CIBlockElement::GetProperty($IB, $ID, 'ID', 'DESC', array('CODE' => $CODE))->fetch()) {
$XXX = $arProp['PROPERTY_VALUE_ID'];
CIBlockElement::SetPropertyValueCode($ID, $CODE, array($XXX => array('del' => 'Y')));
}
This way, you will have a universal XXX, and it must be passed for each file targeted for deletion.
What can be done in case of a multiple file? How to delete a file inside a list? It's simple - use the while instead of if in the example above and, additional filter which file is to be deleted.
Examples of working with multiple properties
Task 1: Delete one of the values of a multiple property of the infoblock element.
Solution:
$el = new CIBlockElement;
$PROP = array();
$PROP[property_id][id] = "4";
$PROP[property_id][id] = "5";
$PROP[property_id][id] = "6";
$arLoadProductArray = Array(
"IBLOCK_ID" => $B_ID,
"PROPERTY_VALUES" => $PROP,
"NAME" => "Element",
);
$PRODUCT_ID = $E_ID;
$res = $el->Update($PRODUCT_ID, $arLoadProductArray);
In this case, in order to delete the value, it is sufficient to exclude the pair: key and value of the property to be deleted from the array $PROP. This solution is appropriate when the property value id must be left unchanged.
$PROP[property_id ][id ]
Alternatively, the method SetPropertyValues may be used as a solution:
CIBlockElement::SetPropertyValues($ELEMENT_ID, $IBLOCK_ID, $PROPERTY_VALUE, $PROPERTY_CODE);
False shall be submitted to the fourth parameter of the function, and the "property code"=>"value" array – to the third parameter.
In this case, all of the values will be deleted except for those indicated in the array submitted to the third parameter.
Task 2: Adding a specific value for a multiple property of the file type:
Solution:
//68 – property id;
//FILES – symbol code of a multiple property of the file type;
$ELEMENT_ID = 392;
$arFile = CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif");
$arFile["MODULE_ID"] = "iblock";
$PROPERTY_VALUE["68"][n0] = $arFile;
CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array("VALUE"=>$arFile) ) );
Task 3: Adding several values for a multiple property of the file type:
Solution:
$arFile = array(
0 => array("VALUE" => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/01.gif"),"DESCRIPTION"=>""),
1 => array("VALUE" => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif"),"DESCRIPTION"=>"")
);
CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array($PROPERTY_CODE => $arFile));
Task 4: Deleting a specific value of a multiple property of the file type:
Solution:
//68 – property id;
//FILES – symbol code of a multiple property of the file type;
//2033 – property value id;
$ELEMENT_ID = 392;
$arFile["MODULE_ID"] = "iblock";
$arFile["del"] = "Y";
$PROPERTY_VALUE["68"]["2033"] = $arFile;
CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array ("2033" => Array("VALUE"=>$arFile) ) );
Task 5: Updating a specific value of a multiple property of the file type file:
Solution:
//68 – property id;
//FILES – symbol code of a multiple property of the file type;
//2033 – property value id;
$ELEMENT_ID = 392;
$arFile = CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif");
$arFile["MODULE_ID"] = "iblock";
$arFile["del"] = "Y";
$PROPERTY_VALUE["68"]["2033"] = $arFile;
CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array ("2033" => Array("VALUE"=>$arFile) ) );
Task 6: Setting a multiple property of the string type with a value description field:
Solution using SetPropertyValueCode:
$arValues = array(
0 => array("VALUE"=>"value","DESCRIPTION"=>"value description"),
1 => array("VALUE"=>"value2","DESCRIPTION"=>"value description2")
);
CIBlockElement::SetPropertyValueCode($IBLOCK_ID, $PROP_CODE, $arValues);
Solution using SetPropertyValuesEx:
$PROPERTY_VALUE = array(
0 => array("VALUE"=>"value","DESCRIPTION"=>"value description"),
1 => array("VALUE"=>"value2","DESCRIPTION"=>"value description2")
);
CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array($PROPERTY_CODE => $PROPERTY_VALUE));
Task 7: Updating a multiple property of the text type without changing the DESCRIPTION:
Solution:
CIBlockElement::SetPropertyValues($nProductID, $nIblockID, array(
array(
"VALUE" => array(
"TEXT"=>time(),
"TYPE"=>"HTML"
),
"DESCRIPTION"=>"111"),
array(
"VALUE" => array(
"TEXT"=>time(),
"TYPE"=>"HTML"
),
"DESCRIPTION"=>"222"),
), $prop['ID']);
Copy element field values to properties
Let us consider an example of the function which copies the field values of infoblock elements ($_FROM_FIELD_NAMES) to the properties of the infoblock elements ($TO_PROPERTY_NAMES).
The fields Date (DATE_ACTIVE_FROM) and Activity End (DATE_ACTIVE_TO) will be copied to the properties DATE_BEGIN and DATE_END of infoblock elements with ID = 22:
function copy_from_fields_to_propertys_values( $IBLOCK_ID, $_FROM_FIELD_NAMES, $TO_PROPERTY_NAMES ){
/* *
* $_FROM_FIELD_NAMES = array(DATE_ACTIVE_FROM, DATE_ACTIVE_TO);
* $TO_PROPERTY_NAMES = array(DATE_BEGIN, DATE_END);
* copy_from_fields_to_propertys_values(22, array("DATE_ACTIVE_FROM","DATE_ACTIVE_TO"), array("DATE_BEGIN","DATE_END"));
* */
if ( CModule::IncludeModule ( "iblock" ) ){
$arOrder = array(
"sort" => "ASC",
);
$arFilter = array(
"IBLOCK_ID" => $IBLOCK_ID,
);
foreach ( $TO_PROPERTY_NAMES as $property_name ) {
$TO_PROPERTY_NAMES_with_prop[] = 'PROPERTY_' . $property_name;
}
$arSelect = array(
"NAME",
"ID"
);
$arSelect = array_merge ( $arSelect, $_FROM_FIELD_NAMES, $TO_PROPERTY_NAMES_with_prop );
$db_elemens = CIBlockElement::GetList ( $arOrder, $arFilter, false, false, $arSelect );
while ( $arElement = $db_elemens->Fetch () ) {
$PRODUCT_ID = $arElement["ID"];
foreach ( $TO_PROPERTY_NAMES as $key => $property_name ) {
CIBlockElement::SetPropertyValues ( $PRODUCT_ID, $IBLOCK_ID, $arElement[$_FROM_FIELD_NAMES[$key]], $property_name );
}
}
} else {
die( "The iblock module is not set" );
}
}
Additional Information
Obtaining the sum of field values of related iblocks
Let us assume that we have related infoblocks and we face the following task: to obtain a sum of field values of related elements in a specific field of an infoblock element.
Solution is possible both with using the module e-Store or without it. If the e-Store module is used, all of the infoblocks must have properties of a trade catalog with an indication of the price in the appropriate fields. If the version without the e-Store module is used, all of the fields of the source infoblocks must have the number type and (as a part of this solution) have the PRICE code.
Let us assume that the resulting infoblock is called COST. It must have the number type. The following expression must be entered in the field “Default values” of the COST parameter:
- {PROP_1_PRICE}+{PROP_2_PRICE}+... - for versions without e-Store.
- {PROP_1} + {PROP_2}+... - for versions with e-Store.
The code below is provided to obtain results in the component catalog.section. Code modification is required in order to display results in another component. The code provided shall be entered in the file result_modifer.php of the indicated component:
<?
//This string can be uncommented and see the contents arResult
//of the component for which this modifier is to be adapted
//echo "<pre>",htmlspecialchars(print_r($arResult, 1)),"</pre>";
//The symbol code of a property with a default value containing expressions
//the calculation result will be displayed by the component template
//The expression itself represents a PHP code executable by eval
//in which specific values will be substituted to the templates of the type {}
//These properties must be selected in the component settings and available through arResult
//otherwise, the function CIBlockElement::GetProperty must be used to access the database
//These properties must NOT be multiple
//Example of the expression: "({PROP_1_PRICE} + {PROP_2_PRICE}) * {PROP_COUNTER}"
//Please pay attention to the _PRICE – it is an indication that the price of a related element must be selected!
//The property itself must have a symbol code PROP_1 and PROP_2, accordingly
$CALCULATOR_CODE="COST";
//ID of the price that will be retrieved for calculations
$PRICE_ID = 1;
//Infoblock identifier (for different components different fields of arResult must be chosen)
$IBLOCK_ID = $arResult["IBLOCK_ID"];
//We obtain metadata of the “Calculator” property (COST)
$arProperty = CIBlockProperty::GetPropertyArray($CALCULATOR_CODE, $IBLOCK_ID);
//If there is such property and the expression for calculation is set:
if($arProperty && strlen($arProperty["DEFAULT_VALUE"]) > 0)
{
//The cycle for all the elements of the catalog
foreach($arResult["ITEMS"] as $i => $arElement)
{
//We take the “Calculator”’s expression
$EQUATION = $arProperty["DEFAULT_VALUE"];
//Check if template substitution is necessary
if(preg_match_all("/(\\{.*?\\})/", $EQUATION, $arMatch))
{
//Cycle for all of the properties used in the expression
$arPropCodes = array();
foreach($arMatch[0] as $equation_code)
{
//This is the "price"
$bPrice = substr($equation_code, -7)=="_PRICE}";
//Symbol code of the property which value will be substituted in the expression
$property_code = ($bPrice? substr($equation_code, 1, -7): substr($equation_code, 1, -1));
if($bPrice)
{
//Let us find a related element
$rsLinkedElement = CIBlockElement::GetList(
array(),
array(
"=ID" => $arElement["PROPERTIES"][$property_code]["~VALUE"],
"IBLOCK_ID" => $arElement["PROPERTIES"][$property_code]["~LINK_IBLOCK_ID"]
),
false, false,
array(
"ID", "IBLOCK_ID", "CATALOG_GROUP_".$PRICE_ID, "PROPERTY_PRICE" )
);
$arLinkedElement = $rsLinkedElement->Fetch();
//We borrow its price
if($arLinkedElement["CATALOG_PRICE_".$PRICE_ID])
$value = doubleval($arLinkedElement["CATALOG_PRICE_".$PRICE_ID]);
else
$value = doubleval($arLinkedElement["PROPERTY_PRICE_VALUE"]);
}
else
{
//If we require not only numbers but also strings
//get rid of doubleval and add screening of string symbols
$value = doubleval($arElement["PROPERTIES"][$property_code]["~VALUE"]);
}
//Value substitution
$EQUATION = str_replace($equation_code, $value, $EQUATION);
}
}
//Calculation proper
$VALUE = @eval("return ".$EQUATION.";");
//and saving its result to be displayed in the template
$arResult["ITEMS"][$i]["DISPLAY_PROPERTIES"][$CALCULATOR_CODE] = $arResult["ITEMS"][$i]["PROPERTIES"][$CALCULATOR_CODE];
$arResult["ITEMS"][$i]["DISPLAY_PROPERTIES"][$CALCULATOR_CODE]["DISPLAY_VALUE"] = htmlspecialchars($VALUE);
}
}
?>
Printing iblock element properties
Objective
Select an element property/properties and display in on the screen.
Solution
First stage of the objective is simple: method GetProperty of class CIBlockElement is detailed in the documentation.
Second stage. Take the property HTML\text. Its value cannot be just printed (key VALUE
), because its an array, containing "raw" value and its type (HTML or text). Requires a single method call: GetDisplayValue of class CIBlockFormatProperties:
$arResult['DISPLAY_PROPERTIES'][$pid] = CIBlockFormatProperties::GetDisplayValue($arResult, $prop);
Now, we can write as follows in the template:
echo $arResult['DISPLAY_PROPERTIES'][$pid]['DISPLAY_VALUE'];
And any property, which type presupposes a value formatting before display - will be converted accordingly.
ORM
ORM (Object-relational mapping) is a programming technique that connects databases with the concepts of object-oriented programming languages creating a "virtual object database" (Wikipedia).
Introduction
In the old core, its own GetList, Update, Add, and Delete are programmed for each entity.
Deficiencies of such method:
- Different set of parameters;
- Different syntax of the filter fields;
- Events may be existent or non-existent;
- Sometimes different code for different databases (
Add
).
New Core D7:
To make database selecting and saving operations uniform with the same parameters and filters. If possible, entity tables must be serviced with a minimum of a new code. Standard events of adding/changing/deleting must be available automatically.
The following notions were introduced to implement these purposes:
- Entities (Bitrix\Main\Entity\Base).
- Entity fields (Bitrix\Main\Entity\Field and its successors).
- Data manager (Bitrix\Main\Entity\DataManager).
An entity describes a table in the database, and also contains entity fields. The DataManager performs selection and entity change operations. In practice, the work is mostly done at the level of the DataManager.
Attention! Before starting development, ensure that the module you selected has classes methods from core D7. It can be checked by available description in the documentation
D7.
Note: This chapter provides abstract examples for purposes of clarification. Using the provided examples via copy\paste may not render the desired result for your specific project. Examples must be adapted for your specific code and database structure.
Entity concept and description
When creating Internet-based projects using the Bitrix Framework platform, vast functional capabilities are available out of the box for use through API calls for relevant modules. In this case, each module in the framework concept is a separate working unit which ensures a solution for a certain range of tasks.
As a rule, the API of each module is designed based on specific tasks, and quite often call formats are different for each module. The basic functionality existing in almost every module is standardized in order to minimize these differences. These are CRUD operations: Create, Read, Update, and Delete (creating, reading, updating, and deleting data).
Concept of Entities
An entity is an aggregate of a set of objects with their intrinsic basic (low level) business logic. An entity has a set of properties which values are subject to specific processing rules.
For example, the entity of the User means numerous users with a set of fields:
- ID
- First name
- Last name
- Password
- Login
- etc.
At the same time, the database issues an ID automatically, First Name and Last Name are limited to 50 characters, Login must contain only Latin letters, numbers, and underscore sign, etc.
Instead of programming each entity, we would rather describe it in a specific format. Such a description would be processed by the system core and would represent some sort of configuration for it:
Book
ID int [autoincrement, primary]
ISBN str [match: /[0-9-]+/]
TITLE str [max_length: 50]
PUBLISH_DATE date
For example, a similar description can be used for a book catalog where the system itself will monitor the correctness and integrity of the data checking of the format and the correct range of acceptable values.
Field Typing
Tag tools (xml, yml, and similar) are not used for the configuration of entities, php is used instead. This option provides for better development opportunities and flexibility.
The determination of data types from the example above is as follows:
namespace SomePartner\MyBooksCatalog;
use Bitrix\Main\Entity;
class BookTable extends Entity\DataManager
{
public static function getTableName()
{
return 'my_book';
}
public static function getMap()
{
return array(
new Entity\IntegerField('ID'),
new Entity\StringField('ISBN'),
new Entity\StringField('TITLE'),
new Entity\DateField('PUBLISH_DATE')
);
}
}
Attention! Notwithstanding the fact that the example means a Book as an entity, the class name is followed by a postfix: BookTable. It is intended, because the name of a descriptive class of an entity must always be completed with the word Table. The main name Book in the same namespace is considered reserved, and in the future the main name is expected to be used (in this case, the Book class) to represent entity elements as objects (at present, these entities are represented by arrays as in the old getList methods).
The method getMap() is responsible for the description of the entity structure. It returns an array to field instances.
Each type of the field is represented as an inheritor class Entity\ScalarField. These fields operate simple scalar values which are saved in the database 'as is'. By default, 8 of such types are available:
- Integral number
- Number
- Line
- Text
- Date
- Date/Time
- Yes/No
- Value from the list
For the sake of consistency with code writing standards, field names should be uppercase. The names must be unique within the same entity.
As a rule, the first parameter in the field designer is the field name, and the second parameter contains additional settings. General settings will be dealt with later on in the same chapter, but there is a specific setting for BooleanField and EnumField:
new Entity\BooleanField('NAME', array(
'values' => array('N', 'Y')
))
new Entity\EnumField('NAME', array(
'values' => array('VALUE1', 'VALUE2', 'VALUE3')
))
Since true and false cannot be stored as such in the database, a mapping of values is created in the form of an array for BooleanField, where the first element replaces false and the second replaces true at the time of saving.
Note: when describing the entity the table name can be set in the method getTableName. In our example, it is my_book. If this method is not defined, the table name will be generated automatically from the namespace and class name, and for this entity it will result in b_somepartner_mybookscatalog_book.
Primary & autoincrement & required
In the majority of cases, the entity has a primary key for one field. The same key is, as a rule, autoincremental. To inform an entity about it, we have to use parameters in the field designer:
new Entity\IntegerField('ID', array(
'primary' => true
))
Note: a composite primary key is also possible. For example, in the relations of two entities, the IDs of both entities will form a composite key. To learn more about this and see an example, please refer to the section
N:M relations.
This is the way to announce the ownership of a field to the primary key. Thanks to this option, the entity will control data entering and will not permit adding an entry without specifying the value for the primary key. During the update and deletion of entries it will be possible to identify them using the primary key only.
The ID value is often not indicated but rather obtained from the database after successfully adding an entry. In this case, the entity must be informed about that:
new Entity\IntegerField('ID', array(
'primary' => true,
'autocomplete' => true
))
The flag autocomplete for an entity means that when adding a new entry the developer is not required to set values for this field. By default, this requirement is applicable only to the primary key fields, but it is possible to ask the system to also insist on setting any other field:
new Entity\StringField('ISBN', array(
'required' => true
))
Now, it will not be possible to add a new book without specifying its ISBN code.
Column Name Mapping
When describing entities for an already existing table you may wish to rename the column. For example, initially the ISBN field in the table my_book is named as ISBNCODE, and the old code uses this column name in SQL-query. If you wish to optimize the name, making it a more readable ISBN in the new API, you may use the parameter column_name:
new Entity\StringField('ISBN', array(
'required' => true,
'column_name' => 'ISBNCODE'
))
There are other instances when the same physical column of the table stores values that differ in meaning. In this case, we can create several entity fields with the same column_name.
ExpressionField Expressions
The system provides for the storage of data as is as well as its transformation during sampling. Let us assume that we need to obtain the age of a book in days at the same time when we obtain the issue date. This number is difficult to store in the database since, in this case, we would have to recalculate and update data daily. The age can be calculated on the side of the database:
SELECT DATEDIFF(NOW(), PUBLISH_DATE) AS AGE_DAYS FROM my_book
For this, it is necessary to describe a virtual field in the entity whose value is based on a SQL expression with other field(s):
new Entity\ExpressionField('AGE_DAYS',
'DATEDIFF(NOW(), %s)', array('PUBLISH_DATE')
)
As with the rest of fields, the first parameter to set is the name. The second parameter is the text of an SQL expression, but in this case other entity fields must be replaced with placeholders according to the format sprintf. The third parameter should transmit the array with entity field names in a certain order set in the expression.
Note: %s or %1$s, %2$s, etc. are recommended as placeholders. For example, when several fields (FIELD_X + FIELD_Y) * FIELD_X
participate in the expression EXPR, the expression can be described as follows:
'(%s + %s) * %s', [FIELD_X, FIELD_Y, FIELD_X];
or:
'(%1$s + %2$s) * %1$s', [FIELD_X, FIELD_Y]
.
Very often, expressions can be used to aggregate data (for example, COUNT(*)
or SUM(FIELD)
). These examples will be reviewed in the chapter Data Retrieval.
Note: expression fields can be used only in data sampling to select, filter, group, and sort according to them. Since such columns do not exist physically in the database table, there is no place to write the field value, and the system will generate an exception.
User-Defined (Custom) Fields
In addition to the fields ScalarField and ExpressionField, the entity may contain User-Defined Fields. They are configured through the Administrative interface and require no additional description on the entity side. Only a selected Object of the user-defined field must be indicated in the entity:/p>
class BookTable extends Entity\DataManager
{
...
public static function getUfId()
{
return 'MY_BOOK';
}
...
}
Note: Support for SqlExpression values for user fields in ORM is added starting from the Main module version 20.5.200 (main).
Later on, it is this identifier that must be indicated when attaching user-defined fields to an entity:
Thus, it is possible to select and update values of user-defined fields on the same basis as values of standard fields of an entity.
Example
The following entity was obtained following this chapter:
namespace SomePartner\MyBooksCatalog;
use Bitrix\Main\Entity;
class BookTable extends Entity\DataManager
{
public static function getTableName()
{
return 'my_book';
}
public static function getUfId()
{
return 'MY_BOOK';
}
public static function getMap()
{
return array(
new Entity\IntegerField('ID', array(
'primary' => true,
'autocomplete' => true
)),
new Entity\StringField('ISBN', array(
'required' => true,
'column_name' => 'ISBNCODE'
)),
new Entity\StringField('TITLE'),
new Entity\DateField('PUBLISH_DATE')
);
}
}
// code to create a table in MySQL
// (obtained by calling BookTable::getEntity()->compileDbTableStructureDump())
CREATE TABLE `my_book` (
`ID` int NOT NULL AUTO_INCREMENT,
`ISBN` varchar(255) NOT NULL,
`TITLE` varchar(255) NOT NULL,
`PUBLISH_DATE` date NOT NULL,
PRIMARY KEY(`ID`)
);
Therefore, it is possible to describe regular scalar fields, single out a primary key from among them, and indicate the autoincrementive fields and the required fields. If there is a discrepancy between the column name in the table and a desired name in the entity, it can be fixed.
Attention! The getMap method is used only as a means to obtain the primary configuration of an entity. If you want to obtain the actual list of entity fields, please use the method BookTable::getEntity()->getFields().
The only thing left is to record the entity code in the project. According to the general file naming rules in D7, the entity code must be stored in the file: local/modules/somepartner.mybookscatalog/book.php
After that, the system will automatically connect the file upon finding calls of the BookTable class.
Note: the example above uses the recommended data entry. The legacy entry form as an array:
'ID' => array(
'data_type' => 'integer',
'primary' => true,
'autocomplete' => true,
),
has been kept for compatibility. Upon initialization, objects of classes
\Bitrix\Main\Entity\*
are still created. You can use both variants, but recommended and correct variant is via objects.
Operations with Entities
There are three methods available for writing inside the described class: BookTable::add, BookTable::update, BookTable:delete.
BookTable::add
The add-entry method admits as an input parameter an array with values containing entity field names as the keys:
namespace SomePartner\MyBooksCatalog;
use Bitrix\Main\Type;
$result = BookTable::add(array(
'ISBN' => '978-0321127426',
'TITLE' => 'Patterns of Enterprise Application Architecture',
'PUBLISH_DATE' => new Type\Date('2002-11-16', 'Y-m-d')
));
if ($result->isSuccess())
{
$id = $result->getId();
}
The method returns the result object Entity\AddResult, and the example above shows how to check the successful adding of an entry and obtain the ID of the added entry.
Note. The objects of the class Bitrix\Main\Type\Date and Bitrix\Main\Type\DateTime must be used as values of the fields DateField and DateTimeField and also for user-defined fields Date and Date with Time. By default, the designer receives a line date in the website format but the format of the date to be submitted can also be indicated explicitly.
Attention! The fields must be used in upper case: FIELDS. This field in lower case is reserved for needs of the system. In similar fashion, the field auth_context is reserved by the system as well.
BookTable::update
Entry update follows a similar procedure; only the value of the primary key is added to the array of values in the parameters:
$result = BookTable::update($id, array(
'PUBLISH_DATE' => new Type\Date('2002-11-15', 'Y-m-d')
));
In the example, the date indicated in the new entry is corrected. As a result, the object Entity\UpdateResult is returned, and it also has a test method isSuccess() (to make sure that there was no errors in the query) and, additionally, it is possible to learn whether the entry was actually updated: getAffectedRowsCount().
BookTable::delete
Only the primary key is needed to delete the record:
$result = BookTable::delete($id);
Operation Results
If one or more errors occur during the operation, their text can be obtained from the result:
$result = BookTable::update(...);
if (!$result->isSuccess())
{
$errors = $result->getErrorMessages();
}
Default Values
Sometimes the majority of new entries always contain the same value for a certain field or it is calculated automatically. Let us assume that the book catalog has today’s date as the issue/publishing date by default (it is logical to add a book to the catalog on the day of its issue). Let us return to the description of the field in the entity and use the parameter default_value:
new Entity\DateField('PUBLISH_DATE', array(
'default_value' => new Type\Date
))
Now, when adding an entry with no expressly indicated issue date, its value will be the current day:
$result = BookTable::add(array(
'ISBN' => '978-0321127426',
'TITLE' => 'Some new book'
));
Let us consider a more complicated task: there is no possibility to promptly add books on the day of their issue but it is known that, as a rule, new books are issued on Fridays. Accordingly, they will be added only in the course of the following week:
new Entity\DateField('PUBLISH_DATE', array(
'default_value' => function () {
// figure out last friday date
$lastFriday = date('Y-m-d', strtotime('last friday'));
return new Type\Date($lastFriday, 'Y-m-d');
}
))
Any callable value can be the value of the parameter default_value: a function name, an array from class/object and a name of the method, or an anonymous function.
Validators
Before writing new data to the database their correctness must be checked without fail. This can be done with the help of validators:
new Entity\StringField('ISBN', array(
'required' => true,
'column_name' => 'ISBNCODE',
'validation' => function() {
return array(
new Entity\Validator\RegExp('/[\d-]{13,}/')
);
}
))
Now each time you add or edit an entry, the ISBN will be checked using the template [\d-]{13,}
– the code must contain only numbers and a hyphen, with a minimum of 13 digits.
Validation is set using the parameter validation in the field designer and is a callback that returns an array of validators.
Note: Why validation – callback, and not just an array of validators? It is a kind of deferred load: validators will be instantiated only when data validation is really needed. Generally no validation is needed for data sampling from the database.
An inheritor Entity\Validator\Base
or any callable that is to return a true or a text of an error, or an object of Entity\FieldError
(if you want to use own code of the error) is accepted as a validator.
It is known for sure that the ISBN code must contain 13 digits, and these digits can be separated by several hyphens:
978-0321127426
978-1-449-31428-6
9780201485677
To make sure that there are exactly 13 digits, let us write our own validator:
new Entity\StringField('ISBN', array(
'required' => true,
'column_name' => 'ISBNCODE',
'validation' => function() {
return array(
function ($value) {
$clean = str_replace('-', '', $value);
if (preg_match('/^\d{13}$/', $clean))
{
return true;
}
else
{
return ‘The ISBN code must contain 13 digits.’;
}
}
);
}
))
The value of this field is submitted to the validator as the first parameter, but more optional information is also available:
new Entity\StringField('ISBN', array(
'required' => true,
'column_name' => 'ISBNCODE',
'validation' => function() {
return array(
function ($value, $primary, $row, $field) {
// value – field value
// primary – an array with the primary key, in this case [ID => 1]
// row – all arrays of data submitted to ::add or ::update
// field – an object of the field under validation – Entity\StringField('ISBN', ...)
}
);
}
))
This set of data allows for a much wider range of complex checks.
If several validators are attached to a field and there is a need to find out on the program level which of them exactly has worked, the error code can be used. For example, the last digit of the ISBN code is a control digit that serves to check the correctness of the numerical part of ISBN. You have to add a validator for checking it and to process its result in a specific way:
// describing validator in the entity field
new Entity\StringField('ISBN', array(
'required' => true,
'column_name' => 'ISBNCODE',
'validation' => function() {
return array(
function ($value) {
$clean = str_replace('-', '', $value);
if (preg_match('/^\d{13}$/', $clean))
{
return true;
}
else
{
return ‘ISBN code must contain 13 digits.’;
}
},
function ($value, $primary, $row, $field) {
// checking the last digit
// ...
// if the number is wrong, a special error is returned
return new Entity\FieldError(
// if the number is wrong, a special error is returned
);
}
);
}
))
// performing the operation
$result = BookTable::update(...);
if (!$result->isSuccess())
{
// checking which errors have been revealed
$errors = $result->getErrors();
foreach ($errors as $error)
{
if ($error->getCode() == 'MY_ISBN_CHECKSUM')
{
// our validator has worked
}
}
}
2 standard error codes are available by default: BX_INVALID_VALUE if the validator has worked, and BX_EMPTY_REQUIRED if no required field is indicated when adding an entry.
Validators work both when adding new entries and when updating the existing entries. This behavior is based on the general purpose of validators consisting in guaranteeing correct and integral data in the database. The event mechanism is available in order to check data only upon their addition or updating and also for other manipulations.
We recommend that you use standard validators in standard situations:
Entity\Validator\RegExp
– check by regular expression,
Entity\Validator\Length
– check the minimum/maximum line length,
Entity\Validator\Range
– check the minimum/maximum number value,
Entity\Validator\Unique
– check the uniqueness of a value.
The validators described above cannot apply to the User-defined fields. Their values shall be configured in field settings through the administrative interface.
Events
In the example with validators, one of the checks for the ISBN field consisted in checking the availability of 13 digits. In addition to numbers, ISBN code may include hyphens, but technically speaking they have no value. In order to store only “clean” data in the database (13 digits only, without hyphens), we can use an internal event handler:
class BookTable extends Entity\DataManager
{
...
public static function onBeforeAdd(Entity\Event $event)
{
$result = new Entity\EventResult;
$data = $event->getParameter("fields");
if (isset($data['ISBN']))
{
$cleanIsbn = str_replace('-', '', $data['ISBN']);
$result->modifyFields(array('ISBN' => $cleanIsbn));
}
return $result;
}
}
The method onBeforeAdd set up in the entity is automatically recognized by the system as a handler for the event “before addition” thus allowing change of data or additional checks to be done in it. In the example, we have changed the ISBN code using the method modifyFields.
// before transformation
978-0321127426
978-1-449-31428-6
9780201485677
// after transformation
9780321127426
9781449314286
9780201485677
After such a transformation, we can return again to the neat validator RegExp instead of using an anonymous function (because we already know that the value will contain no acceptable hyphens and only numbers must remain):
'validation' => function() {
return array(
//function ($value) {
// $clean = str_replace('-', '', $value);
//
// if (preg_match('/^\d{13}$/', $clean))
// {
// return true;
// }
// else
// {
// return 'The ISBN code must contain 13 digits.';
// }
//},
new Entity\Validator\RegExp('/\d{13}/'),
...
);
}
In addition to data change, the event handler makes it possible to delete data or even abort the operation. For example, let us assume that the updating of the ISBN code for the books that already exist in the catalog must be prohibited. It can be done in the event handler onBeforeUpdate using one of two ways:
public static function onBeforeUpdate(Entity\Event $event)
{
$result = new Entity\EventResult;
$data = $event->getParameter("fields");
if (isset($data['ISBN']))
{
$result->unsetFields(array('ISBN'));
}
return $result;
}
In this option, the ISBN will be deleted “with no fuss” as if it were not submitted. The second option consists in prohibiting its update and generating an error:
public static function onBeforeUpdate(Entity\Event $event)
{
$result = new Entity\EventResult;
$data = $event->getParameter("fields");
if (isset($data['ISBN']))
{
$result->addError(new Entity\FieldError(
$event->getEntity()->getField('ISBN'),
'Changing the ISBN code for the existing books is prohibited'
));
}
return $result;
}
If an error is returned, we have formed the object Entity\FieldError
in order for us to learn during subsequent error processing in which field, exactly, the check was activated. If the error applies to more than one field or to the entire entry, the use of the object Entity\EntityError
will be more appropriate:
public static function onBeforeUpdate(Entity\Event $event)
{
$result = new Entity\EventResult;
$data = $event->getParameter("fields");
if (...) // comprehensive data check
{
$result->addError(new Entity\EntityError(
'Impossible to update an entry'
));
}
return $result;
}
Two events were used in the examples: onBeforeAdd and onBeforeUpdate, there are nine such events in total:
- onBeforeAdd (parameters: fields)
- onAdd (parameters: fields)
- onAfterAdd (parameters: fields, primary)
- onBeforeUpdate (parameters: primary, fields)
- onUpdate (parameters: primary, fields)
- onAfterUpdate (parameters: primary, fields)
-
onBeforeDelete (parameters: primary)
- onDelete (parameters: primary)
- onAfterDelete (parameters: primary)
The following diagram shows the sequence in which the event handlers are called, and the actions a handler may carry out.
It goes without saying that these events can be handled in the entity itself as well as in the methods with the same name. In order to subscribe to an event in an arbitrary point of script execution, call for the event manager:
$em = \Bitrix\Main\ORM\EventManager::getInstance();
$em->addEventHandler(
BookTable::class, // entity class
DataManager::EVENT_ON_BEFORE_ADD, // event code
function () { // your callback
var_dump('handle entity event');
}
);
Value formatting
Sometimes it may become necessary to store data in one format and work with them in the program in another. The most common example: work with an array and its serialization before saving into the database. For this, the field parameters save_data_modification and fetch_data_modification are available. They are set up similarly to the validators through callback.
Let us use the example of a book catalog in order to describe the text field EDITIONS_ISBN: it will store the ISBN codes of other editions of the book, if any:
new Entity\TextField('EDITIONS_ISBN', array(
'save_data_modification' => function () {
return array(
function ($value) {
return serialize($value);
}
);
},
'fetch_data_modification' => function () {
return array(
function ($value) {
return unserialize($value);
}
);
}
))
We have indicated the serialization of the value before saving into the database in the parameter save_data_modification, and we have set up de-serialization during sampling from the database in the parameter fetch_data_modification. Now, when writing business logic you can simply work with the array without having to look into conversion issues.
Attention! Before creating a serialized field, make sure the serialization will not interfere during filtering or linking tables. Search by a single value in WHERE among serialized lines is highly inefficient. You may want to opt for
a normalized data storage scheme.
Since serialization is the most typical example for conversion of values it is singled out into a separate parameter serialized:
new Entity\TextField('EDITIONS_ISBN', array(
'serialized' => true
))
However, you can still describe your callables for other data modification options.
Value calculating
More often than not, developers have to implement counters where a new value is to be calculated on the database side for the sake of data integrity instead of selecting the old value and recalculating it on the application side. In other words, the queries of the following type must be executed:
UPDATE my_book SET READERS_COUNT = READERS_COUNT + 1 WHERE ID = 1
If the numeric field, READERS_COUNT is described in the entity, the counter increment can be launched as follows:
BookTable::update($id, array(
'READERS_COUNT' => new DB\SqlExpression('?# + 1', 'READERS_COUNT')
));
The placeholder ?# means that the following argument in the designer is the database ID – the name of the database, table, or column, and this value will be masked appropriately. For all variable parameters, the use of placeholders is highly recommended. This approach will help to avoid problems with SQL injections.
For example, if an increment number of readers is variable, it would be better to describe the expression as follows:
// correct
BookTable::update($id, array(
'READERS_COUNT' => new DB\SqlExpression('?# + ?i', 'READERS_COUNT', $readersCount)
));
// incorrect
BookTable::update($id, array(
'READERS_COUNT' => new DB\SqlExpression('?# + '.$readersCount, 'READERS_COUNT')
));
The list of placeholders currently available:
- ? or ?s – the value is masked in put between single quotes '
- ?# – the value is masked as an identifier
- ?i – the value is reduced to integer
- ?f – the value is reduced to float
Error warnings
The examples above have a peculiarity that the data update query is called without checking the result.
// call without checking successful query execution
BookTable::update(...);
// with check
$result = BookTable::update(...);
if (!$result->isSuccess())
{
// error processing
}
The second option is undoubtedly preferable from the point of view of control. However, if the code is executed only in the agent mode, we have no use for the list of errors occurred during validation. In this case, if the query has not gone through due to a “failed” validation and isSuccess() check was not called, the system will generate E_USER_WARNING with a list of errors which may be seen in the website log (provided that .settings.php is set up properly).
In view of the results of this chapter, some changes have occurred in the entity description. It looks as follows now:
namespace SomePartner\MyBooksCatalog;
use Bitrix\Main\Entity;
use Bitrix\Main\Type;
class BookTable extends Entity\DataManager
{
public static function getTableName()
{
return 'my_book';
}
public static function getUfId()
{
return 'MY_BOOK';
}
public static function getMap()
{
return array(
new Entity\IntegerField('ID', array(
'primary' => true,
'autocomplete' => true
)),
new Entity\StringField('ISBN', array(
'required' => true,
'column_name' => 'ISBNCODE',
'validation' => function() {
return array(
new Entity\Validator\RegExp('/\d{13}/'),
function ($value, $primary, $row, $field) {
// check the last digit
// ...
// if the digit is incorrect we will return a special error
return new Entity\FieldError(
$field, 'ISBN control digit does not match', 'MY_ISBN_CHECKSUM'
);
}
);
}
)),
new Entity\StringField('TITLE'),
new Entity\DateField('PUBLISH_DATE', array(
'default_value' => function () {
// figure out last friday date
$lastFriday = date('Y-m-d', strtotime('last friday'));
return new Type\Date($lastFriday, 'Y-m-d');
}
)),
new Entity\TextField('EDITIONS_ISBN', array(
'serialized' => true
)),
new Entity\IntegerField('READERS_COUNT')
);
}
public static function onBeforeAdd(Entity\Event $event)
{
$result = new Entity\EventResult;
$data = $event->getParameter("fields");
if (isset($data['ISBN']))
{
$cleanIsbn = str_replace('-', '', $data['ISBN']);
$result->modifyFields(array('ISBN' => $cleanIsbn));
}
return $result;
}
}
Copy this code and play with all the options described above.
Objects
To start using the objects you need a described entity only. Just replace fetch with fetchObject in your code:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
Now $book - is a full-scale object of Book entity, having multiple methods for manipulating its data and establishing relations with other entities.
Object class
All entity objects are descendants of class Bitrix\Main\ORM\Objectify\EntityObject, with each entity having its own class for objects. By default, such class is created automatically. When you have generated ORM class annotation, with IDE highlighting this moment:
As seen above, EO_Book object class is located in the same namespace as the Table class, with the same name, but with instead of suffix Table has the prefix EO_ (abbreviated EntityObject). Such prefix is added for backward compatibility: existing projects already can have the class Book or structure
use Some\Another\Book;
resulting in conflict of repeated use of Book word. We deemed the prefix EO_ as sufficiently unique. Particularly, when in a standard case use of _ character contradicts to code naming standards: no conflicts should be present with manually described classes.
At the same time, we have allowed to conveniently customize your own, suitable class name and even place such class in another namespace, if required:
//File bitrix/modules/main/lib/test/typography/book.php
namespace Bitrix\Main\Test\Typography;
class Book extends EO_Book
{
}
Key moment - inheriting from object's base virtual class EO_Book.
Entity must be notified about a new class as follows:
//File bitrix/modules/main/lib/test/typography/booktable.php
namespace Bitrix\Main\Test\Typography;
class BookTable extends Bitrix\Main\ORM\Data\DataManager
{
public static function getObjectClass()
{
return Book::class;
}
//...
}
Now, the method fetchObject will return objects of class Bitrix\Main\Test\Typography\Book. And after re-generating annotations, the new class start showing IDE:
Please be advised: defining your own custom classes is recommended only when you need to directly use the class name or for expanding the class with your extra features. It's not recommended to use names for standard classes in your code, for example instanceof EO_Book, new EO_Book or EO_Book::class. In such cases, it's preferable to describe your class with more "authentic" name that corresponds to naming standards, or to use impersonal methods BookTable::getObjectClass(), BookTable::createObject(), BookTable::wakeUpObject() and etc.
You may not only add your own functionality in your class, but also re-define standard named methods. It's not recommended to use descriptions for class properties with such names as primary, entity and dataClass because such names have been already used by the base class.
Named methods
Majority of methods are designated as named methods. This means that each field has the available set of personal methods:
$book->getTitle();
$book->setTitle($value);
$book->remindActualTitle();
$book->resetTitle();
// and etc.
You can overview full list of methods in other articles under this section.
The approach mentioned above was selected due to several reasons:
- incapsulation - you can control access individually to each field;
- convenient use - no need to memorize field names for each entity by heart, IDE will remind you and will expedite input by autocompletion;
- code readability - such entries look coherently and exhaustive.
With this approach, all methods have an alternative in the form of universal methods, getting field name as one of arguments:
$fieldName = 'TITLE';
$book->get($fieldName);
$book->set($fieldName, $value);
$book->remindActual($fieldName);
$book->reset($fieldName);
// and etc.
Such approach is convenient when field names are stored in memory and you can handle them in anonymized mode.
The named method you have specified directly is called when describing your class for object and re-defining any named method via a universal method:
namespace Bitrix\Main\Test\Typography;
class Book extends EO_Book
{
public function getTitle()
{
return 'custom title';
}
}
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
echo $book->getTitle(); // prints 'custom title'
echo $book->get('TITLE'); // also prints 'custom title'
Exception is the method fill: it doesn't call the named method. Design-wise, this method is aimed at optimizing database handling. In case of simultaneously calling several fields - simultaneous calls for individual methods will create an excessive load.
Internal implementation for named methods is based on magic-method __call. Code generation can act as an alternative: compiling of classes with all methods and their subsequent caching. Bitrix24 has selected the magic method due the following reasons:
- lower memory consumption compared to code generation, when system requires handling of excessively cumbersome classes;
- no longer required task of caching the generated classes and monitoring of entity updates.
Disadvantage of magic-methods is an increased consumption of processor computing resources, which can be resolved in individual cases by directly specifying frequently used methods (as it's done with the method getId in the base class). At the same time, such issue is the most easy to scale out, allowing to add new computing resources instead of a perpetual upgrade of a single operational computing power.
Unavailable magic-methods and code generation would prevent automatic coverage of all fields from the class Table: you would need to describe all necessary fields and methods manually.
Value types
Objects convert values to field type. It means that values will have the value formats and string - the string formats:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
var_dump($book->getId());
// prints int 1
var_dump($book->getTitle());
// prints string 'Title 1' (length=7)
Please, be advised regarding to BooleanField: expects true or false, despite the fact that database could store other values:
//(new BooleanField('IS_ARCHIVED'))
// ->configureValues('N', 'Y'),
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
var_dump($book->getIsArchived());
// prints boolean true
// set values are boolean as well
$book->setIsArchived(false);
Data read (get, require, remindActual, primary, collectValues, runtime)
- get
Data reading is implemented using several methods. The most simple returns a field value or null when value is missing (for example, when field is not specified in select when retrieved):
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$title = $book->getTitle();
- require
When you are sure that field must be completed by a value and the scenario is not viable without this value, you can set this value as mandatory/required by the method require:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$title = $book->requireTitle();
In this case, the result requireTitle() won't be different from the abovementioned getTitle(). And the next example will finish with thrown exception, because field won't be containing a value:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, ['select' => ['ID', 'PUBLISHER_ID', 'ISBN']])
->fetchObject();
$title = $book->requireTitle();
// SystemException: "TITLE value is required for further operations"
- remindActual
One more getter remindActual will be useful when re-setting value to differentiate the original value from the previously set value during the session and not yet saved in the database:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
echo $book->getTitle();
// prints "Title 1"
$book->setTitle("New title");
echo $book->getTitle();
// prints "New title"
echo $book->remindActualTitle();
// prints "Title 1"
As an alternative, you can use universal unnamed methods:
$fieldName = 'TITLE';
$title = $book->get($fieldName);
$title = $book->require($fieldName);
$title = $book->remindActual($fieldName);
- primary
System "getter" primary is implemented as a virtual read-only property, to avoid using the method getPrimary(), thus reserving the PRIMARY field name with corresponding named getter method. Property returns primary key values in the array's format independently from whether the primary key is or composite or singular:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$primary = $book->primary;
// returns ['ID' => 1]
$id = $book->getId();
// returns 1
- collectValues
Method collectValues is used to get all object values as an array.
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$values = $book->collectValues();
This example returns all available values. When some field values are re-set using the "setter", but not yet saved, returns these specific values. Unmodified fields source actual values.
You can use optional filters to specify set of fields and data type:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$values = $book->collectValues(\Bitrix\Main\ORM\Objectify\Values::ACTUAL);
// returns only current values, without not yet saved values
$values = $book->collectValues(\Bitrix\Main\ORM\Objectify\Values::CURRENT);
// returns only current values, not yet saved in the database
$values = $book->collectValues(\Bitrix\Main\ORM\Objectify\Values::ALL);
// equals to calling collectValues() without parameters - first CURRENT, then ACTUAL
Second argument passes the mask, similar to the used in fill, defining field types:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$values = $book->collectValues(
\Bitrix\Main\ORM\Objectify\Values::CURRENT,
\Bitrix\Main\ORM\Fields\FieldTypeMask::SCALAR
);
// returns only updated scalar field values
$values = $book->collectValues(
\Bitrix\Main\ORM\Objectify\Values::ALL,
\Bitrix\Main\ORM\Fields\FieldTypeMask::ALL & ~\Bitrix\Main\ORM\Fields\FieldTypeMask::USERTYPE
);
// returns values for all fields, except for user fields
- runtime
Only universal get is provided for runtime fields created within individual queries:
$author = \Bitrix\Main\Test\Typography\AuthorTable::query()
->registerRuntimeField(
new \Bitrix\Main\Entity\ExpressionField(
'FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME']
)
)
->addSelect('ID')
->addSelect('FULL_NAME')
->where('ID', 17)
->fetchObject();
echo $author->get('FULL_NAME');
// prints 'Name 17 Last name 17'
Storing of such values is isolated from standard field values inside the object and, correspondingly, the rest of data handling methods are not applicable.
Write (set, reset, unset)
- set
Setting value is performed in the familiar manner:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$book->setTitle("New title");
The object memorizes its original values. From this moment, the current value can be accessed via the main "getter" get and the database original/actual value can be accessed via auxiliary "getter" method remindActual:
$book->getTitle(); // current value
$book->remindActualTitle(); // actual database value
Primary key can be set only in new objects, you cannot update it in existing objects. When it's needed, you have to create a new object and delete the old one. Also, Bitrix\Main\ORM\Fields\ExpressionField fields cannot be set due to values being calculated automatically and cannot be modified from outside.
When setting a value different from the current value, such value won't be modified and won't be included into SQL query when saving the object.
- reset
To cancel the new value and rollback to the old one, you can use the auxiliary "setter" method reset:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
echo $book->getTitle();
// prints "Title 1"
$book->setTitle("New title");
echo $book->getTitle();
// prints "New title"
$book->resetTitle();
echo $book->getTitle();
// prints "Title 1"
- unset
One more auxiliary "setter" method unset deletes object as if it wasn't retrieved from the database and wasn't set at all:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
echo $book->getTitle();
// prints "Title 1"
$book->unsetTitle();
echo $book->getTitle();
// null
"Setter" methods also have universal variants for calling with field title as an argument:
$fieldName = 'TITLE';
$book->set($fieldName, "New title");
$book->reset($fieldName);
$book->unset($fieldName);
All actions to update the value result in updates during session only. Read more about how to save the object to register modifications in the database here: Create and edit (save, new).
Checks (isFilled, isChanged, has)
- isFilled
The method isFilled is used to verify, if object contains a current actual value from the database:
use \Bitrix\Main\Test\Typography\Book;
// Values from fetch* and wakeUp methods are deemed as actual
// the example demonstrates how only primary key is passed when initializing an object
$book = Book::wakeUp(1);
var_dump($book->isTitleFilled());
// false
$book->fillTitle();
var_dump($book->isTitleFilled());
// true
- isChanged
The method isChanged responds to question if a new value was set during the session:
use \Bitrix\Main\Test\Typography\Book;
// object may have source value, but may not
// this doesn't affect the further behaviour
$book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
var_dump($book->isTitleChanged());
// false
$book->setTitle('New title 1');
var_dump($book->isTitleChanged());
// true
Such behaviour is applicable for new objects as well, until their values are stored in the database.
- has
The method has checks, if an object has a field value - an actual value from the database or value, specified during the session. In fact, its an abbreviation from isFilled() || isChanged()
:
use \Bitrix\Main\Test\Typography\Book;
$book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
$book->setIsArchived(true);
var_dump($book->hasTitle());
// true
var_dump($book->hasIsArchived());
// true
var_dump($book->hasIsbn());
// false
Object status
Object can receive 3 statuses:
- new, with data never before saved in the database;
- actual, with data matching to the data stored in the database;
- modified, with data different from the data stored in the database.
You can check object status using the public read-only property state
and class constants \Bitrix\Main\ORM\Objectify\State:
use \Bitrix\Main\Test\Typography\Book;
use \Bitrix\Main\ORM\Objectify\State;
$book = new Book;
$book->setTitle('New title');
var_dump($book->state === State::RAW);
$book->save();
var_dump($book->state === State::ACTUAL);
$book->setTitle('Another one title');
var_dump($book->state === State::CHANGED);
$book->delete();
var_dump($book->state === State::RAW);
// true
Create and edit (save, new)
The method save is used for registering object updates in the database:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$book->setTitle("New title");
$book->save();
Note: if you copy this example and attempt to execute it with a test entity from the namespace Bitrix\Main\Test\Typography, due to specifics of test data you will get an SQL error. You will see, however, that a portion of the query with test data is built correctly.
From the moment of saving, all current object values are converted into actual values:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
echo $book->remindActualTitle();
// prints "Title 1"
$book->setTitle("New title");
echo $book->remindActualTitle();
// prints "Title 1"
$book->save();
echo $book->remindActualTitle();
// prints "New title"
Regarding the new objects, there are two aprroaches for creating them. The most readable approach - directly via instantiation:
$newBook = new \Bitrix\Main\Test\Typography\Book;
$newBook->setTitle('New title');
$newBook->save();
$newAuthor = new \Bitrix\Main\Test\Typography\EO_Author;
$newAuthor->setName('Some name');
$newAuthor->save();
The method operates with both standard EO_ classes and re-defined classes. Even if you initially used EO_ class, and then decided to create your own class, you won't have to re-write an existing code - backward compatibility will be saved automatically. System class with prefix EO_ becomes an "alias" to your class.
More universal and applicable method to create new object, is to use entity's factory:
$newBook = \Bitrix\Main\Test\Typography\BookTable::createObject();
$newBook->setTitle('New title');
$newBook->save();
By design, new object sets all default values, described in the getMap "mapping". You can get a completely clean object by passing a corresponding argument in constructor:
$newBook = new \Bitrix\Main\Test\Typography\Book(false);
$newBook = \Bitrix\Main\Test\Typography\BookTable::createObject(false);
Value status changes similarly as during editing. Before saving the object value is deemed as current, after saving in the database, it's status becomes as the actual.
Deleting action
Use the delete() method for deletion:
// delete record
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$book->delete();
// deleting by primary key
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
$book->delete();
First only the record for this object is deleted from the database. When you need to delete or attempt to perform other actions with bindings, you will need direct actions.
Deleting using the self-titled method of Table-class, triggers all the required Events. That's why additional action can be described in the event handler onDelete.
Restoring (wakeUp)
When you already have available records, you don't have to retrieve them from database again. Object can be restored having primary key values as a minimum:
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
You may indicate not only the primary key, but also partial or full set of data:
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1', 'PUBLISHER_ID' => 253]);
Similar to creating objects, the method is applicable for EO_ classes as well, for calling directly from entity:
// your class
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(
['ID' => 1, 'TITLE' => 'Title 1']
);
// system class
$book = \Bitrix\Main\Test\Typography\EO_Book::wakeUp(
['ID' => 1, 'TITLE' => 'Title 1']
);
// using the entity factory
$book = \Bitrix\Main\Test\Typography\BookTable::wakeUpObject(
['ID' => 1, 'TITLE' => 'Title 1']
);
You can pass not only scalar values in wakeUp, but also values of Relations:
$book = \Bitrix\Main\Test\Typography\Book::wakeUp([
'ID' => 2,
'TITLE' => 'Title 2',
'PUBLISHER' => ['ID' => 253, 'TITLE' => 'Publisher Title 253'],
'AUTHORS' => [
['ID' => 17, 'NAME' => 'Name 17'],
['ID' => 18, 'NAME' => 'Name 18']
]
]);
Filling action
When not all object fields are completed with data and you need to fill in the missing data, do not use the following approach:
// initially only ID and NAME are available
$author = \Bitrix\Main\Test\Typography\EO_Author::wakeUp(
['ID' => 17, 'NAME' => 'Name 17']
);
// we need to write LAST_NAME, retrieving it from database
$row = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary($author->getId(),
['select' => ['LAST_NAME']]
)->fetch();
// adding value to the object
$author->setLastName($row['LAST_NAME']);
In this case, the value will be deemed as a newly set, but not an actual value (which, theoretically can lead to unforeseen collisions in further object handling).
The correct way to use the name method for object fill:
// initially only ID and NAME are available
$author = \Bitrix\Main\Test\Typography\EO_Author::wakeUp(
['ID' => 17, 'NAME' => 'Name 17']
);
// add LAST_NAME from the database
$author->fillLastName();
In addition to name methods, a generic method is available as well. It provides a significantly more options then other generic methods:
$author = \Bitrix\Main\Test\Typography\EO_Author::wakeUp(17);
// filling several fields
$author->fill(['NAME', 'LAST_NAME']);
// filling all presently unfilled fields
$author->fill();
// filling fields by mask, for example, all unfilled scalar fields
$author->fill(\Bitrix\Main\ORM\Fields\FieldTypeMask::SCALAR);
// unfilled scalar and user fields
$author->fill(
\Bitrix\Main\ORM\Fields\FieldTypeMask::SCALAR
| \Bitrix\Main\ORM\Fields\FieldTypeMask::USERTYPE
);
/*
* Masks are available as follows:
*
* SCALAR - scalar fields (ORM\ScalarField)
* EXPRESSION - expressions (ORM\ExpressionField)
* USERTYPE - user fields
* REFERENCE - relations 1:1 and N:1 (ORM\Fields\Relations\Reference)
* ONE_TO_MANY - relations 1:N (ORM\Fields\Relations\OneToMany)
* MANY_TO_MANY - relations N:M (ORM\Fields\Relations\ManyToMany)
*
* FLAT - scalar fields and expressions
* RELATION - all relations
*
* ALL - absolutely all available fields
*/
If you need to additionally fill in several objects, it's strongly not recommended to perform this command in a loop: this will result to a significant number of queries to the database. Handling several objects of the same type requires a similar Collection method.
Relations (addTo, removeFrom, removeAll)
Detailed description can be found in the next article for Relations. However, you can find specifications for managing method relations below.
Relations fields can be handled by the already described methods get, require, fill, reset, unset.
Important! Despite the fact that Collections object is used as a relations value, relations can be modified only via the methods
addTo,
removeFrom,
removeAll for partner objects. Modifying a collection directly (
add,
remove) doesn't lead to a desirable result.
- addTo
The addTo method adds a new relations between objects:
// publisher initialization
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
->fetchObject();
// book initialization
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2)
->fetchObject();
// adding book to a relation collection
$publisher->addToBooks($book);
// saving
$publisher->save();
Calling the method binds the object only in the system memory, you need to register the changes using the save method.
- removeFrom
Deleting relation associations - removeFrom - operates in a similar manner:
// publisher initialization
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
->fetchObject();
// book initialization
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2)
->fetchObject();
// deleting a single specific publisher book
$publisher->removeFromBooks($book);
// saving
$publisher->save();
- removeAll
Deleting all records can be done via a single call:
// publisher initialization
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
->fetchObject();
// deleting all publisher books
$publisher->removeAllBooks();
// saving
$publisher->save();
This operation requires knowledge of the source value: which Books are available at the Publisher presently. That's why, if BOOKS field is not initially accessed, it will be accessed automatically before deleting.
As an alternative, you can use generic unnamed methods:
$fieldName = 'BOOKS';
$publisher->addTo($fieldName, $book);
$publisher->removeFrom($fieldName, $book);
$publisher->removeAll($fieldName);
ArrayAccess
Interface for accessing object as an array can ensure backward compatibility when switching from arrays to objects:
$author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(17)->fetchObject();
echo $author['NAME'];
// call is similar to the method $author->getName()
$author['NAME'] = 'New name';
// call is similar to the method $author->setName('New name')
As to runtime fields, you may only read their values in this case, but not set them:
$author = \Bitrix\Main\Test\Typography\AuthorTable::query()
->registerRuntimeField(
new \Bitrix\Main\Entity\ExpressionField('FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME'])
)
->addSelect('ID')
->addSelect('FULL_NAME')
->where('ID', 17)
->fetchObject();
echo $author['FULL_NAME'];
// call is similar to the method $author->get('FULL_NAME');
$author['FULL_NAME'] = 'New name';
// throws exception
Collections
Collections class
Operation is based on the same logic as for EO_, and for Objects. Each entity has its own Collection class, inherited from Bitrix\Main\ORM\Objectify\Collection. The Book entity will have the EO_Book_Collection view by default. To set your own class, create a descendants for this class and designate it in the Table entity class:
//File bitrix/modules/main/lib/test/typography/books.php
namespace Bitrix\Main\Test\Typography;
class Books extends EO_Book_Collection
{
}
//File bitrix/modules/main/lib/test/typography/booktable.php
namespace Bitrix\Main\Test\Typography;
class BookTable extends Bitrix\Main\ORM\Data\DataManager
{
public static function getCollectionClass()
{
return Books::class;
}
//...
}
Now the method fetchCollection will return the collection Bitrix\Main\Test\Typography\Books of class objects Bitrix\Main\Test\Typography\Book. Annotations allow for IDE to create hints, simplifying the developer's work.
Access to Collection items
- foreach
Base collection class implements the \Iterator interface, allowing to get items:
$books = \Bitrix\Main\Test\Typography\BookTable::getList()
->fetchCollection();
foreach ($books as $book)
{
// ...
}
- getAll, getByPrimary
Collection items can also be fetched directly. The method getAll returns all the contained objects as an array:
$books = \Bitrix\Main\Test\Typography\BookTable::getList()
->fetchCollection();
$bookObjects = $books->getAll();
echo $bookObjects[0]->getId();
// prints ID value for the first object
The method getByPrimary gets specific objects, contained in the collection:
// 1. example with standard primary key
$books = \Bitrix\Main\Test\Typography\BookTable::getList()
->fetchCollection();
$book = $books->getByPrimary(1);
// book with ID=1
// 2. example with composite primary key
$booksToAuthor = \Bitrix\Main\Test\Typography\BookAuthorTable::getList()
->fetchCollection();
$bookToAuthor = $booksToAuthor->getByPrimary(
['BOOK_ID' => 2, 'AUTHOR_ID' => 18]
);
// assigns relations for book object with ID=2 and author with ID=18
- has, hasByPrimary
You can check availability of specific object in the collection using the method has:
$book1 = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
$book2 = \Bitrix\Main\Test\Typography\Book::wakeUp(2);
$books = \Bitrix\Main\Test\Typography\BookTable::query()
->addSelect('*')
->whereIn('ID', [2, 3, 4])
->fetchCollection();
var_dump($books->has($book1));
// prints false
var_dump($books->has($book2));
// prints true
Similarly, the method hasByPrimary is convenient for checking by the primary key:
$books = \Bitrix\Main\Test\Typography\BookTable::query()
->addSelect('*')
->whereIn('ID', [2, 3, 4])
->fetchCollection();
var_dump($books->hasByPrimary(1));
// prints false
var_dump($books->hasByPrimary(2));
// prints true
- add, []
Objects are added by the method add and interface ArrayAccess, allowing to use the structure []:
$book1 = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
$books = \Bitrix\Main\Test\Typography\BookTable::query()
->addSelect('*')
->whereIn('ID', [2, 3, 4])
->fetchCollection();
$books->add($book1);
// or
$books[] = $book1;
- remove, removeByPrimary
You can delete object from collection either directly or by specifying the primary key:
$book1 = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
$books = \Bitrix\Main\Test\Typography\BookTable::getList()
->fetchCollection();
$books->remove($book1);
// book with ID=1 is deleted from collection
$books->removeByPrimary(2);
// book with ID=2 is deleted from collection
Group actions
Collections allow performing group actions for items contained inside such collections.
- save (adding)
The method save() performs primary saving in case of new objects, by generating a single group query:
use \Bitrix\Main\Test\Typography\Books;
use \Bitrix\Main\Test\Typography\Book;
$books = new Books;
$books[] = (new Book)->setTitle('Title 112');
$books[] = (new Book)->setTitle('Title 113');
$books[] = (new Book)->setTitle('Title 114');
$books->save(true);
// INSERT INTO ... (`TITLE`, `ISBN`) VALUES
('Title 112', DEFAULT),
('Title 113', DEFAULT),
('Title 114', '114-000')
The method receives the parameter $ignoreEvents = true
, cancelling the ORM events when adding entries. In case of multiple inserting with autoincremental field (ID
) you cannot get multiple values of this field which is possible when inserting a single entry using the function similar to mysqli_insert_id()
.
In the rest of cases, when entity doesn't have autoincremental fields, events are left to the discretion of a developer. Events are executed by default.
- save (editing)
The method save() saves already existing, but modified objects using a single query UPDATE:
use \Bitrix\Main\Test\Typography\PublisherTable;
use \Bitrix\Main\Test\Typography\BookTable;
$books = BookTable::getList()->fetchCollection();
$publisher = PublisherTable::wakeUpObject(254);
foreach ($books as $book)
{
$book->setPublisher($publisher);
}
$books->save();
// UPDATE ... SET `PUBLISHER_ID` = '254'
WHERE `ID` IN ('1', '2')
Group update works only in case, when set of updated data is the same for all objects. When at least a single object has different data, all entries/records will be saved individually.
As in the case with the adding action, the update action can disable event using the parameter $ignoreEvents
in the method save(). By default, events are executed for each collection item individually.
- fill
Collection operation fill is a great alternative to the similar operation in Object, executed in a loop. In case of a loop, number of database queries will be equal to the number of objects:
/** @var \Bitrix\Main\Test\Typography\Book[] $books */
$books = [
\Bitrix\Main\Test\Typography\Book::wakeUp(1),
\Bitrix\Main\Test\Typography\Book::wakeUp(2)
];
foreach ($books as $book)
{
$book->fill();
// SELECT ... WHERE ID = ...
// do not do this!
}
In case of collection the query will be an only single one:
$books = new \Bitrix\Main\Test\Typography\Books;
// or $books = \Bitrix\Main\Test\Typography\BookTable::createCollection();
$books[] = \Bitrix\Main\Test\Typography\Book::wakeUp(1);
$books[] = \Bitrix\Main\Test\Typography\Book::wakeUp(2);
$books->fill();
// SELECT ... WHERE ID IN(1,2)
As in the case with objects, the parameter fill can pass the array with names of fields for completing or a mask type as follows:
$books->fill(['TITLE', 'PUBLISHER_ID']);
$books->fill(\Bitrix\Main\ORM\Fields\FieldTypeMask::FLAT);
You can find more details on possible parameter values in this article for Objects fill.
- get*List
Not the most rare case: getting list of values for dedicated field from the query result. In standard case this can look as follows:
$books = \Bitrix\Main\Test\Typography\BookTable::getList()
->fetchCollection();
$titles = [];
foreach ($books as $book)
{
$titles[] = $book->getTitle();
}
Named group "getter" method allows to reduce such loop to a single code line:
$books = \Bitrix\Main\Test\Typography\BookTable::getList()
->fetchCollection();
$titles = $books->getTitleList();
Such "getters" are available for all entity fields and are described in annotations for IDE.
Restoring collection
Restoring collection from completed data operates the same as described in the Objects:
// restoring by primary key
$books = \Bitrix\Main\Test\Typography\Books::wakeUp([1, 2]);
// restoring by set of fields
$books = \Bitrix\Main\Test\Typography\Books::wakeUp([
['ID' => 1, 'TITLE' => 'Title 1'],
['ID' => 2, 'TITLE' => 'Title 2']
]);
With the difference that array with object data is passed to be placed in the collection.
Relations
The diagram below highlights the relations for test entities: Book, Author andPublisher with all combinations and direction for relations: 1:N,
Starting point is the assumption that a book belongs to a single publisher, but can have several authors and it's sold in several stores.
1:N
In our presented test environment, a book can belong to and published strictly by a single publishing house. We'll get the relation "1 publisher - N books".
Book and a Publisher
In such cases, add the field PUBLISHER_ID to the table Books with value designating the Publisher.
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Fields\IntegerField;
class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap()
{
return [
// ...
(new IntegerField('PUBLISHER_ID'))
];
}
}
However, a single field is not enough for ORM to recognize relations between Book and Publisher entities. For ORM to recognize such relations, use the multifields Bitrix\Main\ORM\Fields\Relations. In this case, the Reference type field is needed for indicating "multiple to one" relation:
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap()
{
return [
// ...
(new IntegerField('PUBLISHER_ID')),
(new Reference(
'PUBLISHER',
PublisherTable::class,
Join::on('this.PUBLISHER_ID', 'ref.ID')
))
->configureJoinType('inner')
];
}
}
Reference constructor parameters:
Parameter | Description |
$name | Field name. |
$referenceEntity | Class for bound entity. |
/support/training/course/index.php?COURSE_ID=68&LESSON_ID=24526
$referenceFilter | "Join" conditions. Expects a filter object. In difference to regular filter use, add prefixes "this." and "ref." to column names here to designate association to current and related bounded entities accordingly.
For readability, a created class Bitrix\Main\ORM\Query\Join is available, with a only method on that returns filter object Bitrix\Main\ORM\Query\Filter\ConditionTree, indicating the most popular condition whereColumn. |
|
Additionally, you can configure "join" type. By default, it's left join; example above sets inner join.
Now you can use the described relations during data fetch:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
'select' => ['*', 'PUBLISHER']
])->fetchObject();
echo $book->getPublisher()->getTitle();
// prints Publisher Title 253
Access to Publisher entity object is implemented via "getter" getPublisher(). This way, you can connect more deeply nested relations chains, and use the "getter" chains to reach terminal objects.
To set a relation, it's sufficient to pass Publisher entity object in a corresponding "settter":
// publisher initialization
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::wakeUpObject(253);
// book initialization
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// setting object value
$book->setPublisher($publisher);
// saving
$book->save();
PUBLISHER_ID field value will be completed automatically from the passed object.
The array results will not look so compact:
$result = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
'select' => ['*', 'PUBLISHER']
]);
print_r($result->fetch());
/* prints
Array (
[ID] => 1
[TITLE] => Title 1
[PUBLISHER_ID] => 253
[ISBN] => 978-3-16-148410-0
[IS_ARCHIVED] => Y
[MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_ID] => 253
[MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_TITLE] => Publisher Title 253
)
*/
Unique names, based at the class name and namespace, are assigned to related entity fields. You can use the "alias" mechanism to get more brief and practical names:
$result = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
'select' => ['*', 'PUB_' => 'PUBLISHER']
]);
print_r($result->fetch());
/* prints
Array (
[ID] => 1
[TITLE] => Title 1
[PUBLISHER_ID] => 253
[ISBN] => 978-3-16-148410-0
[IS_ARCHIVED] => Y
[PUB_ID] => 253
[PUB_TITLE] => Publisher Title 253
)
*/
Publisher and Books
At the moment, access to