'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 SetDesiredKeywords 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.
Caching
Starting from Main module version 24.100.0, now you have an option to restrict caching in ORM table.
To restrict caching, add the following description:
public static function isCacheable(): bool
{
return false;
}
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 a relation operates only in a direction "Book" -> "Publisher". To make it two-way, you'll need to describe the relation at the side of Publisher entity:
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\Relations\OneToMany;
class PublisherTable extends DataManager
{
public static function getMap()
{
return [
// ...
(new OneToMany('BOOKS', BookTable::class, 'PUBLISHER'))->configureJoinType('inner')
];
}
}
OneToMany constructor parameters:
Parameter | Description |
$name | Field name. |
$referenceEntity | Class for entity to be related/bound. |
$referenceFilter | `Reference` field name in the partner entity, used to establish the relation. |
|
Additionally, you can re-define join type. By default, uses the type, specified in the Reference field for entity to be related.
Now you can use the described relation when selecting data:
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253, [
'select' => ['*', 'BOOKS']
])->fetchObject();
foreach ($publisher->getBooks() as $book)
{
echo $book->getTitle();
}
// loop prints "Title 1" and "Title 2"
The example above highlights an essential advantage of object-based model compared to array-based one. Despite the fact that two entities were selected (two books were found for a single Publisher), in actuality, result gets only a single object. System has independently recognized this case and included all the publisher books in a single Collection.
Requesting an array from the result returns a classic data structure with duplicating Publisher data:
$data = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253, [
'select' => ['*', 'BOOK_' => 'BOOKS']
])->fetchAll();
// returns
Array (
[0] => Array (
[ID] => 253
[TITLE] => Publisher Title 253
[BOOK_ID] => 2
[BOOK_TITLE] => Title 2
[BOOK_PUBLISHER_ID] => 253
[BOOK_ISBN] => 456-1-05-586920-1
[BOOK_IS_ARCHIVED] => N
)
[1] => Array (
[ID] => 253
[TITLE] => Publisher Title 253
[BOOK_ID] => 1
[BOOK_TITLE] => Title 1
[BOOK_PUBLISHER_ID] => 253
[BOOK_ISBN] => 978-3-16-148410-0
[BOOK_IS_ARCHIVED] => Y
)
)
To add a new Book to the Publisher, use the named "setter" addTo:
// publisher initialization
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253)
->fetchObject();
// book initialization
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2)
->fetchObject();
// adding book into relations collection
$publisher->addToBooks($book);
// saving
$publisher->save();
You can delete relation from the side of a book by assigning another publisher by setting setPublisher() or specifying a null. Specialized "setters" removeFrom() and removeAll() are available to do the same for the Publisher:
// book initialization
$book = \Bitrix\Main\Test\Typography\Book::wakeUp(2);
// publisher initialization
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::getByPrimary(253, [
'select' => ['*', 'BOOKS']
])->fetchObject();
// deleting a specific single publisher book
$publisher->removeFromBooks($book);
// or deleting all publisher books
$publisher->removeAllBooks();
// when saving, the PUBLISHER_ID field is updated in Books to be empty
// the books themselves are not deleted, the only relation is deleted
$publisher->save();
It's important: for ensuring the correct operation the relation field must contain a value. In the example above its specified in the fetched data. You need to preliminarily call the method
fill if you didn't fetch values from the database or aren't sure if they are filled in the specific object:
// book initialization
$book = \Bitrix\Main\Test\Typography\BookTable::wakeUpObject(2);
// the publisher will have a primary key completed only
$publisher = \Bitrix\Main\Test\Typography\PublisherTable::wakeUpObject(253);
// complete relation field
$publisher->fillBooks();
// delete specific single book
$publisher->removeFromBooks($book);
// or delete all books
$publisher->removeAllBooks();
// when saving, the PUBLISHER_ID field in Books will be updated to be empty
// the books themselves won't be deleted
$publisher->save();
In case of arrays, the operations with addTo, removeFrom and removeAll are impossible; you can create relation only from the side of Books entity.
1:1
One-for-one relation operates in a similar manner to one-to-many relations with the only difference that both entities will have both Reference fields instead of Reference + OneToMany pair.
N:M
These are primitive relations without auxiliary data
A book can have several authors and an author can have several books. In such cases, a separate table is created with two fields AUTHOR_ID
and BOOK_ID
. The ORM won't have to issue it as a separate entity, its sufficient to describe the relation by the special field ManyToMany:
//File bitrix/modules/main/lib/test/typography/booktable.php
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap()
{
return [
// ...
(new ManyToMany('AUTHORS', AuthorTable::class))
->configureTableName('b_book_author')
];
}
}
//File bitrix/modules/main/lib/test/typography/authortable.php
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
class AuthorTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap()
{
return [
// ...
(new ManyToMany('BOOKS', BookTable::class))
->configureTableName('b_book_author')
];
}
}
Field description for both entities is optional - a single field can have a description; however, the access to data is granted only to this one field.
The constructor passes the field name and partner entity class. In case of primitive relations, it's sufficient to call the method configureTableName with indicated table name, storing related data. The more complex case will be overviewed below, in the example for Books and Stores relations.
In this case, system memory automatically creates a temporary entity for handling a staging table. In actuality, you won't see its traces anywhere, but for purposes of understanding the process and possible additional settings, we overview such case. System entity for staging table has the following approximate contents:
class ... extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName()
{
return 'b_book_author';
}
public static function getMap()
{
return [
(new IntegerField('BOOK_ID'))
->configurePrimary(true),
(new Reference('BOOK', BookTable::class,
Join::on('this.BOOK_ID', 'ref.ID')))
->configureJoinType('inner'),
(new IntegerField('AUTHOR_ID'))
->configurePrimary(true),
(new Reference('AUTHOR', AuthorTable::class,
Join::on('this.AUTHOR_ID', 'ref.ID')))
->configureJoinType('inner'),
];
}
}
This is no more than standard entity with references (directed relations 1:N) to the source partner entities. Field names are generated based on entity names and their primary keys:
new IntegerField('BOOK_ID') - snake_case from Book + primary field ID
new Reference('BOOK') - snake_case from Book
new IntegerField('AUTHOR_ID') - snake_case from Author + primary field ID
new Reference('AUTHOR') - snake_case from Author
To directly set field name, use the following configuration methods (this is especially pertinent in entities with composite primary keys to avoid confusion):
//File bitrix/modules/main/lib/test/typography/booktable.php
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap()
{
return [
// ...
(new ManyToMany('AUTHORS', AuthorTable::class))
->configureTableName('b_book_author')
->configureLocalPrimary('ID', 'MY_BOOK_ID')
->configureLocalReference('MY_BOOK')
->configureRemotePrimary('ID', 'MY_AUTHOR_ID')
->configureRemoteReference('MY_AUTHOR')
];
}
}
//File bitrix/modules/main/lib/test/typography/authortable.php
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
class AuthorTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap()
{
return [
// ...
(new ManyToMany('BOOKS', BookTable::class))
->configureTableName('b_book_author')
->configureLocalPrimary('ID', 'MY_AUTHOR_ID')
->configureLocalReference('MY_AUTHOR'),
->configureRemotePrimary('ID', 'MY_BOOK_ID')
->configureRemoteReference('MY_BOOK')
];
}
}
The method configureLocalPrimary indicates how the field relation from current entity's primary key will be named. In similar fashion configureRemotePrimary indicates the primary key fields for partner entity key. Methods configureLocalReference and configureRemoteReference set reference names to source entities. For the configuration described above, the relations system entity will be approximately as follows:
class ... extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName()
{
return 'b_book_author';
}
public static function getMap()
{
return [
(new IntegerField('MY_BOOK_ID'))
->configurePrimary(true),
(new Reference('MY_BOOK', BookTable::class,
Join::on('this.MY_BOOK_ID', 'ref.ID')))
->configureJoinType('inner'),
(new IntegerField('MY_AUTHOR_ID'))
->configurePrimary(true),
(new Reference('MY_AUTHOR', AuthorTable::class,
Join::on('this.MY_AUTHOR_ID', 'ref.ID')))
->configureJoinType('inner'),
];
}
}
Just as in case with Reference and OneToMany, you can also redefine type of join by the method configureJoinType (default value - "left"):
(new ManyToMany('AUTHORS', AuthorTable::class))
->configureTableName('b_book_author')
->configureJoinType('inner')
Data reading operates similarly to the relations 1:N:
// fetched from author side
$author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(18, [
'select' => ['*', 'BOOKS']
])->fetchObject();
foreach ($author->getBooks() as $book)
{
echo $book->getTitle();
}
// prints "Title 1" and "Title 2"
// retrieved from side of books
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(2, [
'select' => ['*', 'AUTHORS']
])->fetchObject();
foreach ($book->getAuthors() as $author)
{
echo $author->getLastName();
}
// prints "Last name 17" and "Last name 18"
Once again, retrieving objects instead of arrays is more advantageous due to not having "duplicated" data, as it happens with arrays:
$author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(18, [
'select' => ['*', 'BOOK_' => 'BOOKS']
])->fetchAll();
// вернет
Array (
[0] => Array
[ID] => 18
[NAME] => Name 18
[LAST_NAME] => Last name 18
[BOOK_ID] => 1
[BOOK_TITLE] => Title 1
[BOOK_PUBLISHER_ID] => 253
[BOOK_ISBN] => 978-3-16-148410-0
[BOOK_IS_ARCHIVED] => Y
)
[1] => Array (
[ID] => 18
[NAME] => Name 18
[LAST_NAME] => Last name 18
[BOOK_ID] => 2
[BOOK_TITLE] => Title 2
[BOOK_PUBLISHER_ID] => 253
[BOOK_ISBN] => 456-1-05-586920-1
[BOOK_IS_ARCHIVED] => N
)
)
Creating relations between objects of two entities occurs in the same manner as in case with relations 1:N:
// from author side
$author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(17)
->fetchObject();
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$author->addToBooks($book);
$author->save();
// from books' side
$author = \Bitrix\Main\Test\Typography\AuthorTable::getByPrimary(17)
->fetchObject();
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
$book->addToAuthors($author);
$book->save();
Methods removeFrom and removeAll operate in the same manner.
No constructions are designed for such constructor arrays. See the example below for Books with Stores to overview how to bind entities using the arrays.
Relations with auxiliary data
STORE_ID | BOOK_ID | QUANTITY |
33 | 1 | 4 |
33 | 2 | 0 |
43 | 2 | 9 |
|
When there is additional data (number of books in stock) and not only primary keys for source entities, such relation must be described by a separate entity:
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\Relations\Reference;
use Bitrix\Main\ORM\Query\Join;
class StoreBookTable extends DataManager
{
public static function getTableName()
{
return 'b_store_book';
}
public static function getMap()
{
return [
(new IntegerField('STORE_ID'))
->configurePrimary(true),
(new Reference('STORE', StoreTable::class,
Join::on('this.STORE_ID', 'ref.ID')))
->configureJoinType('inner'),
(new IntegerField('BOOK_ID'))
A ->configurePrimary(true),
(new Reference('BOOK', BookTable::class,
Join::on('this.BOOK_ID', 'ref.ID')))
->configureJoinType('inner'),
(new IntegerField('QUANTITY'))
->configureDefaultValue(0)
];
}
}
ManyToMany fields were used for simple relations, but here their use will be significantly limited. Relations can be created and deleted, but without access to auxiliary field QUANTITY
. Use of removeFrom*() can delete the relation and addTo*() can add the relation with QUANTITY
value only by default, without the option to update the QUANTITY
value. That's why in such cases more flexible approach would be using proxy entity directly:
// book object
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1)
->fetchObject();
// store object
$store = \Bitrix\Main\Test\Typography\StoreTable::getByPrimary(34)
->fetchObject();
// new book and store relations object
$item = \Bitrix\Main\Test\Typography\StoreBookTable::createObject()
->setBook($book)
->setStore($store)
->setQuantity(5);
// saving
$item->save();
Number of books update:
// existing relation object
$item = \Bitrix\Main\Test\Typography\StoreBookTable::getByPrimary([
'STORE_ID' => 33, 'BOOK_ID' => 2
])->fetchObject();
// quantity update
$item->setQuantity(12);
// saving
$item->save();
Deleting the relation:
// existing relation object
$item = \Bitrix\Main\Test\Typography\StoreBookTable::getByPrimary([
'STORE_ID' => 33, 'BOOK_ID' => 2
])->fetchObject();
// deleting
$item->delete();
The relation object is handled in the same manner as objects of any other entities. Arrays must also use standard approaches for data handling:
// adding
\Bitrix\Main\Test\Typography\StoreBookTable::add([
'STORE_ID' => 34, 'BOOK_ID' => 1, 'QUANTITY' => 5
]);
// updating
\Bitrix\Main\Test\Typography\StoreBookTable::update(
['STORE_ID' => 34, 'BOOK_ID' => 1],
['QUANTITY' => 12]
);
// deleting
\Bitrix\Main\Test\Typography\StoreBookTable::delete(
['STORE_ID' => 34, 'BOOK_ID' => 1]
);
As mentioned above, using the field ManyToMany in case with auxiliary data is unproductive. More correct way is to use the type OneToMany:
//File bitrix/modules/main/lib/test/typography/booktable.php
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Fields\Relations\OneToMany;
class BookTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap()
{
return [
// ...
(new OneToMany('STORE_ITEMS', StoreBookTable::class, 'BOOK'))
];
}
}
//File bitrix/modules/main/lib/test/typography/storetable.php
namespace Bitrix\Main\Test\Typography;
use Bitrix\Main\ORM\Fields\Relations\OneToMany;
class StoreTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getMap()
{
return [
// ...
(new OneToMany('BOOK_ITEMS', StoreBookTable::class, 'STORE'))
];
}
}
In such case fetched data won't be different from the relations 1:N, only in this case, returns StoreBook relations objects and not partner-entities:
$book = \Bitrix\Main\Test\Typography\BookTable::getByPrimary(1, [
'select' => ['*', 'STORE_ITEMS']
])->fetchObject();
foreach ($book->getStoreItems() as $storeItem)
{
printf(
'store "%s" has %s of book "%s"',
$storeItem->getStoreId(), $storeItem->getQuantity(), $storeItem->getBookId()
);
// prints store "33" has 4 of book "1"
}
Class annotations
Majority of Object and Collection methods are virtual, processed via magic __call. At the same time, they are created for intuitively clear and self-explanatory named methods; without IDE autocomplete their significance substantially falls.
We have created a special service file with annotation for all entities for IDE to be aware that these methods exist, helping to navigate in the large number of classes and methods.
Starting from the Main version 20.100.0, file with ORM kernel class annotations is included into distribution package and located at /bitrix/modules/main/meta/orm.php
.
The cli-command orm:annotate is used for generating such annotations:
$ cd bitrix
$ php bitrix.php orm:annotate
Modules are scanned during command execution and specifically all files from folders bitrix/modules/[module]/lib. When file detects an entity "mapping" (class Table, subclass Bitrix\Main\ORM\Data\DataManager
), its map is analyzed (list of fields).
Command result contains the file (by default bitrix/modules/orm_annotations.php
), containing description of entity Object and Collection classes. It also declares Table class duplicate and several actually non-existent helper classes, assisting in IDE autocomplete from the moment of query to the use of resulting objects.
By default, scans only the Main module. Scanning random modules can be set directly:
// annotating entities for arbitrary module:
$ php bitrix.php orm:annotate -m tasks
// annotating several modules:
$ php bitrix.php orm:annotate -m main,intranet,faceid
// annotating all modules:
$ php bitrix.php orm:annotate -m all
In the nearest future we plan to introduce monitoring for all known entities in the development mode, to be able to call annotations automatically when updating the fields. Then, console won't have to be used as much often.
Partially, it's convenient to selectively replace classes on re-generation. When annotations already have the described modules, repeat annotation for one of them will update description only one of its classes and other won't be deleted. Use the parameter -c for the reset:
$ php bitrix.php orm:annotate -c -m all
To view all available command parameters, execute the command:
$ php bitrix.php help orm:annotate
Backward compatibility
Introduction of objects release in the main module version 18.0.4 some ORM internal mechanisms were updated and streamlined.
- Field names now are case-insensitive. Previously, two fields LAST_NAME and last_name could be described and these two fields would have been different fields. But now it's the same entity that cannot initialize. This update is related to named methods in Objects.
- The fetched data cannot have an assigned same-name alias in different case, for example: getList(['select' => ['id' => 'ID']]).
- Previously, BooleanField field received an empty string as a value, resulting in erroneous value interpretation. Now an empty string is prohibited, it can be indicated as true/false or values, specified in field configuration.
Objects do not support the following yet:
- Fields with serialization, due to existing inconsistency: field type is indicated as StringField or TextField but actually stores an array, which contradicts to the declared type. Upcoming updates will add the new field type: ArrayField.
Data retrieval
The most frequent objective is to retrieve data with various conditions of filtering, grouping and sorting.
getList
For the new API to be more familiar to a developer, the most popular method name was saved: getList. When previously each getList had its own set of parameters and individual uncustomizable behavior, now this method is the same for all entities and is subject to the same rules.
The entity BookTable, sourced as an example, is not an exception. Which parameters are received by the method BookTable::getList?
BookTable::getList(array(
'select' => ... // fields name to be retrieved in the result
'filter' => ... // filter description for WHERE and HAVING
'group' => ... // directly set fields used to group the result
'order' => ... // sorting parameters
'limit' => ... // number of entries
'offset' => ... // offset for limit
'runtime' => ... // dynamically determined fields
));
getList always returns the object DB\Result
, used to retrieve data via the method fetch():
$rows = array();
$result = BookTable::getList(array(
...
));
while ($row = $result->fetch())
{
$rows[] = $row;
}
To get all entries, you can use the method fetchAll():
$result = BookTable::getList($parameters);
$rows = $result->fetchAll();
// or just as follows:
$rows = BookTable::getList($parameters)->fetchAll();
Now, let's review all parameters in more detail.
select
Parameter `select`
is defined as an array with entity field names:
BookTable::getList(array(
'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
));
// SELECT ISBN, TITLE, PUBLISH_DATE FROM my_book
When due to some reasons you do not like original field names in the result, you can use aliases:
BookTable::getList(array(
'select' => array('ISBN', 'TITLE', 'PUBLICATION' => 'PUBLISH_DATE')
));
// SELECT ISBN, TITLE, PUBLISH_DATE AS PUBLICATION FROM my_book
In this example, the field name `PUBLISH_DATE` replaced with `PUBLICATION` and specifically this name will be used in the resulting array.
When all fields must be selected, you can use the '*' character:
BookTable::getList(array(
'select' => array('*')
));
In this case, only the fields ScalarField will be used, and ExpressionField and associations with other entities won't be affected - they always can be indicated directly.
Calculated field in 'select', skipping runtime
Example below highlights the abovementioned grouping.
When you need the calculated fields only in the `select`
section (which is most likely), the section `runtime`
is optional: you can save time by placing the expression directly into 'select'.
The system allows to use the nested expression to be sequentially expanded in the final SQL code:
BookTable::getList(array(
'select' => array(
'runtime' => array(
new ORM\Fields\ExpressionField('MAX_AGE', 'MAX(%s)', array('AGE_DAYS'))
)
));
// SELECT MAX(DATEDIFF(NOW(), PUBLISH_DATE)) AS MAX_AGE FROM my_book
Be advised, the new Expression's MAX_AGE field has used an already existing Expression field AGE_DAYS. This way, the system allows using nested expressions to be sequentially expanded in the final SQL code.
Already existing Expression field AGE_DAYS was used in the the new Expression field MAX_AGE.
The 'runtime' section can register not only Expression fields, but also the fields of any other types. The 'runtime' mechanism operates to add new field to the entity as if it was initially described in the method getMap. But this field is visible only within a single query - in the next getList call it won't be accessible, you will have to re-register it.
filter
Parameter `filter`
inherited the format iblock filter:
// WHERE ID = 1
BookTable::getList(array(
'filter' => array('=ID' => 1)
));
// WHERE TITLE LIKE 'Patterns%'
BookTable::getList(array(
'filter' => array('%=TITLE' => 'Patterns%')
));
Filter can be multi-levelled with AND/OR:
// WHERE ID = 1 AND ISBN = '9780321127426'
BookTable::getList(array(
'filter' => array(
'=ID' => 1,
'=ISBN' => '9780321127426'
)
));
// WHERE (ID=1 AND ISBN='9780321127426') OR (ID=2 AND ISBN='9781449314286')
BookTable::getList(array(
'filter' => array(
'LOGIC' => 'OR',
array(
// 'LOGIC' => 'AND', // elements are conjoined via AND
'=ID' => 1,
'=ISBN' => '9780321127426'
),
array(
'=ID' => 2,
'=ISBN' => '9781449314286'
)
)
));
Full list of comparative operators that can be used in filter:
- = equal (works with array as well)
- % substring
- > more
- < less
- @ IN (EXPR), DB\SqlExpression array or object is passed as a value
- !@ NOT IN (EXPR), array or object DB\SqlExpression is passed as value
- != not equal to
- !% not substring
- >< between, passes array (MIN, MAX) as a value
- >= more or equal
- <= less or equal
- =% LIKE
- %= LIKE
- == boolean expression for ExpressionField (for example, for EXISTS() or NOT EXISTS())
- !>< not between, passes array(MIN, MAX) as a value
- !=% NOT LIKE
- !%= NOT LIKE
- '==ID' => null strict comparison with NULL by ID
- '!==NAME' => null strict comparison with NULL by NAME
Attention! When comparison operator
= is not indicated directly, executes
LIKE by default. In this case, uses filter code from Iblock module.
Fields type int have:
- [dw]before released object ORM[/dw][di]Starting from main module version 18.0.3.[/di]: = (equality comparison, array expands into set of OR conditions =)
- after object ORM release: IN().
group
The parameter `group`
lists fields to be grouped:
BookTable::getList(array(
'group' => array('PUBLISH_DATE')
));
In majority of cases, there is no need to directly indicate grouping - system does it automatically. Find more details below in section dynamically determined fields.
order
The parameter `order`
allows indicating sorting order:
BookTable::getList(array(
'order' => array('PUBLISH_DATE' => 'DESC', 'TITLE' => 'ASC')
));
BookTable::getList(array(
'order' => array('ID') // sorting direction - ASC
));
offset/limit
Parameters `offset`
and `limit`
help limiting number of selected entries or implementing per page selection:
// 10 recent entries
BookTable::getList(array(
'order' => array('ID' => 'DESC')
'limit' => 10
));
// 5th page with entries, 20 per page
BookTable::getList(array(
'order' => array('ID')
'limit' => 20,
'offset' => 80
));
runtime
Calculated fields (ExpressionField), mentioned in the first section of this lesson are needed mostly not in the entity description, but for selecting various calculations with grouping.
The most simple example, calculation of number of entities, can be performed as follows:
BookTable::getList(array(
'select' => array('CNT'),
'runtime' => array(
new ORM\Fields\ExpressionField('CNT', 'COUNT(*)')
)
));
// SELECT COUNT(*) AS CNT FROM my_book
Calculated field in this example not just converts a field value, but implements arbitrary SQL expression with function COUNT.
After registering a field in`runtime`
, it can be referred not only in `select`
section, but in other sections as well:
BookTable::getList(array(
'select' => array('PUBLISH_DATE'),
'filter' => array('>CNT' => 5),
'runtime' => array(
new ORM\Fields\ExpressionField('CNT', 'COUNT(*)')
)
));
// select days, when more than 5 books were released
// SELECT PUBLISH_DATE, COUNT(*) AS CNT FROM my_book GROUP BY PUBLISH_DATE HAVING COUNT(*) > 5
Note. This example shows the above mentioned automatic grouping - system has identified, what must be grouped by the field PUBLISH_DATE.
When calculated field is required only in section `select`
(as in most cases), the section `runtime`
is optional: you can save time by inserting expression directly into `select`
.
BookTable::getList(array(
'select' => array(
new ORM\Fields\ExpressionField('MAX_AGE', 'MAX(%s)', array('AGE_DAYS'))
)
));
// SELECT MAX(DATEDIFF(NOW(), PUBLISH_DATE)) AS MAX_AGE FROM my_book
Note, another already existing Expression field AGE_DAYS was used inside the new Expression field MAX_AGE. This way, the system allows using inserted expressions, which will be sequentially expanded in the final SQL code.
Fields of any other types can be registered in the section `runtime`
, not only Expression fields. The `runtime`
mechanism operates by adding a new field to an entity as if it was originally described in the method `getMap`. However, such files are going to be visible only once in a single query: in the next call for getList such field won't be available, it must be registered again.
Selection caching
Caching of a specific section is available from version 16.5.9. No need to describe anything in the entity itself. No caching by default.
The key cache
is added to getList parameters:
$res = \Bitrix\Main\GroupTable::getList(array("filter"=>array("=ID"=>1), "cache"=>array("ttl"=>3600)));
The same is implemented in via the Query:
$query = \Bitrix\Main\GroupTable::query();
$query->setSelect(array('*'));
$query->setFilter(array("=ID"=>1));
$query->setCacheTtl(150);
$res = $query->exec();
It's possible that cached selection result will contain the object ArrayResult.
Selections with JOIN are not cached by default. However, you can optionally cache it:
"cache"=>array("ttl"=>3600, "cache_joins"=>true);
//or
$query->cacheJoins(true);
Cache reset is performed in any method add/update/delete. Forced cache reset for the table:
/* Example for user table */
\Bitrix\Main\UserTable::getEntity()->cleanCache();
Administrators can restrict TTL update and caching.
Fetching all elements
Use the parameter count_total with the value true
to get list of all elements without pagination.
$res = MyTable::getList(...'count_total' => true, );
This allows getting the complete list via single query.
$res->getCount(); // all elements without pagination
Short calls
In addition to GetList there are several methods allowing to get specific data in shorter format:
- getById($id) - fetches data using the primary key;
- getByPrimary($primary, array $parameters) - gets data via primary key using additional parameters;
Note: both methods can pass id
either as integer or by directly specified this element as key, by passing an array. You need to use an array if you have several primary
fields. When you pass a non-primary key element in the array, it will be an error.
BookTable::getById(1);
BookTable::getByPrimary(array('ID' => 1));
// such calls will be similar to the next call of getList:
BookTable::getList(array(
'filter' => array('=ID' => 1)
));
- getRowById($id) - fetches by primary key, but returns an array with data;
$row = BookTable::getRowById($id);
// similar result can be retrieved as follows:
$result = BookTable::getById($id);
$row = $result->fetch();
// or as follows
$result = BookTable::getList(array(
'filter' => array('=ID' => $id)
));
$row = $result->fetch();
- getRow(array $parameters) - fetches not using the primary key, but some other parameters. Returns only a single record.
$row = BookTable::getRow(array(
'filter' => array('%=TITLE' => 'Patterns%'),
'order' => array('ID')
));
// similar result can be retrieved as follows:
$result = BookTable::getList(array(
'filter' => array('%=TITLE' => 'Patterns%'),
'order' => array('ID')
'limit' => 1
));
$row = $result->fetch();
Query object
All parameters for getList, getRow as well as others are passed jointly, executing the query and returning the result: all is done in a single call. However, there is an alternative method of query configuration and execution oversight: the object Entity\Query:
// gets data using getList
$result = BookTable::getList(array(
'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
'filter' => array('=ID' => 1)
));
// similar method using Entity\Query
$q = new Entity\Query(BookTable::getEntity());
$q->setSelect(array('ISBN', 'TITLE', 'PUBLISH_DATE'));
$q->setFilter(array('=ID' => 1));
$result = $q->exec();
Such approach can be convenient, when you need flexibility in building a query. For example, when query parameters are not known beforehand and are software-generated, you can use a single Query object instead of several various arguments. This object accumulates query parameters:
$query = new Entity\Query(BookTable::getEntity());
attachSelect($query);
attachOthers($query);
$result = $query->exec();
function attachSelect(Entity\Query $query)
{
$query->addSelect('ID');
if (...)
{
$query->addSelect('ISBN');
}
}
function attachOthers(Entity\Query $query)
{
if (...)
{
$query->setFilter(...);
}
if (...)
{
$query->setOrder(...);
}
}
Also the Entity\Query object allows to build a query without executing it. This can be useful for executing subqueries or simply for getting query text and subsequent use:
$q = new Entity\Query(BookTable::getEntity());
$q->setSelect(array('ID'));
$q->setFilter(array('=PUBLISH_DATE' => new Type\Date('2014-12-13', 'Y-m-d')));
$sql = $q->getQuery();
file_put_contents('/tmp/today_books.sql', $sql);
// as a result, the query "SELECT ID FROM my_book WHERE PUBLISH_DATE='2014-12-31'" will be saved into file, but not executed.
Full list of Entity\Query methods for implementing options described above:
select, group:
- setSelect, setGroup - sets array with field names
- addSelect, addGroup - adds field name
- getSelect, getGroup - returns array with field names
filter:
- setFilter - sets a single- or multidimensional array with filter description
- addFilter - adds a single filter parameter with a value
- getFilter - returns current filter description
order:
- setOrder - sets array with field names and sort order
- addOrder - adds a single field with sort order
- getOrder - returns current sort description
limit/offset:
- setLimit, setOffset - sets value
- getLimit, getOffset - returns current value
runtime fields:
- registerRuntimeField - registers new temporary field for original entity
The Query Object is the key element in data retrieval; its also used inside the standard method getList. That's why re-defining getList methods is ineffective: calling a corresponding single method is OK, but with similar query directly via Query is not.
Pre-set data scope fetch
Global data area
When required, a single table can be described by several entities, by splitting records into segments:
class Element4Table extends \Bitrix\Iblock\ElementTable
{
public static function getTableName()
{
return 'b_iblock_element';
}
public static function setDefaultScope(Query $query)
{
$query->where("IBLOCK_ID", 4);
}
}
class Element5Table extends \Bitrix\Iblock\ElementTable
{
public static function getTableName()
{
return 'b_iblock_element';
}
public static function setDefaultScope(Query $query)
{
$query->where("IBLOCK_ID", 5);
}
}
Method setDefaultScope will be executed on each query, skipping the query object. It can set both filter and any other query parameters.
Local data area
Starting from version 20.5.500 you can pre-set data via methods with*. This is similar to setDefaultScope, but not at the global level, but at the user's level: call when necessary. After describing the method in the entity you can call it in the query constructor:
class UserTable
{
public static function withActive(Query $query)
{
$query->where('ACTIVE', true);
}
}
$activeUsers = UserTable::query()
->withActive()
->fetchCollection();
// WHERE `ACTIVE`='Y'
The object Bitrix\Main\ORM\Query\Query is used as an argument: you can set filter and any other query parameters. Additionally, method can be supplemented by your arguments, also passed when calling from query constructor:
class UserTable
{
public static function withActive(Query $query, $value)
{
$query
->addSelect('LOGIN')
->where('ACTIVE', $value);
}
}
$activeUsers = UserTable::query()
->withActive(false)
->fetchCollection();
// SELECT `LOGIN` ... WHERE `ACTIVE`='N
Data retrieval from stored procedures
ORM is also suitable for such exotic queries for data retrieval targeting not the table, but stored procedures. Such procedures can be created in MSSQL-database.
Indicate the function name in the method getTableName:
public static function getTableName()
{
// return "foo_table_name"
return "foo_table_procedure()";
}
This code won't work as demonstrated. The reason: when using the connection Bitrix\Main\DB\MssqlConnection all table name entries are pre-screened. An attempt to directly execute such query will cause a thrown exception:
MS Sql query error: Invalid object name 'foo_table_procedure()'. (400)
SELECT
[base].[bar] AS [BAR],
[base].[baz] AS [BAZ],
FROM [foo_table_procedure()] [base]
Getting a required result is prevented only by characters [ and ], used by MssqlSqlHelper to protect the "table" name. You can resolve this issue by creating a custom Connection and SqlHelper.
Alternate solution: install a server mssql extension and implement the following architecture:
class MssqlSqlHelper extends Bitrix\Main\DB\SqlHelper
{
public function quote($identifier)
{
if (self::isKnowFunctionalCall($identifier))
{
return $identifier
}
else
{
return parent::quote($identifier);
}
}
}
Where self::isKnownFunctionCall is a verification method, returning true, if $identifier is located in “foo_table_procedure()”
.
Data fetching in relations 1:N and N:M
Logic for LIMIT
Intuitive standby for LIMIT logic:
$iblockEntity = IblockTable::compileEntity(…);
$query = $iblockEntity->getDataClass()::query()
->addSelect('NAME')
->addSelect('MULTI_PROP_1')
->setLimit(5);
$elements = $query->fetchCollection();
You may not expect to get 5 items in this example. The retrieved data limit is indicated at the SQL query level, not at the object level:
SELECT ... FROM `b_iblock_element`
LEFT JOIN `b_iblock_element_property` ...
LIMIT 5
In fact, retrieving 5 property values with corresponding items results in error. That's why the retrieved selection may contain less than 5 items, or a single item without partially fetched property values.
Field selection for relations in a single query
Selecting several relation fields in a single query results in Cartesian join for all records. For example:
$iblockEntity = IblockTable::compileEntity(…);
$query = $iblockEntity->getDataClass()::query()
->addSelect('NAME')
->addSelect('MULTI_PROP_1')
->addSelect('MULTI_PROP_2')
->addSelect('MULTI_PROP_3');
$elements = $query->fetchCollection();
Executes the following query:
SELECT ... FROM `b_iblock_element`
LEFT JOIN `b_iblock_element_property` ... // 15 property values
LEFT JOIN `b_iblock_element_property` ... // 7 property values
LEFT JOIN `b_iblock_element_property` ... // 11 property values
And if intuitively it seems that 15 + 7 + 11 = 33 strings are retrieved, in fact, retrieves 15 * 7 * 11 = 1155 strings. In cases when query contains a lot more properties or values, the resulting records count can be in millions, subsequently leading to the insufficient app's memory for getting a complete result.
Solving the issues
To avoid such issues, the following class Bitrix\Main\ORM\Query\QueryHelper was added along with a universal method decompose:
/**
** Query decomposition with relations 1:N and N:M
**
** @param Query $query
** @param bool $fairLimit. Setting this option first selects object IDs and the following query selects the rest of data with ID filter
** @param bool $separateRelations. Each 1:N or N:M relation is selected using separate queries under this option
** @return Collection
**/
public static function decompose(Query $query, $fairLimit = true, $separateRelations = true)
Parameter fairLimit leads to two queries: first, selects record primary with query-defined Limit / Offset, and then all relations are selected by a single query for all primary.
Additional parameter separateRelations allows executing a separate query per each relation, to avoid Cartesian join for all records.
Returns a complete object collection with already merged data as the result.
Sorting is applied at the primary selection with corresponding formatting. Sorting of relation objects at the top level is not that significant compared to when working with arrays.
ORM filter
The Main module version update 17.5.2 introduced new filter in ORM.
Single conditions
Example of a simple query:
\Bitrix\Main\UserTable::query()
->where("ID", 1)
->exec();
// WHERE `main_user`.`ID` = 1
When you need another comparison operator, it's indicated directly:
\Bitrix\Main\UserTable::query()
->where("ID", "<", 10)
->exec();
// WHERE `main_user`.`ID` < 10
Example with using IS NULL:
\Bitrix\Main\UserTable::query()
->whereNull("ID")
->exec();
// WHERE `main_user`.`ID` IS NULL
There are whereNot* similar methods for all where* methods. Example:
\Bitrix\Main\UserTable::query()
->whereNotNull("ID")
->exec();
// WHERE `main_user`.`ID` IS NOT NULL
In addition to shared where, you can use the following operational methods:
whereNull($column) / whereNotNull($column)
whereIn($column, $values|Query|SqlExpression) / whereNotIn($column, $values|Query|SqlExpression)
whereBetween($column, $valueMin, $valueMax) / whereNotBetween($column, $valueMin, $valueMax)
whereLike($column, $value) / whereNotLike($column, $value)
whereExists($query|SqlExpression) / whereNotExists($query|SqlExpression)
For arbitrary expression with binding to entity fields, use the method [dw]whereExpr[/dw][di]Available from the main module version 20.400.0.[/di]:
\Bitrix\Main\UserTable::query()
->whereExpr('JSON_CONTAINS(%s, 4)', ['SOME_JSON_FIELD'])
->exec();
// WHERE JSON_CONTAINS(`main_user`.`SOME_JSON_FIELD`, 4)
Expression arguments and multipart fields are similar to the ExpressionField
constructor and are inserted using the function [ds]sprintf[/ds][di]sprintf — Returns formatted string.
Learn more in the PHP documentation.[/di].
List of operators is stored at \Bitrix\Main\ORM\Query\Filter\Operator::$operators
(see array keys):
= , <> , != , < , <= , > , >= , in , between , like , exists
Comparison with another field
Separate method whereColumn simplifies the field comparison to one another:
\Bitrix\Main\UserTable::query()
->whereColumn('NAME', 'LOGIN')
->exec();
// WHERE `main_user`.`NAME` = `main_user`.`LOGIN`
This method is not significantly different from the where method, and formally it's the same call with a small wrapper:
\Bitrix\Main\UserTable::query()
->where('NAME', new Query\Filter\Expression\Column('LOGIN'))
->exec();
// WHERE `main_user`.`NAME` = `main_user`.`LOGIN`
whereColumn provides a flexible use for columns used in filter, for example:
\Bitrix\Main\UserTable::query()
->whereIn('LOGIN', [
new Column('NAME'),
new Column('LAST_NAME')
])
->exec();
// WHERE `main_user`.`LOGIN` IN (`main_user`.`NAME`, `main_user`.`LAST_NAME`)
Columns can be used in any operator. They can be perceived specifically as fields for specific entities and not just arbitrary SQL expression.
Multiple conditions
The following record is presented for several conditions:
\Bitrix\Main\UserTable::query()
->where('ID', '>', 1)
->where('ACTIVE', true)
->whereNotNull('PERSONAL_BIRTHDAY')
->whereLike('NAME', 'A%')
->exec();
// WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y' AND `main_user`.`PERSONAL_BIRTHDAY` IS NOT NULL AND `main_user`.`NAME` LIKE 'A%'
Note: boolean fields with values Y/N, 1/0 and etc. can use true and false.
When you need to indicate several conditions in a single call, use the following format: (operator methods can be replaced by operator codes)
\Bitrix\Main\UserTable::query()
->where([
['ID', '>', 1],
['ACTIVE', true],
['PERSONAL_BIRTHDAY', '<>', null],
['NAME', 'like', 'A%']
])
->exec();
// WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y' AND `main_user`.`PERSONAL_BIRTHDAY` IS NOT NULL AND `main_user`.`NAME` LIKE 'A%'
OR and nested filters
Storage of all filter conditions in Query uses the condition container \Bitrix\Main\Entity\Query\Filter\ConditionTree
. In addition to standard conditions, allows to add several instances of ConditionTree, thus creating any levels of branching and nesting depth.
All calls of where shown above - is proxying to base container. Next two calls will lead to completely identical result:
\Bitrix\Main\UserTable::query()
->where([
['ID', '>', 1],
['ACTIVE', true]
])
->exec();
\Bitrix\Main\UserTable::query()
->where(Query::filter()->where([
["ID", '>', 1],
['ACTIVE', true]
]))->exec();
// WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y'
Uses filter object instead of array. This allows to create subfilters and change logic from AND to OR:
\Bitrix\Main\UserTable::query()
->where('ACTIVE', true)
->where(Query::filter()
->logic('or')
->where([
['ID', 1],
['LOGIN', 'admin']
])
)->exec();
// WHERE `main_user`.`ACTIVE` = 'Y' AND (`main_user`.`ID` = 1 OR `main_user`.`LOGIN` = 'admin')
The following chain of calls is permitted:
\Bitrix\Main\UserTable::query()
->where('ACTIVE', true)
->where(Query::filter()
->logic('or')
->where('ID', 1)
->where('LOGIN', 'admin')
)
->exec();
// WHERE `main_user`.`ACTIVE` = 'Y' AND (`main_user`.`ID` = 1 OR `main_user`.`LOGIN` = 'admin')
Expressions
Filter allows ExpressionField used as field names. Such fields are automatically registered as entity field runtime.
\Bitrix\Main\UserTable::query()
->where(new ExpressionField('LNG', 'LENGTH(%s)', 'LAST_NAME'), '>', 10)
->exec();
// WHERE LENGTH(`main_user`.`LAST_NAME`) > '10'
Helper is added to simplify such constructions. The helper builds calculated fields:
\Bitrix\Main\UserTable::query()
->where(Query::expr()->length("LAST_NAME"), '>', 10)
->exec();
// WHERE LENGTH(`main_user`.`LAST_NAME`) > '10'
\Bitrix\Main\UserTable::query()
->addSelect(Query::expr()->count("ID"), 'CNT')
->exec();
// SELECT COUNT(`main_user`.`ID`) AS `CNT` FROM `b_user` `main_user`
Helper has the most popular SQL expressions:
- count
- countDistinct
- sum
- min
- avg
- max
- length
- lower
- upper
- concat
Compatibility with getList
If you use getList instead of Query call chain, inserts filter instead of array:
\Bitrix\Main\UserTable::getList([
'filter' => ['=ID' => 1]
]);
\Bitrix\Main\UserTable::getList([
'filter' => Query::filter()
->where('ID', 1)
]);
// WHERE `main_user`.`ID` = 1
JOIN conditions
Descriptions of references is provided in the following format:
new Entity\ReferenceField('GROUP', GroupTable::class,
Join::on('this.GROUP_ID', 'ref.ID')
)
The method `on` is a brief and more semantically proper record Query::filter()
with preset condition per columns. Returns filter instance and can build any conditions for JOIN:
new Entity\ReferenceField('GROUP', GroupTable::class,
Join::on('this.GROUP_ID', 'ref.ID')
->where('ref.TYPE', 'admin')
->whereIn('ref.OPTION', [
new Column('this.OPTION1'),
new Column('this.OPTION2'),
new Column('this.OPTION3')
]
)
Anywhere with indicated field names its implied that you can specify any chain of branching:
->whereColumn('this.AUTHOR.UserGroup:USER.GROUP.OWNER.ID', 'ref.ID');
Array format
There is an existing method for conversion from array to object \Bitrix\Main\ORM\Query\Filter\ConditionTree::createFromArray
to use filter as an array. Array format looks as follows:
$filter = [
['FIELD', '>', 2],
[
'logic' => 'or',
['FIELD', '<', 8],
['SOME', 9]
],
['FIELD', 'in', [5, 7, 11]],
['FIELD', '=', ['column' => 'FIELD2']],
['FIELD', 'in', [
['column' => 'FIELD1'],
['value' => 'FIELD2'],
'FIELD3']
],
[
'negative' => true,
['FIELD', '>', 19]
],
];
With standard comparison, pass the value either directly:
['FIELD', '>', 2]
or as an array with key value
:
['FIELD', '>', ['value' => 2]]
Use the array with key column
in a comparison with column:
['FIELD1', '>', ['column' => 'FIELD2']]
Nested filter are passed as similar nested arrays. Use self-title keys for replacement of object methods for negative()
and to update the logic()
:
$filter = [
['FIELD', '>', 2],
[
'logic' => 'or',
['FIELD', '<', 8],
['SOME', 9]
],
[
'negative' => true,
['FIELD', '>', 19]
]
]
the rest of methods where*
are replaced by corresponding comparison operators in
, between
, like
and etc.
['FIELD', 'in', [5, 7, 11]]
Attention: exercise close caution when using arrays. Do not insert unchecked data, passed by user, as a filter due to the possible dangerous conditions for data deployment in the database. Please, verify all input conditions using the field whitelist.
Entity Relations (legacy variant)
Create, update, delete, and select data makes quite sizeable functionality sufficient for the organization of work with unrelated data. However, web projects often imply relations among entities. That is why various ways of relating the entities are provided for:
1:1 Relations
The simplest type of relation is when an entity element refers to an element of another or the same entity. The examples described a deal with a book catalog, but until now book entries contained no indication of their authors. Let us implement that. First, let us describe the entity The author of the book:
<?php
namespace SomePartner\MyBooksCatalog;
use Bitrix\Main\Entity;
class AuthorTable extends Entity\DataManager
{
public static function getTableName()
{
return 'my_book_author';
}
public static function getMap()
{
return array(
new Entity\IntegerField('ID', array(
'primary' => true,
'autocomplete' => true
)),
new Entity\StringField('NAME'),
new Entity\StringField('LAST_NAME')
);
}
}
This example is based on the assumption that a book has only one author which is a person with a first name and a last name, and an entry about such author is contained in the entity Author. At the same time, one author may have many books. Thus, we obtain the relation 1 author : N books.
This relation can be described as follows:
class BookTable extends Entity\DataManager
{
...
public static function getMap()
{
return array(
...
new Entity\IntegerField('AUTHOR_ID'),
new Entity\ReferenceField(
'AUTHOR',
'SomePartner\MyBooksCatalog\Author',
array('=this.AUTHOR_ID' => 'ref.ID')
)
);
}
...
}
First, we should add a numeric field AUTHOR_ID where the author’s ID will be stored. Based on this field, the relation between entities will be configured through a new type of field – ReferenceField. This is a virtual field that has no actual reflection in the database:
new Entity\ReferenceField(
'AUTHOR',
'SomePartner\MyBooksCatalog\Author',
array('=this.AUTHOR_ID' => 'ref.ID')
array('join_type' => 'LEFT')
)
// LEFT JOIN my_book_author ON my_book.AUTHOR_ID = my_book_author.ID
Parameters | Description |
First | a field name is set up |
Second | name of a partner entity with which the relation is being established |
Third | describes which fields are used to connect the entities, and is set up in a format similar to the filter for the section select in getList. Keys and values are field names with prefixes:
- this. – field of the current entity,
- ref. – field of the partner entity.
|
Fourth, additional | table connection type join_type can be specified – LEFT (by default), RIGHT, or INNER. |
|
Now the described relation can be used during data sampling:
BookTable::getList(array(
'select' => array('TITLE', 'AUTHOR.NAME', 'AUTHOR.LAST_NAME')
));
SELECT
`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
`somepartner_mybookscatalog_author`.`NAME` AS `SOMEPARTNER_MYBOOKSCATALOG_AUTHOR_NAME`,
`somepartner_mybookscatalog_author`.`LAST_NAME` AS `SOMEPARTNER_MYBOOKSCATALOG_AUTHOR_LAST_NAME`
FROM `my_book`
LEFT JOIN `my_book_author` `somepartner_mybookscatalog_author` ON `somepartner_mybookscatalog_book`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`
The switch to the Author entity is carried out by the entry AUTHOR: reference field name is specified, and the context switches to this entity after the dot. After that, the field name from this entity, including the Reference, can be specified, thus going even further and generating a new table connection:
'select' => array('AUTHOR.CITY.COUNTRY.NAME')
This could be a query to select a book author’s residence country if there was the structure Countries -> Cities -> Book authors living in the cities..
In order to make sure that the field names of different entities do not overlap, the system generates unique aliases for the entities connected. Sometimes they are not very readable. In these cases, we can use the reassignment of aliases you already know:
BookTable::getList(array(
'select' => array(
'TITLE',
'AUTHOR_NAME' => 'AUTHOR.NAME',
'AUTHOR_LAST_NAME' => 'AUTHOR.LAST_NAME'
)
));
SELECT
`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
`somepartner_mybookscatalog_author`.`NAME` AS `AUTHOR_NAME`,
`somepartner_mybookscatalog_author`.`LAST_NAME` AS `AUTHOR_LAST_NAME`
FROM ...
Similarly to the source entity, the symbol * can be used to select all the scalar fields of an entity. Short aliases can also be used:
BookTable::getList(array(
'select' => array(
'TITLE',
'AR_' => 'AUTHOR.*'
)
));
SELECT
`somepartner_mybookscatalog_author`.`ID` AS `AR_ID`,
`somepartner_mybookscatalog_author`.`NAME` AS `AR_NAME`,
`somepartner_mybookscatalog_author`.`LAST_NAME` AS `AR_LAST_NAME`
FROM `my_book` `somepartner_mybookscatalog_book`
LEFT JOIN `my_book_author` `somepartner_mybookscatalog_author` ON `somepartner_mybookscatalog_book`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`
As mentioned above, the conditions for the entity relation are described similarly to a filter. This means that tables can be connected by several fields, and SqlExpression can also be used:
$author_type = 5;
new Entity\ReferenceField(
'AUTHOR',
'SomePartner\MyBooksCatalog\Author',
array(
'=this.AUTHOR_ID' => 'ref.ID',
'=ref.TYPE' => new DB\SqlExpression('?i', $author_type)
)
)
// LEFT JOIN my_book_author ON my_book.AUTHOR_ID = my_book_author.ID AND my_book_author.TYPE = 5
The field ReferenceField, like other fields, can be described during data selection in the ‘runtime’ section and be used for the connection of other entities with which the relations were not initially described.
If a field of an adjacent entity is used frequently, ExpressionField can be used, and the remote field can be determined as local.
new Entity\ExpressionField('AUTHOR_NAME', '%', 'AUTHOR.NAME')
In this example, the difference is not evident, but if you have a longer chain of junctions instead of AUTHOR.NAME, the use of one short name may come in handy.
Relation 1:N, or back Reference
The ReferenceField concept means that this field must be located in the entity N of the relation 1:N. Thus, the Reference must indicate just one entry: in the example above, it is assumed that a book may have only 1 author, and thus the ReferenceField in the Book entity indicates one entry of the Author entity.
It is easy to select a book author because the Book entity has an indication of the relation with the author entity. But how can we select all of the books of an author if the Author entity contains no explicit indication to the Books?
The point is that the Reference described in the book entity is sufficient for two-way selection; we only have to use a special syntax:
\SomePartner\MyBooksCatalog\AuthorTable::getList(array(
'select' => array(
'NAME',
'LAST_NAME',
'BOOK_TITLE' => '\SomePartner\MyBooksCatalog\BookTable:AUTHOR.TITLE'
)
));
SELECT
`somepartner_mybookscatalog_author`.`NAME` AS `NAME`,
`somepartner_mybookscatalog_author`.`LAST_NAME` AS `LAST_NAME`,
`somepartner_mybookscatalog_author_book_author`.`TITLE` AS `BOOK_TITLE`
FROM `my_book_author` `somepartner_mybookscatalog_author`
LEFT JOIN `my_book` `somepartner_mybookscatalog_author_book_author` ON `somepartner_mybookscatalog_author_book_author`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`
Instead of the Reference name, we have to indicate the Name of an entity which has a Reference to the current entity:Name of the reference to the current entity. Following such construction, the context switches to the Book entity, and the TITLE field and other fields can be selected in it.
Relations M:N
Any book can be characterized from the point of view of genre/category, whether business or fiction literature, history or training books, thrillers, comedies, dramas, books on marketing, development, sales, etc. For simplicity sake, let us call all of this as tags, and assign several tags to each book.
Let us describe the entity of tags:
<?php
namespace SomePartner\MyBooksCatalog;
use Bitrix\Main\Entity;
class TagTable extends Entity\DataManager
{
public static function getTableName()
{
return 'my_book_tag';
}
public static function getMap()
{
return array(
new Entity\IntegerField('ID', array(
'primary' => true,
'autocomplete' => true
)),
new Entity\StringField('NAME')
);
}
}
In order to connect this entity with the Books using the principle N:M (one book may have several tags, one tag may be connected with several books), it is necessary to create in the database an interim entity with a table to store data about the connections of books with tags.
< true
)),
new Entity\ReferenceField(
'BOOK',
'SomePartner\MyBooksCatalog\Book',
array('=this.BOOK_ID' => 'ref.ID')
),
new Entity\IntegerField('TAG_ID', array(
'primary' => true
)),
new Entity\ReferenceField(
'TAG',
'SomePartner\MyBooksCatalog\Tag',
array('=this.TAG_ID' => 'ref.ID')
)
);
}
}
In this case, the entity is just required to store the connection Book ID – tag ID, and relevant ReferenceFields will help to describe this connection in software.
The interim entity itself may be of little interest. Standard tasks in this case involve obtaining a list of tags for a book or a list of books according to a tag. These tasks are solved using the methods of work with the Reference described above:
// tags for the book with ID = 5
\SomePartner\MyBooksCatalog\BookTable::getList(array(
'filter' => array('=ID' => 5),
'select' => array(
'ID',
'TITLE',
'TAG_NAME' => 'SomePartner\MyBooksCatalog\BookTag:BOOK.TAG.NAME'
)
));
SELECT
`somepartner_mybookscatalog_book`.`ID` AS `ID`,
`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
`somepartner_mybookscatalog_book_book_tag_book_tag`.`NAME` AS `TAG_NAME`
FROM `my_book` `somepartner_mybookscatalog_book`
LEFT JOIN `my_book_to_tag` `somepartner_mybookscatalog_book_book_tag_book` ON `somepartner_mybookscatalog_book_book_tag_book`.`BOOK_ID` = `somepartner_mybookscatalog_book`.`ID`
LEFT JOIN `my_book_tag` `somepartner_mybookscatalog_book_book_tag_book_tag` ON `somepartner_mybookscatalog_book_book_tag_book`.`TAG_ID` = `somepartner_mybookscatalog_book_book_tag_book_tag`.`ID`
WHERE `somepartner_mybookscatalog_book`.`ID` = 5
The entry SomePartner\MyBooksCatalog\BookTag:BOOK.TAG.NAME may seem complicated, but it is actually pretty simple when considered by parts:
Parameters | Description |
BookTable::getList | source entity – BookTable |
SomePartner\MyBooksCatalog\BookTag:BOOK | switch to the entity BookTag through its reference BOOK, current entity – BookTag |
TAG | switch following the reference TAG from BookTag, current entity – Tag |
NAME | a field from the current entity Tag |
|
The call chain will be very similar in order to obtain books with a specific tag:
// books for the tag with ID = 11
\SomePartner\MyBooksCatalog\TagTable::getList(array(
'filter' => array('=ID' => 11),
'select' => array(
'ID',
'NAME',
'BOOK_TITLE' => 'SomePartner\MyBooksCatalog\BookTag:TAG.BOOK.TITLE'
)
));
Thus, using two types of switch between entities, REFERENCE and Entity:REFERENCE, the necessary related information can be easily accessed.
Using ORM or why fields from SELECT and ORDER BY are automatically included into GROUP BY
Very often, developers have a question when using ORM and during data retrieval specifically: "Why did GROUP BY automatically include some fields? I haven't indicated this directly when calling getList/Query". This article overviews such phenomenon and why it's designed this way.
In the past, Bitrix24 products have supported three DMBS: MySQL, Oracle, SQL Server. And if MySQL is clearly familiar to and used by the general developer community, the Oracle and SQL Server is the choice of more serious and large-scale projects. Enterprise segment mandates DMBS to satisfy higher-level requirements, including compliance to standards.
Below is the example of how the data retrieval operates in the abovelisted systems:
SELECT t.* FROM a_city t
This is a standard fetching of data from the table: you have 7 populated cities, located in the numbered regional areas.
Set conditions for retrieval - only No.1 and No.1 regions:
SELECT NAME, REGION FROM a_city t WHERE t.REGION IN ('1','2')
As you can see, data format didn't change, only the filtering is performed.
Now group the retrieved selection - count how many cities are located in each region:
SELECT COUNT(*), t.REGION FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION
Previous selection was "collapsed" by unique REGION
values and each of such value the number of "collapsed records" was calculated:
Group the selection SELECT NAME, REGION FROM a_city t WHERE t.REGION IN ('1','2')
by region.
Up to this moment this use case is fairly straightforward and simple.
Now, let's overview quite widespread case: developer decides that he some cities have insufficient number of inhabitants in the grouped selection:
SELECT COUNT(*), t.REGION, POPULATION FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION
Great, MySQL has successfully processed the query and returned a number of inhabitants (but in fact, it didn't), developer is satisfied (incorrectly). This issue, it seems, is resolved, but somehow the same query in Oracle returns an error
ORA-00979: not a GROUP BY expression
а SQL Server responds
Column 'a_city.POPULATION' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Let's take a look on what numbers have been returned by MySQL instead of an error:
SELECT NAME, REGION, POPULATION FROM a_city t WHERE t.REGION IN ('1','2')
This is how the selection looked like before grouping had started. It seems, MySQL just took the first available values for each region. What does it give to the developer? Absolutely nothing: these numbers are meaningless, their values cannot be predicted.
The solution is to select the summary number of inhabitants for a region:
SELECT COUNT(*), t.REGION, SUM(POPULATION) FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION
or medium number of inhabitants in cities for each region:
SELECT COUNT(*), t.REGION, ROUND(AVG(POPULATION),0) FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION
In such case, all databases will successfully process the query, because now they recognize how to handle the column POPULATION
during "collapsing".
Important rule: when selection has an aggregation or a grouping (aggregation by unique value) for at least single column, the rest of selectable columns also must be aggregated or grouped.
But, let's return to the query builder in в Bitrix Framework: it monitors compliance to this rule and upon detecting non-aggregated fields, adds them to the section GROUP BY
.
The provided example with cities with unavailable direct aggregation, query will look as follows:
SELECT COUNT(*), t.REGION, t.POPULATION FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION, t.POPULATION
The result demonstrates, how many inhabitants do cities in the specific regions have. Only this way the DMBS recognizes the developer's input.
So, the result is as follows: you need to either indicate the aggregation or group by field, otherwise its value will be meaningless. For example, developer decides to add a string by ID and sees how the ID
field automatically goes to GROUP BY
and "breaks" the result:
SELECT COUNT(*), t.REGION, SUM(t.POPULATION) FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION, t.ID ORDER BY ID DESC
As you might think, if ID is not added to the grouping, the Oracle and SQL Server will refuse to execute the query again, referring to uncertainty in data aggregation. What is the reason this time?
The sorting in this case occurs already after grouping/aggregating the data. It's not important, if ID is included in the source table: after the grouping we get a new virtual table with aggregated data.
It means, you need to add the ID field into the intermediary grouped result: only then the sorting by this field will be possible. And at this point, we are circling back to the previous rule and clarify it:
Important: When the fetched data has an aggregated or grouped data (aggregated by unique value) for at least a single column, then al the rest of columns from SELECT and ORDER BY must be aggregated or grouped in the same manner.
Adhering to this rule will help you understand which calculations are done by the database for you. If you disregard it, you will be confused by the retrieved results (in case of MySQL): the data will seem truthful, but incorrect.
Note: this rule doesn't apply to WHERE: this filtration is performed specifically BEFORE data is grouped, with desirable original column values. Filtering by aggregated values is performed in the section HAVING and if it contains column without aggregation - this column values must be pre-grouped before getting any meaningful data in the result. Now, the ORM query builder will ensure the distribution in filter WHERE and HAVING. You don't have to pay special attention to this aspect, as well as to automatic grouping.
Conclusion
If an automatic adding of fields to GROUP BY
in a specific query is an unpleasant surprise for you, then:
- You have added field to the fetched selection by habit or accidentally; in reality you don't need its value
или
- You have forgotten to indicate the aggregation (MAX, MIN, SUM, AVG, etc.)
MySQL had executed the query, without issuing an error, only due to its tolerance to inaccuracies (by default). This is a disservice, because it has returned a false and meaningless result that looks valid at the first glance.
ORM in Bitrix Framework corrects such inaccuracies independently. In case of direct queries, use the setting ONLY_FULL_GROUP_BY to disable such behaviour and force MySQL to adhere to standard and common sense.
Page Navigation
How to implement page navigation in core D7 for ORM selection (Available from version 16.0.0).
Basics
Page navigation is an auxiliary object for UI. For legacy kernel/core, page navigation was implemented in the query result object. Handling of limits required calling a special function CDBResult::NavQuery() to pass the query text. However, page navigation in D7 is just an auxiliary object for UI. Query is deemed as primary and page navigation just helps to substitute correct limit and offset.
Limit requirement in queries. It's incorrect to relegate php with all potential fetching without limitations. In case the retrieval is executed without limits, you have an option to use new navigation. This means that developer does this at his/her own risk, without vendor recommendation. And you'll need to solve issues arising from using different databases and other tasks.
For page navigation, you have to know the number of records. Just as in the old kernel/core, you need to execute a separate query with COUNT
. It's more effective than retrieving a large chunk of data records. For small-scale fetching this issue is not as prevalent. However, if you are getting a voluminous batch of data indeed, current core page navigation will be able to handle it even without COUNT
.
Page navigation tools
- \Bitrix\Main\UI\PageNavigation class for standard navigation;
- \Bitrix\Main\UI\ReversePageNavigation class for reverse navigation;
- \Bitrix\Main\UI\AdminPageNavigation class for navigation in administration section;
- main.pagenavigation components with .default templates (based at the old component's round), admin (section), modern (for grids).
Page navigation supports both parameters in GET and SEF. Now you need to directly indicate a navigation identifier instead of global variable $NavNum
. The following URL are supported:
/page.php?nav-cars=page-5&nav-books=page-2&other=params
/page.php?nav-cars=page-5-size-20&nav-books=page-2
/page.php?nav-cars=page-all&nav-books=page-2
/dir/nav-cars/page-2/size-20/
/dir/nav-cars/page-all/?other=params
/dir/nav-cars/page-5/nav-books/page-2/size-10
where nav-cars
and nav-books
- identifiers for two different page navigations at a single page.
Page navigation in the administrative section
$nav = new \Bitrix\Main\UI\AdminPageNavigation("nav-culture");
$cultureList = CultureTable::getList(array(
'order' => array(strtoupper($by) => $order),
'count_total' => true,
'offset' => $nav->getOffset(),
'limit' => $nav->getLimit(),
));
$nav->setRecordCount($cultureList->getCount());
$adminList->setNavigation($nav, Loc::getMessage("PAGES"));
while($culture = $cultureList->fetch())
{
}
'count_total' => true
- new parameter that forces ORM to execute an individual query COUNT
, resulting in retrieved object and can be received via $cultureList->getCount()
. Without this parameter there's no point to execute getCount() and event counterproductive (you'll get a thrown exception).
$nav->getOffset()
returns first record position for the current navigation page.
$nav->getLimit()
returns number of records at the page or 0, if "all records" are selected.
$nav->setRecordCount()
sets number of records for navigation.
For convenience, administration section now has an added function $adminList->setNavigation()
which just connects the component main.pagenavigation with an admin template.
Direct page navigation in Public section
<?
$filter = array("=IBLOCK_ID"=>6);
$nav = new \Bitrix\Main\UI\PageNavigation("nav-more-news");
$nav->allowAllRecords(true)
->setPageSize(5)
->initFromUri();
$newsList = \Bitrix\Iblock\ElementTable::getList(
array(
"filter" => $filter,
"count_total" => true,
"offset" => $nav->getOffset(),
"limit" => $nav->getLimit(),
)
);
$nav->setRecordCount($newsList->getCount());
while($news = $newsList->fetch())
{
}
?>
<?
$APPLICATION->IncludeComponent(
"bitrix:main.pagenavigation",
"",
array(
"NAV_OBJECT" => $nav,
"SEF_MODE" => "Y",
),
false
);
?>
Now navigation initialization is added:
$nav->allowAllRecords(true) ->setPageSize(5) ->initFromUri();
Public section now has more settings, such as "allow all records", and it's all needed before initializing the object (the admin section was already re-defined by descendant class). Additionally, getting current page from URL is optional, because it can be sourced and set from anywhere.
The component has one important parameter: "SEF_MODE" => "Y"
used to generate SEF for page navigation.
Reverse page navigation in Public section
<?
$filter = array("=IBLOCK_ID"=>6);
$cnt = \Bitrix\Iblock\ElementTable::getCount($filter);
$nav = new \Bitrix\Main\UI\ReversePageNavigation("nav-news", $cnt);
$nav->allowAllRecords(true)
->setPageSize(5)
->initFromUri();
$newsList = \Bitrix\Iblock\ElementTable::getList(
array(
"filter" => $filter,
"offset" => $nav->getOffset(),
"limit" => $nav->getLimit(),
)
);
while($news = $newsList->fetch())
{
}
?>
<?
$APPLICATION->IncludeComponent(
"bitrix:main.pagenavigation",
"",
array(
"NAV_OBJECT" => $nav,
"SEF_MODE" => "Y",
),
false
);
?>
The descendant class ReversePageNavigation is constructed with mandatory received number of records already in the constructor. This is done because the reverse navigation cannot mathematically operate without the number of records. The method getCount() was updated in ORM for this purpose: now it receives filter parameter on input.
Page navigation in UI grid
<?
$filter = array("=IBLOCK_ID"=>6);
$nav = new \Bitrix\Main\UI\PageNavigation("nav-grid-news");
$nav->allowAllRecords(true)
->setPageSize(5)
->initFromUri();
$newsList = \Bitrix\Iblock\ElementTable::getList(
array(
"filter" => $filter,
"count_total" => true,
"offset" => $nav->getOffset(),
"limit" => $nav->getLimit(),
)
);
$rows = array();
while($news = $newsList->fetch())
{
$cols = array(
"ID" => $news["ID"],
"NAME" => $news["NAME"],
);
$rows[] = array(
"columns"=>$cols,
);
}
$nav->setRecordCount($newsList->getCount());
?>
<?
$APPLICATION->IncludeComponent(
"bitrix:main.interface.grid",
"",
array(
"GRID_ID"=>"news_grid",
"HEADERS"=>array(
array("id"=>"ID", "name"=>"ID", "default"=>true),
array("id"=>"NAME", "name"=>"Name", "default"=>true),
),
"ROWS"=>$rows,
"FOOTER"=>array(array("title"=>"Total", "value"=>$newsList->getCount())),
"NAV_OBJECT"=>$nav,
"NAV_PARAMS"=>array(
"SEF_MODE" => "Y"
),
)
);
?>
"NAV_PARAMS"=>array( "SEF_MODE" => "Y" )
- new parameter that passes new parameter values to navigation component.
Page navigation without COUNT
Only direct navigation is possible. General principle: developer manually pinpoints a little more records back and forth to determine, if you can show "more" records at the page. This helps developer to define the limit shown at the page.
<?
$filter = array("=IBLOCK_ID"=>6);
$nav = new \Bitrix\Main\UI\PageNavigation("nav-less-news");
$nav->allowAllRecords(true)
->setPageSize(5)
->initFromUri();
$newsList = \Bitrix\Iblock\ElementTable::getList(
array(
"filter" => $filter,
"offset" => ($offset = $nav->getOffset()),
"limit" => (($limit = $nav->getLimit()) > 0? $limit + 1 : 0),
)
);
$n = 0;
while($news = $newsList->fetch())
{
$n++;
if($limit > 0 && $n > $limit)
{
break;
}
}
$nav->setRecordCount($offset + $n);
?>
<?
$APPLICATION->IncludeComponent(
"bitrix:main.pagenavigation",
"",
array(
"NAV_OBJECT" => $nav,
"SEF_MODE" => "Y",
"SHOW_COUNT" => "N",
),
false
);
?>
Attention: "already known" number of records is inserted:
$nav->setRecordCount($offset + $n);
This is done for navigation component to understand that pages are still available. However, for templates that show number of records, you need to pass "SHOW_COUNT" => "N"
to prevent attempts to display an incorrect number (this number will be correct only at the last page).
Attention! Navigation without COUNT
indicates a very significant amount of records. That's why it's not recommended to allow all records via the method $nav->allowAllRecords(true)
.
Please see the file with examples.
ORM integration in the information blocks
Starting from iblock module version 19.0.0 now supports ORM when handling iblock elements.
Backward compatibility
Legacy kernel iblock module events are not supported. Standard features of other modules, based on such event calls, are no longer supported as well.
Presently, functional blocks support is not implemented:
For API handling the elementы (Add, Edit, Delete)
- PREVIEW_PICTURE, DETAIL_PICTURE image resize;
- iblock faceted index update (if used);
- element seo-parameter update;
- tagged cache reset;
- access permission assignment;
- document processing support;
- drive space quote check for file properties;
- product availability with SKU recalculation;
- price recalculation for product sorting with SKU;
- indexing by search module;
- element operations logging.
- automatic external code generation when creating an element.
For API handling with sections (adding, editing, deleting).
- automatic recalculation for LEFT_MARGIN, RIGHT_MARGIN, GLOBAL_ACTIVE, DEPTH_LEVEL field value;
- PREVIEW_PICTURE, DETAIL_PICTURE image resize;
- iblock faceted index update (if used);
- updating seo parameters for a section and child entities (subsections and elements);
- tagged cache rest;
- assigning access permissions to section and child entities;
- drive space quote check for file fields;
- indexing by search module;
- sections property assignment;
- section operations logging.
Attention! Listed features must be implemented independently.
List of related links:
- [ds]REST ORM API for iblocks[/ds][di]
REST API for information blocks is available from the iblocks module version 20.5.0.
Integration with ORM is taken as the basis for accessing the iblock data via REST. Specifically, the main concept was adopted: a single iblock is an individual ORM entity and iblock element is a record (object) of entity.
Learn more...[/di]
Concept and architecture
Each iblock is an independent data type with its own set of properties. It's represented by a separate entity in the ORM:
Entity class name includes a new field value taken from API symbolic code iblock config. This code ensures the class unique status independently from ID and environment.
Important! You need to set API symbolic code (API_CODE field) for specific block to start using ORM. It's a string with a symbol from 1 to 50, starting from a letter and consisting of Latin characters and numerals.
Property contains not just scalar values, but also relations with individual mini-entities having two key fields: VALUE and DESCRIPTION. Singular properties are presented in the iblock element by the field Reference, multiple properties - OneToMany:
You can add extra fields to entities of some property types. For example, link to a bound iblock element:
More details on basic property types
|
For standard case, the property entity consist of two fields - VALUE (type depends on property) and, if defined in property settings, DESCRIPTION (StringField).
Entity | VALUE | DESCRIPTION | Additional field |
String | StringField | StringField | No additional fields. |
Integer | IntegerField | StringField | No additional fields. |
List | IntegerField | StringField | ITEM Reference (\Bitrix\Iblock\PropertyEnumerationTable) |
File | IntegerField | StringField | FILE Reference (\Bitrix\Main\FileTable) |
Binding to element | IntegerField | StringField | ELEMENT Reference (iblock element) |
Binding to section | IntegerField | StringField | SECTION Reference (\Bitrix\Iblock\SectionTable) |
|
You can find direction among a large number of properties using [ds]annotations mechanism[/ds][di]Majority of Object and Collection methods - are virtual, processed via magic__call. At the same time, they are designed for the intuitively clear and self-explanatory named methods and without autocomplete in IDE their value significantly decreases.
For the IDE to know about their existence and to help find direction in the large number of classes and methods, Bitrix24 have provided a special service file with annotations for all entities.
Learn more ...[/di]. All iblocks will be described as ORM entities when indexing the iblock module. You need to clearly designate iblock class to get hints in the code:
// connecting iblock module
\Bitrix\Main\Loader::includeModule('iblock');
// input data
$iblockId = 32;
$iblockElementId = 678;
// iblock object
$iblock = \Bitrix\Iblock\Iblock::wakeUp($iblockId);
// element object
/** @var \Bitrix\Iblock\Elements\EO_ElementLink $element */
$element = $iblock->getEntityDataClass()::getByPrimary($iblockElementId)
->fetchObject();
// getting SOME_STRING property
$element->getSomeString();
Class autoloading automatically triggers the call getEntityDataClass()
, i. e. you won't have to pre-compile iblock entity.
If you want to use IDE hints on iblock element type checking, you need to directly set its class by annotation type:
where Link in the class name - iblock API symbolic code.
Attention! Property's symbolic code shall not match with element field name in an iblock. Otherwise, you won't be able to work with property by name (symbolic code) in ORM.
Read and write
Getting property values requires only their names listed in query:
$elements = $iblock->getEntityDataClass()::getList([
'select' => ['ID', 'SOME_FIELD', 'ANOTHER_FIELD.ELEMENT']
])->fetchCollection();
foreach ($elements as $element)
{
echo $element->getSomeField()->getValue();
echo $element->getAnotherField()->getElement()->getTitle();
}
Standard ORM relations mechanics are supported.
When filtering, keep in mind the property structure. Property field is a link (Reference or OneToMany) to a complete entity, that's why the property name indicated in filter leads to nothing:
// incorrect example
$element = $iblock->getEntityDataClass()::query()
->where('SOME_FIELD', 'some value')
->fetchObject();
Property value is stored in the entity field, always named as VALUE. That's why it's correct to indicate it specifically in filter:
// correct example
$element = $iblock->getEntityDataClass()::query()
->where('SOME_FIELD.VALUE', 'some value')
->fetchObject();
You can use both constructor of corresponding class for creating new object,
$newElement = new \Bitrix\Iblock\Elements\EO_ElementLink;
as well as entity factory.
$newElement = $iblock->getEntityDataClass()::createObject();
Property values and property descriptions are edited directly via property object:
// setting string value
$element->getSomeString()->setValue('new value');
// setting description
$element->getSomeString()->setDescription('new descr');
// setting element binding
$element->getSomeElement()->setElement($anotherElement);
Additionally, you can set the value directly in the property field:
$element->setSomeString('new value');
You can also use pseudo-object for property value Bitrix\Iblock\ORM\PropertyValue
:
use Bitrix\Iblock\ORM\PropertyValue;
// value only
$value = new PropertyValue('new value');
// value and description
$value = new PropertyValue('new value', 'new descr');
// setting value/description
$element->setSomeString($value);
Setting values for multiple properties operates similarly with the only difference that it's performed for OneToMany and not for Reference:
use Bitrix\Iblock\ORM\PropertyValue;
foreach ($element->getOtherField() as $value)
{
$value->setValue('new value');
$value->setDescription('new descr');
}
$element->addToOtherField(new PropertyValue('new value'));
$element->addToOtherField(new PropertyValue('new value', 'new descr'));
Element object is saved in the same manner as any other ORM object:
$element->save();
Despite the property values being actually stored in different tables as relations with object, everything will be placed in correct locations upon saving.
You can Delete element via the 'delete' object-method:
$element->delete();
Property values are processed automatically when deleting as well as saving. Section associations are deleted as well.
Events and custom property types
Events
You can use standard ORM mechanisms to subscribe to iblock entity events:
use Bitrix\Main\ORM\Data\DataManager;
// iblock ID
$iblockId = 32;
// iblock object
$iblock = \Bitrix\Iblock\Iblock::wakeUp($iblockId);
// event manager
$em = \Bitrix\Main\ORM\EventManager::getInstance();
$em->registerEventHandler(
$iblock->getEntityDataClass(),
DataManager::EVENT_ON_BEFORE_ADD,
'mymodule',
'MyClass',
'method'
);
Attention! At this moment, events for legacy kernel iblocks are not supported.
Custom property types
You need to set an individual callback GetORMFields to add your own fields to entity when describing the property:
public static function GetUserTypeDescription()
{
return [
...
"GetORMFields" => array(__CLASS__, "GetORMFields"),
];
}
/**
* @param \Bitrix\Main\ORM\Entity $valueEntity
* @param \Bitrix\Iblock\Property $property
*/
public static function GetORMFields($valueEntity, $property)
{
$valueEntity->addField(
...
);
}
Inheritance
Iblock ORM API allows to [dw]inherit[/dw][di]
Option to inherit elements from ORM is available fromiblock module version 21.500.0.
[/di]entity for specific iblock, as well as to supplement or re-define its behaviour. You can inherit classes for the entity itself and its object. It's sufficient to indicate Table iblock class for inheriting an entity:
class MyExtTable extends Bitrix\Iblock\Elements\Element{API_CODE}Table
{
}
Parent class name uses API CODE from iblock settings. Object class description is specified by [ds]ORM general rules[/ds][di]
All entity objects are descendants for class Bitrix\Main\ORM\Objectify\EntityObject, with each entity having its own class for objects. By default, such class is created automatically, in passing.
Learn more...[/di], including configuring the class Table:
class MyExtTable extends Bitrix\Iblock\Elements\Element{API_CODE}Table
{
public static function getObjectClass()
{
return MyExt::class;
}
}
class MyExt extends EO_MyExt
{
}
Components
Components are the main tool for developers when it comes to working with the projects created with Bitrix Framework. Developers’ professionalism largely depends on their ability to use this tool.
Component - is a logically completed code intended for the retrieval of information from infoblocks and other sources and its conversion to HTML code for displaying as fragments of web pages. It consists of a component proper (controller) and a template (form). The component manipulates data using API of one or more modules. The component template displays data on the page.
Classical workflow diagram of a component:
Carrier Rider Mapper
Components implement the design pattern Carrier Rider Mapper to the full extent.
- Carrier. Carrier of any information that can be accessed by several clients simultaneously.
- Rider (Reader or Writer) - objects through which the Carrier provides acces to the information stored in it. The clients read and write information stored in the Carrier exclusively using only the objects of the Reader and Writer type. Thus, Reader and Writer are information access interfaces.
- Mapper (Scanneror Formatter) – objects that cover Reader or Writer, accordingly. Mappers are responsible for the conversion of data formats to the formats convenient for the clients.
Information flow from carrier to client (read): Carrier -> Reader -> Scanner -> Client.
Information flow from client to carrier (write): Carrier <- Writer <- Formatter <- Client.
Introducing mappers between Carrier-Rider and clients permits connecting one and the same Carrier-Rider with different types of clients by using appropriate (different) mappers.
The Use of Components
Components are used for the following purposes:
- Creating fully functionining sections on a website, e.g. news section, photo gallery, goods catalog, etc. These sections are created using composite components;
- Creating frequently used areas in the template or on website pages (e.g., authorization forms and subscription forms);
- Representing dynamically updated information (e.g., news feed and an occasional photo);
- Performing any other operations with data.
When placing a component on a page the user sets parameters that are used to retrieve the page program module on this particular page. The set of parameters (including their types) is to be established in the component parameter file as a special hash array.
Several components may be placed on a website page. One component may be in charge of displaying the text of the article, the second may display banners, the third may display news related to the subject of this article, etc. One and the same component may be used on different pages of the website and on any website within the same product installation.
Components may be simple and composite.
Main Specifics of Component Technique
- Logic and visual form are separated in components. Logic is the component itself, and form is the component display template. Several forms may be created for the same logic, including forms which depend on the template of the current website. Visual form (display template) may be written using any template language that can be connected from PHP. For example, templates may be written using PHP, Smarty, XSL etc.
- There is no need to change the component logic in order to change the specifics of its display. That is why it is quite easy to manage the external appearance of the information displayed by the component. Display template is by far simpler than the component as a whole.
- Components are stored together in one folder. It ensures the integrity and transparency of a website structure. The folder is available for queries. This means the component and its templates can easily connect their additional resources.
Note: The evolution of the development of Bitrix Framework using the components of regular standards has led to the components that are currently used in 2.0 standard. The regular standard (version 1.0 components) is not supported any longer. However, sometimes outdated components can be found in the projects operating on older versions of Bitrix Site Manager.
Simple and composite components
Components are subdivided into simple (single page) and composite (compound, multi-page). A simple component displays on one physical page available under a specific URL. A composite component replaces a set of simple components. For example, a news section can be implemented using several simple components located on a separate physical page each, or using one composite component located on one physical page.
In terms of the structure and means of connection, simple and composite components are very similar. However, in terms of functioning, they are very different.
Simple components
Simple (single page) components create an area on a page surface. They come at hand when you need to render data from different modules on a single page (for example: blogs and information blocks) or data from different information blocks (news and commercial catalog). Evidently, simple components are not convenient to create a whole news section or a catalog because this requires the creation of a considerable number of pages and controlling their linkage.
Composite components
Composite (compound, multi-page) components create the site sections. For example, the commercial catalog component creates the whole catalog section including the catalogs page, the groups page and the product pages. Essentially, a composite component is a set of pages from the visitor's point of view, but represented by a single page physically. The foundation of composite components are simple components and the use of the single-page logic.
The advantage of composite components is the automatic preparation of parameters of the underlying simple components: you do not have to care about the interrelationships of internal parameters.
Composite components resolve the following problems:
- Avoid the creation of numerous pages to add all required sub-components to them.
There is no need to configure common parameters of each individual component.
- Avoid the need to establish composite associations between sub-components.
For example, you do not have to configure the link creation rules etc.
- Components (even those driven by custom view templates) can be enriched with new pages.
For example, if you add page (sub-component) showing 10 most rated forum visitors, this page becomes momentarily available in the public section.
- The component view template can be changed at one stroke instead of configuring each of the underlying sub-components.
A typical composite component workflow is as follows:
- the component takes user actions on input and defines the exact page to show to a visitor and attaches an appropriate template to the page;
- the page template attaches simple components and configures their parameters as required;
- the simple components perform the requested operations: request data from the system kernel, format and display data to the visitor; show various controls (links, forms, buttons etc.);
- the visitor sends a new request to the composite component using the controls.
Example
Let us consider the difference between composite and simple components using the creation of a news section as an example.
A news section may be organized, for example, by placing a news list component on the page index.php and a detailed news component on the page news.php. In this case, the news list component must have entry parameters set in such a manner so that it could generate links to the page containing the detailed news (with news code). The detailed news component must have entry parameters set in such a manner so that it could generate a link to the page containing the news list.
ЧTo set a link to the page with detailed news the path to this page must be specified along with the name of the parameter in which a news code will be submitted for display. The same parameter name must also be set in the entry parameters of the detailed news component to indicate where it shall retrieve the code of the news to be displayed. Even in this oversimplified case, the settings are not so simple. What if it is a set of tens of components of a forum?
The use of a composite news component is a better option for website developers. For example, this component can be simply installed on the page index.php. The composite component itself will take care of link and parameter alignment. No further actions are required on the part of the website developer.
Template pages of the composite component will contain a connection of the relevant regular components with the correct setting of their entry parameters. Regular components will perform their regular functions: they do no care who retrieves them or why. For regular components, only the correct setting of their entry paramenters is important.
MVC pattern is implemented as follows:
- News composite component (controller) receives an HTTP query (user’s actions);
- News composite component (controller) checks if a news code is set through the HTTP query and connects from its template the page containing the news list or the page containing the detailed news (view);
- The connected page, in its turn, connects the relevant regular component at the same time installing entry paramenters accordingly;
- Regular component performs its work: requests data from the kernel (model), formats them, and displays to the visitor, and also displays control elements (links, forms, buttons, etc.);
- The user sends a new HTTP query to the news composite component (controller) using control elements;
- The procedure is repeated as necessary.
Location of a Component in the System and Its Connection
All components are stored in the /bitrix/components/ folder. The system components are in the /bitrix/components/bitrix/ folder. The content of this folder is updated by the update system and must not be modified by users.
Note! Modifying the system component folder /bitrix/components/bitrix/ can have unpredictable effects.
Users can store their own components in any subfolder of /bitrix/components/, or in the /bitrix/components/ root.
Naming convention
The name of a subfolder in /bitrix/components/ is the component namespace. For example, all system components belong to the bitrix namespace. When creating a custom component, you should create a namespace and put custom components there.
For example, a system component news exists located in the bitrix namespace. If you need a custom news component implementing functions unavailable in the standard system component, you should create a namespace (subfolder) in /bitrix/components/ (for example, demo) and put the custom component news there. The two components news will be available in different namespaces: bitrix:news and demo:news; both of them will be shown in the component tree in the visual editor.
|
|
|
Names of the components are as follows identifier1.identifier2…. For example, catalog, catalog.list, catalog.section.element, etc. The names should be built hierarchically starting from the general term and ending with specific purpose of the component. For example,the component showing the list of goods of this group may have the name catalog.section.elements. The full name of the component is the name of the component indicating the namespace. The full name looks like name_space:component_name. For example, bitrix:catalog.list. If the component is located outside the namespace, the namespace will not be specified. For example, catalog.section.
Component Connection
In the most general terms, the component connects as follows:
<?$APPLICATION->IncludeComponent(
componentName, // component name
componentTemplate, // component template, empty line if default template is used
arParams=array(), // parameters
parentComponent=null,
arFunctionParams=array()
);?>
The following preset variables are available inside the component (file component.php):
$componentName
– full name of a component (e.g.: bitrix:news.list).
$componentTemplate
– the template with which the component is retrieved.
$arParams
– entry parameters of the component (i.e. parameters with which the component is retrieved). Parameters are also available by their names.
$componentPath
– a path to the component from the website root (e.g.: /bitrix/components/bitrix/news.list
).
$parentComponentName
– name of the parent component (empty if there is no parent).
$parentComponentPath
–path to the parent component from the website root (empty if there is no parent).
$parentComponentTemplate
– template of the parent component (empty if there is no parent).
$arResult
— result, read/change. Affects the member of the component class with the same name.
$this
— naturally a link to the current component retrieved (CBitrixComponent class object), all class methods can be used.
In addition, the variables $APPLICATION, $USER, $DB
are named as global in the component.
Component Structure
A component stores everything it needs for operation in its folder. That is why they can be easily moved from one project to another.
Important: Component files cannot be used independently. A component is a single entity, and it is indivisible.
The component folder may contain the following subfolders and files:
- The help subfolder (outdated from version 11.0) containing component help files. This folder contains subfolders named by the language abbreviation.
Each language folder must have a file index.php, which is the main help file for the corresponding language.
For example, the path name of the english help file can be /help/en/index.php.
The help files should describe the structure of an array in which the component returns data to the template.
The subfolder help is optional.
- subfolder install that contains installation and uninstallation scripts. The installation script name is install.php, and the uninstallation script is uninstall.php. The subfolder install is optional.
- subfolder lang containing component messages. From version 11.0 and later it may also contain help folder files.
- subfolder templates in which the component view templates are stored. The subfolder templates is optional if the component has no view templates.
- The file component.php containing logics (code) of the component. This file is responsible for generating an array $arResult from the obtained parameters ($arParams) that will subsequently get to the component template. This file must always be located in the component folder.
- The file .description.php containing the component name, description, and its position in the deduction tree (for WYSIWYG editor). This file must always be located in the component folder. Its absence will not affect the work of the component, but will make it impossible to place the component through a visual editor.
- file .parameters.php, which contains the description of the component input parameters for the visual editor. This file must exist if the component uses input parameters.
- The file .class.php to support OOP components.
- any other folders and files containing the resources required by the component, for example: the folder images.
General Structure of the Component
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
<?
//Check and initialize the incoming parameters of a component
if(component retrieval is in a valid cache)
{
//Retrieval of the data from the cache
}
else
{
//Data request and generation of $arResult array according to
//the structure described in the component help file
$this->IncludeComponentTemplate();
//Cached retrieval
}
?>
Localization Folders
Components and their templates support the option of displaying a user’s messages in multiple languages. Therefore, if a component displays the contents of an infoblock, for example, these contents may be preceded by a string constant. E.g.: “Here you can see the contents of the infoblock.” If the user chooses to go to, for example, the English version of the website, this constant may be automatically displayed in English.
In order to implement this function when displaying string constants, a special function shall be retrieved instead of the constant itself and the identifier of this constant shall be allocated to the special function.
Example:
In the file
/template.php
without localization:
<?echo “Commercial catalog”?>
In the file
/template.php
with localization:
<?echo GetMessage(“CATALOG”)?>
In this case we introduce the following in the file
/lang/en/template.php
:
<?$MESS[“CATALOG”] = “Commercial catalog”;?>
For each language version, a folder with the name of the language shall be created in the lang folder (e.g., de, en, etc.) to host the language message files. A language message file for a component file should have the same name as the file itself. These files contain arrays with constant identifiers as keys and the constants themselves translated into a relevant language as values.
The files should be located according to the same hierarchy with respect to the /lang/anguage_code/
folder where the file is located with respect to a component folder. For example, a language file with English phrases for the /install/uninstall.php
file should be located on the path /lang/en/install/uninstall.php
. There may be no lang subfolder at all if the component has no language-dependent phrases.
Localization folders are created separately for components and for each template.
How to Replace Standard Texts in the Interface.
Copy the component template to be customized (if it is not customized yet). And then either:
- Using the module Localization: Settings > Localization.
- Or manually edit language files
/bitrix/templates/site_template_name/components/component_name/templates/template_name/lang/en/template.php
.
Connection of a Component Language File (Translation File)
Language files in a component and all its standard files (component.php, .description.php, .parameters.php) are connected automatically. In other component files language files can be connected using the following command:
$this->IncludeComponentLang($relativePath = "", $lang = False)
where:
$relativePath - is a path to the file from the component folder,
$lang - is the language. If False is submitted, the current language is used.
Example: $this->IncludeComponentLang("myfile.php");
Prompts to Components
Bitrix Framework permits creating prompts to component parameters:
There are two ways to add prompts. The first one you can see in the projects created on the versions earlier than 11.0, and the second one – in subsequent versions. In both cases a file named .tooltips.php must be created.
Before version 11.0
In this case, the file shall be created in the help folder located in the root of the component. In the file, the array $arTooltips shall be created where the component parameters are the keys, and GetMessage() calls are values. For example:
$arTooltips = array(
'IBLOCK_TYPE' => GetMessage('VWS_COMP_DVL_TIP_IBLOCK_TYPE'),
'IBLOCK_ID' => GetMessage('VWS_COMP_DVL_TIP_IBLOCK_ID'),
'SORT_BY1' => GetMessage('VWS_COMP_DVL_TIP_SORT_BY1'),
'SORT_ORDER1' => GetMessage('VWS_COMP_DVL_TIP_SORT_ORDER1'),
);
Language files for the file .tooltips.php are located, accordingly, in the folder (from the root folder of the component) /lang/language_ID/help/.tooltips.php
.
From version 11.0 on
This time, the file shall be created in the language folder named lang. In the file, the array $MESS shall be created, where the component parameters with _TIP suffix are the keys, and the prompts are values. For example:
$MESS["IBLOCK_TYPE_TIP"] = "Select one of the existing information block types in the list";
$MESS["IBLOCK_ID_TIP"] = "Select an information block of the selected type";
$MESS["SORT_BY1_TIP"] = "Defines the sorting field";
$MESS["SORT_ORDER1_TIP"] = "Defines the sorting order";
In this version, there is no need to create a separate lang file with prompts. It is sufficient to save them in the language file .parameters.php (both for the component and for the component template). The prompt format remains the same.
In both case
The prompts for parameters of the component IBLOCK_TYPE, IBLOCK_ID, SORT_BY1 and SORT_ORDER1.
A number of standard parameters (CACHE_TIME, AJAX_MODE, and others) have several prompts. For CACHE_TIME:
- CACHE_TIME - caching time;
- CACHE_TYPE - caching type.
For pager:
- DISPLAY_TOP_PAGER - show pager above the list;
- DISPLAY_BOTTOM_PAGER - show pager below the list;
- PAGER_TITLE - pager element title;
- PAGER_SHOW_ALWAYS - always show pager;
- PAGER_TEMPLATE - pager template name;
- PAGER_DESC_NUMBERING - reverse addressing;
- PAGER_DESC_NUMBERING_CACHE_TIME - caching time of reverse addressing.
Attention! Do not create prompts in advance (creating them but leaving empty): component settings will not be displayed.
Composite Component Structure
Composite component serves to organize a website section as a whole (forum, catalog, etc.). It connects simple components to the display data. It actually operates as a controller (manager) of simple components. Composite component determines a page to be displayed to the visitor based on HTTP and connects the template of such a page.
An example of a composite component:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?><?
$arDefaultUrlTemplates404 = array(
"list" => "index.php",
"element" => "#ELEMENT_ID#.php"
);
$arDefaultVariableAliases404 = array();
$arDefaultVariableAliases = array();
$arComponentVariables = array("IBLOCK_ID", "ELEMENT_ID");
$SEF_FOLDER = "";
$arUrlTemplates = array();
if ($arParams["SEF_MODE"] == "Y")
{
$arVariables = array();
$arUrlTemplates =
CComponentEngine::MakeComponentUrlTemplates($arDefaultUrlTemplates404,
$arParams["SEF_URL_TEMPLATES"]);
$arVariableAliases =
CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases404,
$arParams["VARIABLE_ALIASES"]);
$componentPage = CComponentEngine::ParseComponentPath(
$arParams["SEF_FOLDER"],
$arUrlTemplates,
$arVariables
);
if (StrLen($componentPage) <= 0)
$componentPage = "list";
CComponentEngine::InitComponentVariables($componentPage,
$arComponentVariables,
$arVariableAliases,
$arVariables);
$SEF_FOLDER = $arParams["SEF_FOLDER"];
}
else
{
$arVariables = array();
$arVariableAliases =
CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases,
$arParams["VARIABLE_ALIASES"]);
CComponentEngine::InitComponentVariables(false,
$arComponentVariables,
$arVariableAliases, $arVariables);
$componentPage = "";
if (IntVal($arVariables["ELEMENT_ID"]) > 0)
$componentPage = "element";
else
$componentPage = "list";
}
$arResult = array(
"FOLDER" => $SEF_FOLDER,
"URL_TEMPLATES" => $arUrlTemplates,
"VARIABLES" => $arVariables,
"ALIASES" => $arVariableAliases
);
$this->IncludeComponentTemplate($componentPage);
?>
The following arrays are defined in the beginning of the code:
- $arDefaultUrlTemplates404 - is used to set up paths by default in order to work in a Search Engine Friendly URL (SEF) mode. Each element of the array is a path template and is to be set up as follows:
"path template code" => "path template"
Structure of the type "#word#" may be used in the path template. These structures are replaced with the values of the relevant variables when the actual path is being generated. For example, for a path template:
"element" => "#ELEMENT_ID#.php"
The actual path will look like 195.php or 7453.php. Path templates may have parameters, for example:
"element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SECTION_ID=#SECTION_ID#"
All path templates used by the component must be set up.
- $arDefaultVariableAliases404 - is used to set up default aliases for variables in a SEF mode. As a rule, this array is empty (real names of variables are used). In case a variable must have a different name in an HTTP request (address), an alias can be set up for this variable and the variable value can be recovered from the alias during component operation. In case an alias must be set for one or more variables of any path templates, an element similar to that indicated below must appear in the array:
"path template code" => array(
"variable 1 name" => "variable 1 alias",
"variable 2 name" => "variable 2 alias",
* * *
)
For example, if a link to the page containing detailed information about an infoblock element (e.g., a product card) must be similar to the following:
"/<infoblock mnemonic code>/.php?SID=<element group code>"
then the path template can be set up as follows:
"element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SID=#SECTION_ID#"
and an alias for the variable SECTION_ID in the array $arDefaultVariableAliases404 can be set up as:
"element" => array(
"SECTION_ID" => "SID"
)
In this case, links (addresses) will be generated using the SID parameter and the variable SECTION_ID will be set up in the components.
- $arDefaultVariableAliases - is used to set up default aliases for variables not in a CNC mode. As a rule, this array is empty, i.e. real names of variables are used. In case a variable must have a different name in an HTTP request (address), an alias can be set up for this variable and the variable value can be recovered from the alias during component operation. In case an alias must be set for any variable, an element similar to that indicated below must appear in the array:
"variable name" => "variable alia"
For example, if the variable name is defined in the component SECTION_ID, but the SID variable is to be used in the links, an alias for SECTION_ID can be set up as follows:
"SECTION_ID" => "SID"
In this case, links (addresses) will be generated using the SID parameter, and the components will contain the SECTION_ID variable. All of these arrays or their parts can be redefined using the input parameters of the component (upon component retrieval). For example, the following array can be set up in the input parameter SEF_URL_TEMPLATES in a SEF mode:
"SEF_URL_TEMPLATES" => array(
"element" => "#IBLOCK_CODE#/#ELEMENT_ID#.php?GID=#SECTION_ID#"
)
and the following parameter can be set up in the input parameter VARIABLE_ALIASES :
"VARIABLE_ALIASES" => array(
"element" => array(
"SECTION_ID" => "GID",
),
)
Then, the paths in addresses (links) will have the type similar to /phone/3425.php?GID=28and the following variables will be recovered in the component out of these path: IBLOCK_CODE = phone, ELEMENT_ID = 3425 and SECTION_ID = 28.
- $arComponentVariables - is used to set up a list of variables wherein the component can accept in an HTTP request and admit aliases. Each element of an array is the name of a variable.
An input parameter with the preset name SEF_MODE can have two values: Y and N. If $arParams["SEF_MODE"] is equal to Y, the component works in a SEF mode; otherwise, it does not.
An input parameter with the preset name SEF_FOLDER makes sense if the component works in a SEF mode. In this case, it contains the path along which the component works. The path may be virtual (i.e. it might not exist physically). For example, the component from the example can be located in the file /fld/n.php, at the same time, it works in a SEF mode, and the input parameter SEF_FOLDER is equal to /company/news/. In this case, the component will respond to queries at the addresses /company/news/index.php, /company/news/25.php etc.
The following methods are used in order to determine the page to be shown by the composite component and also to recover component variables from the path and aliases:
- CComponentEngine::MakeComponentUrlTemplates
array
CComponentEngine::MakeComponentUrlTemplates($arDefaultUrlTemplates404,
$arParams["SEF_URL_TEMPLATES"]);
The method combines into a single array the default array of path templates and path templates submitted in input parameters of a component. If a template of any path is defined in $arParams["SEF_URL_TEMPLATES"], it will redefine the default template for such path.
- CComponentEngine::MakeComponentVariableAliases
array
CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases404,
$arParams["VARIABLE_ALIASES"]);
The method combines into a single array the default array of variable aliases and the variable aliases submitted in the input parameters of a component. If an alias of a variable is also defined both in the default array and in input parameters, the alias from the input parameters is returned.
- CComponentEngine::ParseComponentPath
string
CComponentEngine::ParseComponentPath($arParams["SEF_FOLDER"],
$arUrlTemplates, $arVariables);
The method based on the parameter $arParams["SEF_FOLDER"] and path template array (returned by the method MakeComponentUrlTemplates) determines the path template to which the requested address corresponds. If the template was found, its code is returned. Otherwise, an empty line is returned. In addition, an array of component variables recovered from the path template without parameters is returned in the $arVariables variable. For example, if a path template array (resulted from the array $arDefaultUrlTemplates404 after the redefinition of all or a part of templates through input parameters of the component) looks like:
$arUrlTemplates = array(
"list" => "index.php",
"element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SID=#SECTION_ID#"
);
If the input parameter SEF_FOLDER is equal to /company/news/, and the requested address is equal to /company/news/15/7653.php?SID=28, the method ParseComponentPath will return the line "element" (code of the relevant template), and the array $arVariables will look as follows:
$arVariables = array(
"IBLOCK_ID" => 15,
"ELEMENT_ID" => 7653
)
- CComponentEngine::InitComponentVariables
CComponentEngine::InitComponentVariables($componentPage,
$arComponentVariables,
$arVariableAliases,
$arVariables);
where:
- $componentPage - is the code of the template that returned the ParseComponentPath method and to which the requested address corresponds;
- $arComponentVariables - is an array of variables that a component may admit in a HTTP request and that may have aliases;
- $arVariableAliases - is an alias array (returned by the method MakeComponentVariableAliases).
The method recovers variables from $_REQUEST taking into account their possible aliases and returns them in the variable $arVariables. For example, if for the code above the array $arVariableAliases contains an entry
"element" => array(
"SECTION_ID" => "SID",
)
the method InitComponentVariables will return an array in the $arVariables parameter
$arVariables = array(
"IBLOCK_ID" => 15,
"ELEMENT_ID" => 7653,
"SECTION_ID" => 28
)
Here, the method InitComponentVariables initialized the third element of the array. The first two were initialized by the method ParseComponentPath in the example above. In case the component works in a non-SEF mode, the first parameter submits the value False to the method InitComponentVariables.
If the component works in a SEF mode based on the path template code and variables from HTTP request (and in case of non-SEF addresses only based on variables from HTTP request) the component determines which page from the template is to be connected and forwards its name to the method invocation:
$this->IncludeComponentTemplate($componentPage);
Simple components are connected and their input parameters are set up on pages of a composite component template based on the input parameters of the composite component and certain calculated values and constants. For example, the page "element" of the component template from the example (file of the type /templates/.default/list.php with respect of the component folder) may look as follows:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
<?$APPLICATION->IncludeComponent(
"bitrix:news.detail",
"",
Array(
"IBLOCK_ID" => $arParams["IBLOCK_ID"],
"ELEMENT_ID" => $arResult["VARIABLES"]["ELEMENT_ID"],
"SECTION_ID" => $arResult["VARIABLES"]["SECTION_ID"],
"CACHE_TIME" => $arParams["CACHE_TIME"],
),
$component
);?>
The last parameter $component in the component connection is an object representing the current component. It is forwarded to the component connection call. Thus, the component to be connected will “know” that it is connected from the composite component. Accordingly, it will be able to use the resources of the composite component, invoke its methods, etc.
Component Description
The file .description.php contains a description of the component. This description is used for work with the component (for example, in a visual editor) and also for work in the website editing mode. During the work of the component itself (when invoking the page where the component is located), the description is not used, and the file .description.php is not connected.
The file .description.php must be located in the component folder. The language file connects automatically (it must be located in the folder /lang/<language>/.description.php of the component folder).
Typically, the file .description.php has the following structure:
<?
$arComponentDescription = array(
"NAME" => GetMessage("COMP_NAME"),
"DESCRIPTION" => GetMessage("COMP_DESCR"),
"ICON" => "/images/icon.gif",
"PATH" => array(
"ID" => "content",
"CHILD" => array(
"ID" => "catalog",
"NAME" => "Catalog"
)
),
"AREA_BUTTONS" => array(
array(
'URL' => "javascript:alert('Button');",
'SRC' => '/images/button.jpg',
'TITLE' => "Button"
),
),
"CACHE_PATH" => "Y",
"COMPLEX" => "Y"
);
?>
As we can see, the file determines the array $arComponentDescription which describes the component. This array may have the following keys:
- NAME - name of the component;
- DESCRIPTION - description of the component;
- ICON - path to the pictogram of the component from the component folder. The icon of the component is used in different parts of the system (e.g., in the visual editor);
- PATH - component location in the virtual tree of the component in the visual editor. The value of this element must be an array with the following keys:
- ID - code of the tree branch. Node ID must be unique within the entire component tree (including standard nodes). If the nodes have two equal ID, both of them will not open. E.g., for the proprietary component the ID node = “news” is selected, and such ID already exists for standard components.
- NAME - name of the tree branch. It must be indicated. The NAME is taken from any component from the node. If no NAME is found or there is no language constant necessary, ID is used as a NAME.
- CHILD - a child or subordinate branch. In the element with the CHILD key a subordinate branch of the tree with the same structure as the parent branch can be set up.
The tree is limited to three tiers. As a rule, a two-tier tree is built, and the components are located on the second tier. The following service names of the first tier are reserved and cannot be used: content, service, communication, e-store and utility.
If the PATH key is not established, the component will not be available in the visual editor.
- AREA_BUTTONS - user buttons shown for the component in the website editor mode;
- CACHE_PATH - if the value is equal to Y, the cache flush button of the component is shown in the website editor mode (the cache is supposed to be located at the standard path: /<website code>/<relative path to the component>). If equal to a non-empty line different from Y, the cache flush button of the component is shown in the website editor mode (the cache is located at the path equal to the value with the key CACHE_PATH - for non-standard paths);
- COMPLEX - the element must have the value Y for a composite component and has no significance for simple components.
Component Parameters
The file .parameters.php contains the description of the input parameters of the component. The file data are needed exclusively to create the form to enter the properties of the component in Bitrix Framework environment (e.g., in a visual editor). This description is used for work with the component and also during work in the website editing mode. The description is not used and the said file is not connected during the operation of the component itself (when the page with the component is requested).
The fil .parameters.php must be located in the component folder. The language file is connected automatically (it must be located in the folder /lang/<language>/.parameters.php, of the component folder).
The file determines the array $arComponentParameters, which describes the input parameters of the component. If necessary, any additional data are selected. E.g., all active types must be selected in order to establish a drop-down list of information block types (input parameter IBLOCK_TYPE_ID).
The structure of the typical file .parameters.php (based on the example of the components that operate with the module Information Block):
<?
CModule::IncludeModule("iblock");
$dbIBlockType = CIBlockType::GetList(
array("sort" => "asc"),
array("ACTIVE" => "Y")
);
while ($arIBlockType = $dbIBlockType->Fetch())
{
if ($arIBlockTypeLang = CIBlockType::GetByIDLang($arIBlockType["ID"], LANGUAGE_ID))
$arIblockType[$arIBlockType["ID"]] = "[".$arIBlockType["ID"]."] ".$arIBlockTypeLang["NAME"];
}
$arComponentParameters = array(
"GROUPS" => array(
"SETTINGS" => array(
"NAME" => GetMessage("SETTINGS_PHR")
),
"PARAMS" => array(
"NAME" => GetMessage("PARAMS_PHR")
),
),
"PARAMETERS" => array(
"IBLOCK_TYPE_ID" => array(
"PARENT" => "SETTINGS",
"NAME" => GetMessage("INFOBLOCK_TYPE_PHR"),
"TYPE" => "LIST",
"ADDITIONAL_VALUES" => "Y",
"VALUES" => $arIblockType,
"REFRESH" => "Y"
),
"BASKET_PAGE_TEMPLATE" => array(
"PARENT" => "PARAMS",
"NAME" => GetMessage("BASKET_LINK_PHR"),
"TYPE" => "STRING",
"MULTIPLE" => "N",
"DEFAULT" => "/personal/basket.php",
"COLS" => 25
),
"SET_TITLE" => array(),
"CACHE_TIME" => array(),
"VARIABLE_ALIASES" => array(
"IBLOCK_ID" => array(
"NAME" => GetMessage("CATALOG_ID_VARIABLE_PHR"),
),
"SECTION_ID" => array(
"NAME" => GetMessage("SECTION_ID_VARIABLE_PHR"),
),
),
"SEF_MODE" => array(
"list" => array(
"NAME" => GetMessage("CATALOG_LIST_PATH_TEMPLATE_PHR"),
"DEFAULT" => "index.php",
"VARIABLES" => array()
),
"section1" => array(
"NAME" => GetMessage("SECTION_LIST_PATH_TEMPLATE_PHR"),
"DEFAULT" => "#IBLOCK_ID#",
"VARIABLES" => array("IBLOCK_ID")
),
"section2" => array(
"NAME" => GetMessage("SUB_SECTION_LIST_PATH_TEMPLATE_PHR"),
"DEFAULT" => "#IBLOCK_ID#/#SECTION_ID#",
"VARIABLES" => array("IBLOCK_ID", "SECTION_ID")
),
),
)
);
?>
Let us have a closer look at the keys of the array $arComponentParameters.
GROUPS
The value of this key is an array of component parameter groups. In the visual means of the Bitrix Framework (e.g., in a visual editor), the parameters are grouped. The groups in the Bitrix Framework environment are located according to the order set in the file. The array of the component parameter groups consists of the following types of elements:
"group code" => array(
"NAME" => "name of the group in the current language"
"SORT" => "sorting",
)
List of standard groups:
- ADDITIONAL_SETTINGS (sorting - 700). This group appears, for example, when indicating the parameter SET_TITLE.
- CACHE_SETTINGS (sorting - 600). It appears when indicating the parameter CACHE_TIME.
- SEF_MODE (sorting 500). The group for all parameters connected with the use of SEF.
- URL_TEMPLATES (sorting 400). Link templates
- VISUAL (sorting 300). This group is rarely used. These are parameters responsible for physical appearance.
- DATA_SOURCE (sorting 200). Infoblock type and ID.
- BASE (sorting 100). Main parameters.
- AJAX_SETTINGS (sorting 550). Everything associated with AJAX.
PARAMETERS
The value of this key is an array of component parameters. In each parameter group, the parameters are located in the order set in the file. The array of regular parameters of the component consists of the following types of elements:
"parameter code" => array(
"PARENT" => "group code", // if not - ADDITIONAL_SETTINGS shall be established
"NAME" => "parameter name in the current language",
"TYPE" => "type of the control element in which the parameter will be established",
"REFRESH" => "refresh settings or not after selection (N/Y)",
"MULTIPLE" => "single/multiple value (N/Y)",
"VALUES" => "array of values for the list (TYPE = LIST)",
"ADDITIONAL_VALUES" => "show the field for the values to be entered manually (Y/N)",
"SIZE" => "number of lines for the list (if a list which is not a drop-down list is needed)",
"DEFAULT" => "default value",
"COLS" => "field width in characters",
),
The following values are available for the TYPE control element type:
The physical appearance of the list changes depending on whether the MULTIPLE and ADDITIONAL_VALUES keys are available/absent:
- If there are no MULTIPLE and ADDITIONAL_VALUES or they are equal to “N”, a simple list is displayed, and no values are added to the list:
- If ADDITIONAL_VALUES = "Y", MULTIPLE = "N", the value Other is added to the list with an additional field allowing manual entry of a value:
- If ADDITIONAL_VALUES = "N", MULTIPLE = "Y", nothing will be added to the list, but it becomes possible to select several elements:
- If ADDITIONAL_VALUES = "Y", MULTIPLE = "Y", the value Not selected is added to the list with a multiple additional field allowing manual entry of a value.
The REFRESH parameter permits to refresh the entire form with parameters following value selection. This is done, for example, to select an infoblock of a specific type. I.e., we have two parameters: infoblock type and infoblock code. The starting point is as follows: the first parameter contains all types of infoblocks, the second parameter contains a list of all infoblocks of this website; after the selection of a certain type of infoblock, component parameters are refreshed, and we can only see the infoblocks of the selected type.
Externally, for the LIST - type parameters, this key is represented as the OK button located close to the relevant parameter (see screenshots above).
If a certain parameter must appear or remain hidden depending on another parameter, the procedure is as follows. For example, we need to display the list of properties of an infoblock. Assuming that the infoblock ID is contained in the IBLOCK_ID component parameter, let us name the parameter containing property list PROP_LIST. The parameter IBLOCK_ID must have the key REFRESH = 'Y' established. The code:
if (0 < intval($arCurrentValues['IBLOCK_ID'])
{
$arPropList = array();
$rsProps = CIBlockProperty::GetList(array(),array('IBLOCK_ID' => $arCurrentValues['IBLOCK_ID']));
while ($arProp = $rsProps->Fetch())
{
$arPropList[$arProp['ID']] = $arProp['NAME'];
}
$arComponentParameters['PARAMETERS']['PROP_LIST'] => array(
'NAME' => 'parameter title',
'TYPE' => 'LIST'
'VALUES' => $arPropList,
);
}
There are special parameters that are standardized so that there is no need to describe them in detail. It is sufficient just to indicate that they exist. For example,
"SET_TITLE" => array(),
"CACHE_TIME" => array(),
The first of the indicated parameters shows whether the component should set a page header and the second one shows all cache-related settings.
Only composite components can work in a SEF mode or redefine the variables coming from an HTTP request. In this case, two special parameters must be indicated among the parameters:
- VARIABLE_ALIASES - an array describing variables that the component may receive from an HTTP request. Each element of the array looks like the following:
"internal name of the variable" => array(
"NAME" => "name of the variable in the current language",
)
- SEF_MODE - an array describing path templates in a SEF mode. Each element of the array looks like the following:
"path template code" => array(
"NAME" => "name of the path template in the current language",
"DEFAULT" => "default path template",
"VARIABLES" => "an array of internal names of variables that may be used in the template"
)
From product version 12 on (new core D7) there is an option to add a control element into the component parameters that permits to indicate color (COLORPICKER).
To do so, the following must be indicated in the component parameter file .parameters.php:
$arParams["COLOR"] = Array(
"PARENT" => "BASE",
"NAME" => 'Colour selection',
"TYPE" => "COLORPICKER",
"DEFAULT" => 'FFFF00'
);
Component Templates
Component template is a program code that converts data prepared by a component directly to HTML code.
Component templates are subdivided into system and user:
- System templates come together with the component and are located in the subfolder templates of the component folder.
- User templates of the component are the templates that have been changed according to the requirements of a specific website. They must be located in the folders of website templates (i.e. in
/bitrix/templates/website_template/
). When the component template is copied using the system it means that they will be located at the following path: /bitrix/templates/website_template/components/namespace/component_name/template_name
.
Component templates are defined by names. Default template is named .default. If no template name is indicated in the component parameter settings, the default template is retrieved. Other templates can have arbitrary names.
Component templates can be folders or files. If the template does not require translation into other languages, own styles, and other resources, such template may be located in a file. Otherwise, the template should be located in a folder.
Each component template is indivisible. If the system template of a component must be changed to fit for a specific website, such a component template must be copied to the website template folder in its integrity.
For example, the component bitrix:catalog.site.selector has the system template dropdown located in the subfolder templates of the component folder. If this component template is to be changed to fit a specific website, the dropdown template folder should be copied into the folder /bitrix/templates/website_template/components/bitrix/catalog.site.selector/
and changed as one deems necessary.
When including a component into a web page, the administrator shall establish which display template, exactly, will be used in this specific case.
Variables Available in the Component Template
The following variables are used in the template:
- $templateFile – a path to the template from the website root, e.g.:
/bitrix/components/bitrix/iblock.list/templates/.default/template.php
).
- $arResult – an array of component operation results.
- $arParams – an array of input parameters of a component; it may be used to keep track of the set parameters of the template (e.g., displaying full pictures or links).
- $arLangMessages – an array of language messages of the template (not required for php templates).
- $templateFolder - a path to the folder with the template from DOCUMENT_ROOT (e.g.,
/bitrix/components/bitrix/iblock.list/templates/.default
).
- $parentTemplateFolder – a parent template folder. This variable is very convenient to connect additional images or scripts (resources). It should be introduced to form a full path from the template folder.
- $component — a link to the currently invoked component (CBitrixComponent type).
- $this - a link to the current template (object describing the template, CBitrixComponentTemplate type)
- $templateName — the name of the component template (e.g., .default)
- $componentPath - a path to the component from DOCUMENT_ROOT (e.g.,
/bitrix/components/bitrix/iblock.list
)
- $templateData — an array for entry; please note that here the data from template.php can be transferred to the file component_epilog.php, and these data will be sent to cache because the file component_epilog.php is executed with each hit.
Also, within the PHP template, the variables $APPLICATION, $USER and $DB are declared global.
Simple Component Template
The file of a simple component template may contain the following subfolders and files:
- Subfolder
/lang
where the files of language messages (translations) of the component template are located;
- The file result_modifier.php that is to be connected directly before connecting the component template. This file receives as input the array of component operation results
$arResult
and the array of component invocation parameters $arParams
. This way, it is possible, for example, to change the array of the component operation results to fit a specific template.
- The file style.css determines the styles required for this template.
- The file script.js determines and connects java scripts required for this template. This file may be absent.
- The file .description.php containing the name and description of the template for the visual editor.
Example of file .description.php
|
<?
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
$arTemplateDescription = array(
"NAME" => GetMessage("ADV_BANNER_NAME"),
"DESCRIPTION" => GetMessage("ADV_BANNER_DESC"),
);
?> |
- The file .parameters.php containing a description of additional input parameters of the template for the visual editor.
Example of file .parameters.php
|
<?
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
if (!CModule::IncludeModule("advertising"))
return;
$arTypeFields = Array("-" =>GetMessage("ADV_SELECT_DEFAULT"));
$res = CAdvType::GetList($by, $order, Array("ACTIVE" => "Y"),$is_filtered, "Y");
while (is_object($res) && $ar = $res->GetNext())
{
$arTypeFields[$ar["SID"]] = "[".$ar["SID"]."] ".$ar["NAME"];
}
$arTemplateParameters = [
"TYPE" => [
"NAME" => GetMessage("ADV_TYPE"),
"PARENT" => "BASE",
"TYPE" => "LIST",
"DEFAULT" => "",
"VALUES" => $arTypeFields,
"ADDITIONAL_VALUES" => "N",
],
"NOINDEX" => [
"NAME" => GetMessage("adv_banner_params_noindex"),
"PARENT" => "BASE",
"TYPE" => "CHECKBOX",
"DEFAULT" => "N",
],
"QUANTITY" => [
"NAME" => GetMessage("ADV_QUANTITY"),
"PARENT" => "BASE",
"TYPE" => "STRING",
"DEFAULT" => "1",
],
"CACHE_TIME" => ["DEFAULT"=>"0"],
];
if ($templateProperties['NEED_TEMPLATE'] == 'Y')
{
$templates = array('-' => GetMessage("ADV_NOT_SELECTED"));
$arTemplates = CComponentUtil::GetTemplatesList('bitrix:advertising.banner.view');
if (is_array($arTemplates) && !empty($arTemplates))
{
foreach ($arTemplates as $template)
{
$templates[$template['NAME']] = $template['NAME'];
}
}
$arTemplateParameters['DEFAULT_TEMPLATE'] = [
"NAME" => GetMessage("ADV_DEFAULT_TEMPLATE"),
"PARENT" => "BASE",
"TYPE" => "LIST",
"VALUES" => $templates,
"DEFAULT" => '',
"ADDITIONAL_VALUES" => "N",
];
unset($templateProperties['NEED_TEMPLATE']);
} |
- The file template.ext is the template proper. The extension ext depends on the type of the templating motor to be connected. The default extension is php. This file is mandatory.
- Any other folders and files containing the resources required for the component template. For example, the image folder containing the images required by the template.
Composite Component Template
A composite component template contains the same folders as a simple component template, plus:
- Templates of simple components that form part of the composite template. These templates are located in the folders of the type
/namespace/simple_component_name/
in the composite component template folder.
- Simple components that form part of the composite template are connected in the templates of pages of the composite component.
How the System Searches for the Template
The following search algorithm is used to find a suitable template for a component:
- Everything begins with the folder
/bitrix/templates/current_site_template/components/
. In this folder, the file or folder with the template name is searched for along the path /component_namespace/component_name/
. If there are none, then the availability of the file template_name.ext is searched for, where the ext extension includes all the available extensions of all templating motors established on the website that are checked one by one. If the template is found, the algorithm is ended.
- If no template is found on step 1, the search is performed in the folder
/bitrix/templates/.default/components/
. The algorithm described in step 1 applies. If the template is found, the algorithm is ended.
- If no template is found in step 2, the search among the system templates (i.e. those supplied with the component) is performed.
Specifics of the search:
- If no template name is set, the template with the .default name is searched for.
- If the template is set as a folder name, then in case of a simple component in this folder the file template.extis searched for, and in case of a composite template - page_name.ext. The ext extension is assumed equal to php first, and then to extensions of other templating motors available on the website.
For example, the component bitrix:catalog.list must be shown using the template table. Let us assume that on the website, in addition to the standard templating motor (php files), the Smarty motor (tpl files) is also available. The system will first check the folder /bitrix/templates/current_site_template/components/bitrix/catalog.list/
to find a file or a folder named table. If there are none, the system will check the aforementioned folder to find the files table.php and table.tpl. If nothing is found, the system will review the folders /bitrix/templates/.default/components/bitrix/catalog.list/
and /bitrix/components/bitrix/catalog.list/templates/
.
f the component folder is found, the file template.php is searched for first and if no such file is found, the file template.tpl. is searched for. If the template is set as table/template.php
, the indicated file is retrieved at once.
If a simple component is retrieved as a part of a composite component, the simple component template is first searched for within the composite component template, and after that (if not found) in own templates. To make sure this rule works, when calling for simple components within composite components do not forget to indicate the variable $component as the fourth parameter indicating the parent component. I.e. the code for invoking a simple component must look as follows:
$APPLICATION->IncludeComponent("custom:catalog.element", "", array(...), $component);
Note:
The same folder (e.g., /bitrix/templates/current_site_template/components/
) contains templates of two components, a composite and a simple:
- catalog (composite component that contains the simple catalog.section)
- catalog.section (simple)
According to website operation conditions, a single template must be used for two
catalog.section occurrences. In this case, this template must have a name other than
.default; otherwise, it will not be caught.
Template Connection
Only <namespace>, component name, and template name (and parameters of the component itself) must be indicated in the connection code. When processing the code, the core first checks the availability of the component template in the current website template: /bitrix/templates/<site template name>/components/<namespace>/<component name>/<template name>/template.php
.
If <namespace> is bitrix it is the folder for templates of standard components. If <namespace> is selected by you <name> for your components is located in /bitrix/components/<name>
, it is the folder for the templates of your components.
If no template file is available, the default website template is checked: /bitrix/templates/.default/components/<namespace>/<component name>/<template name>/template.php
.
Only after that will the component template be connected from the component folder.
The template is connected by the command:
$this->IncludeComponentTemplate($templatePage = "");
Where $templatePage is the name of the current page for a composite component, and an empty line for a simple component.
Component template connection examples:
- Let us connect the template of the current composite component for the Section page:
$this->IncludeComponentTemplate("section");
- Connect the template of the current simple component:
$this->IncludeComponentTemplate();
Template Edit
Template editing is one of the ways to get a required component result suitable for site requirements. It's not recommended to edit the system template because , что при первом же обновлении он восстановит свое прежнее состояние. Редактировать и применять можно только пользовательский шаблон.
Copying is done using the command Copy component template with Edit mode enabled.
When copying the template you can specify its application to the component and start its editing.
If you enable the option Edit template you будет осуществлен переход на страницу редактирования шаблона компонента.
Editing can include an action logic, but it's better to move such update to files result_modifier.php and component_epilog.php (located in the template folder) for more complex result.
AJAX handling specifics
Using AJAX mode has its own specifics. For a breadcrumb bar at the page opened via AJAX to have its own name, the component template must have an element with id="navigation"
. This may not be div
, but span
, h1
, p
and etc.
Similarly, title must have an element with id="pagetitle"
.
Template Example
Menu Component Template
|
Start test | <?if (!defined("B_PROLOG_INCLUDED")
|| B_PROLOG_INCLUDED!==true)die();?> |
Script start | <?if (!empty($arResult)):?> |
<ul> tag opening — unnumbered list | <ul class="…"> |
Search cycle start | <?foreach ($arResult as $arItem):?> |
Link display | <?if($arItem["SELECTED"]):?> |
Active link | <li><a href="<?=$arItem["LINK"]?>" class=
"selected"><?=$arItem["TEXT"]?></a></li> |
Check for cycle continuation | <?else:?> |
Inactive link | <li><a href="<?=$arItem["LINK"]?>">
<?=$arItem["TEXT"]?></a></li> |
Link display ends | <?endif?> |
Search cycle ends | <?endforeach?> |
<ul> tag closes — unnumbered list | </ul> |
Script ends | <?endif?> |
|
Support of Component Classes
From version 12.0.0 and later, component class support is available. It is implemented in the form of a file /component_name/class.php
. Class.php is a reserved file name, and this file is automatically connected upon retrieval:
$APPLICATION->IncludeComponent()
In this case, the final call of the method initComponent is made, where class.php (if any) is connected from which the most recent inheritor from CBitrixComponent is taken.
The actions like:
class CDemoTest extends CBitrixComponent{}
class CDemoTestDecorator1 extends CDemoTest {}
class CDemoTestDecorator2 extends CDemoTest {}
will not be successful. As a result, CDemoTestDecorator2 will be used.
Please note that when changing the base class of the component, the behavior of all its descendants (other components) will have to be taken into account.
Examples of Use
Let us consider the simplest component taking the square of a parameter.
File /bitrix/components/demo/sqr/component.php:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
$arParams["X"] = intval($arParams["X"]);
if($this->startResultCache())
{
$arResult["Y"] = $arParams["X"] * $arParams["X"];
}
$this->includeComponentTemplate();
?>
File /bitrix/components/demo/sqr/templates/.default/template.php
:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
<div class="equation">
<?echo $arParams["X"];?> squared is equal to <?echo $arResult["Y"];?>
</div>
In the real components, there may be three dozen lines and 5-6 similar operations instead of the multiplication operation. As a result, the file component.php becomes difficult to understand.
Let us separate the component logic into a class.
File /bitrix/components/demo/sqr/class.php
:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
class CDemoSqr extends CBitrixComponent
{
//Parent method passes along all of the parameters transferred to $APPLICATION->IncludeComponent
//and applies the function htmlspecialcharsex to them. In this case, such processing is excessive.
//Redefine.
public function onPrepareComponentParams($arParams)
{
$result = array(
"CACHE_TYPE" => $arParams["CACHE_TYPE"],
"CACHE_TIME" => isset($arParams["CACHE_TIME"]) ?$arParams["CACHE_TIME"]: 36000000,
"X" => intval($arParams["X"]),
);
return $result;
}
public function sqr($x)
{
return $x * $x;
}
}?>
File /bitrix/components/demo/sqr/component.php
:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
if($this->startResultCache())
{
//$this - an instance of CDemoSqr
$arResult["Y"] = $this->sqr($arParams["X"]);
}
$this->includeComponentTemplate();
?>
Now the code in the fileе component.php has become controllable.
Component Inheritance
For example:
The file /bitrix/components/demo/double_sqr/class.php
:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
//Necessary for the correct search of the class CDemoSqr
CBitrixComponent::includeComponentClass("demo:sqr");
//Inheritor that expands functionality:
class CDemoDoubleSqr extends CDemoSqr
{
public function sqr($x)
{
return parent::sqr($x)*2;
}
}?>
The file /bitrix/components/demo/double_sqr/component.php
is identical to the file /bitrix/components/demo/sqr/component.php
, the contents can be copied.
The file /bitrix/components/demo/double_sqr/templates/.default/template.php
:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
<div class="equation">
<?echo $arParams["X"];?> squared multiplied by 2 is equal to <?echo $arResult["Y"];?>
</div>
A Component without component.php
A component may also be created without the file component.php
To do so, it is sufficient to deactivate the method executeComponent. For example:
class CDemoSqr extends CBitrixComponent
{
...
public function executeComponent()
{
if($this->startResultCache())
{
$this->arResult["Y"] = $this->sqr($this->arParams["X"]);
}
$this->includeComponentTemplate();
return $this->arResult["Y"];
}
};
Now, the files component.php can be deleted from both components.
result_modifier.php
The file result_modifier.php is a tool that is used to arbitrarily modify the data of the component operation. The developer shall create this file independently.
If the file result_modifier.php is located in the template folder, it is retrieved prior to connecting the template.
Sequence of Work of the Component with the File result_modifier.php:
In this file, it is possible to request additional data and introduce them into the array of component operation results $arResult
. It may prove to be useful if any additional data are to be displayed but component customization is undesirable since it means abandoning component support and updates.
Note: The file result_modifier.php will run only if the template is NOT cached. And no dynamic properties of the: title, keywords and descriptioin type can be established through it.
Language phrases of the component template and the following variables are available in the file:
- $arParams - parameters, read, change. Does not affect the homonymous component member but changes made here affect $arParams in the file template.php.
- $arResult — result, read/change. Affects the homonymous component class member.
- $APPLICATION, $USER, $DB - there is no need to declare them global because they are available by default.
- $this — link to the current template (object describing the template CBitrixComponentTemplate type)
component_epilog.php
The file component_epilog.phpis a tool to modify the component operation data when caching is activated. It should be created by the developer independently (available in version 9.0 and later).
Sequence of Work of the Component with the Files result_modifier.php and component_epilog.php:
This file is connected after the execution of the template. Similarly to style files, the parent component stores a list of epilogue files of all the templates of child components (possibly embedded) in its cache and (following cache hit) connects these files in the same order as they were executed without caching. Likewise, when invoking child components, the value $component
must be submitted to the template.
$arParams, $arResult are available in the file component_epilog.php, but these values are retrieved from the cache. A set of keys of the $arResult array to be cached is determined in the file component.php as follows:
$this->SetResultCacheKeys(array(
"ID",
"IBLOCK_TYPE_ID",
"LIST_PAGE_URL",
"NAV_CACHED_DATA",
"NAME",
"SECTION",
"ELEMENTS",
));
When developing your own components, always use this structure in order to limit the cache size to the data that are really necessary.
Note: The file
component_epilog.php may contain any code. You should only keep in mind that it will be executed with each hit whether cache is available or not. In versions of the main module earlier than 10.0 a copy of the
arResult array was submitted to the component template. Due to this, the modification of this array in the file
result_modifier.php did not give any results. The following code shall be located in the
result_modifier.php to permit making changes in the results of cached data retrieval:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
global $APPLICATION;
$cp = $this->__component; // component object
if (is_object($cp))
{
// we add two fields - MY_TITLE and IS_OBJECT to arResult of the component
$cp->arResult['MY_TITLE'] = 'My title';
$cp->arResult['IS_OBJECT'] = 'Y';
$cp->SetResultCacheKeys(array('MY_TITLE','IS_OBJECT'));
// we save them in copy of arResult used by the template
$arResult['MY_TITLE'] = $cp->arResult['MY_TITLE'];
$arResult['IS_OBJECT'] = $cp->arResult['IS_OBJECT'];
$APPLICATION->SetTitle($cp->arResult['MY_TITLE']); // will not work on each hit:
//will work only the first time, then everything will be retrieved from the cache and there will be no invocation of $APPLICATION->SetTitle()
//That is why the title is changed in the component_epilog which is executed on each hit.
}
?>
After that, the changes made to arResult and performed in the template will become available in component_epilog.php.
Example of the File component_epilog.php
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
global $APPLICATION;
if (isset($arResult['MY_TITLE']))
$APPLICATION->SetTitle($arResult['MY_TITLE']);
?>
Specifics of Use
The file component_epilog.php is connected immediately after the connection and execution of the template. Thus, if the component provides for a template connection and then the component code provides for other operations, they will be executed after the execution of the file component_epilog.php.
Accordingly, in case the changed data coincide in the component_epilog.php and in the component code after the template is connected, only the latest data will be displayed, i.e. those from the component code.
Example of Such Situation
Let us use the file component_epilog.php from the example above. The component code (file component.php) contains the following code:
<?
$this->IncludeComponentTemplate();
if($arParams["SET_TITLE"])
{
$APPLICATION->SetTitle($arResult["NAME"]);
}
?>
In this case, you will not obtain the desired result since the component data will be displayed instead of those from component_epilog.php.
The file component_epilog.php contains the following:
- $arParams - parameters, read/change. Does not affect the homonymous component member.
- $arResult — result, read/change. Does not affect the homonymous component class member.
- $componentPath — path to the component folder from DOCUMENT_ROOT (e.g.,
/bitrix/components/bitrix/iblock.list
).
- $component — link to $this.
- $this — link to the currently invoked component (CBitrixComponent class object), all class methods may be used.
- $epilogFile — path to the file component_epilog.php from DOCUMENT_ROOT
- $templateName - component template name (e.g.: .default)
- $templateFile — path to the template file from DOCUMENT_ROOT (e.g.
/bitrix/components/bitrix/iblock.list/templates/.default/template.php
)
- $templateFolder — path to the template folder from DOCUMENT_ROOT (e.g.
/bitrix/components/bitrix/iblock.list/templates/.default
)
- $templateData — please note that this is the way to transfer data from template.php to the file component_epilog.php, and these data will be sent to the cache and become available in component_epilog.php with each hit/
- $APPLICATION, $USER, $DB — global variables.
Operation of a Composite Component in a SEF Mode
Composite components have an embedded SEF generation function. These components always come with the input parameter SEF_MODE which may admit the values Y and N. If the SEF_MODE parameter is equal to N, the component works with physical links and transfers all parameters through the standard parameters of an HTTP request. For example:
/fld/cat.php?IBLOCK_ID=12&SECTION_ID=371
If the parameter SEF_MODE is equal to Y, the component generates and processes links based on templates. For example, the component can “understand” and process the link:
/catalog/section/371.php?IBLOCK_ID=12, even if the component itself is located in the file /fld/cat.php.
If the parameter SEF_MODE is equal to Y, the component must also have the parameter SEF_FOLDER, containing a path to the folder necessary for the component. This path may or may not coincide with the physical path. For example, in the component bitrix:catalog, connected to the file /fld/cat.php, the parameters SEF_MODE = Y and SEF_FOLDER=/catalog/ can be set up. In this case, the component will respond to queries following the path /catalog/....
A composite component that can work in an SEF mode must have a set of default path templates. For example, in a composite component bitrix:catalog the following array must be defined:
$arDefaultUrlTemplatesSEF = array(
"list" => "index.php",
"section" => "section.php?IBLOCK_ID=#IBLOCK_ID#&SECTION_ID=#SECTION_ID#",
"element" => "element.php?ELEMENT_ID=#ELEMENT_ID#"
);
These path templates can be redefined using the input parameters of the composite component SEF_URL_TEMPLATES, containing a new array of all the path templates or a part of them.
When saving a page with a component working in an SEF mode, the editor creates or updates the entry in the urlrewrite system. E.g., when saving the file /fld/cat.php containing the component bitrix:catalog switched in the SEF mode with the parameter SEF_FOLDER=/catalog/, the urlrewrite system creates or update the following entry:
array(
"CONDITION" => "#^/catalog/#",
"ID" => "bitrix:catalog",
"PATH" => "/fld/cat.php"
),
- A value of the SEF_FOLDER parameter is written in CONDITION between the symbols «#^» and «#»;
- The name of the component is written in ID;
- A physical path to the saved file is written in PATH.
If an entry with such PATH and ID already exists, it is updated; if not, it is added.
In run-time when a physically inexistent page is requested, the urlrewrite mechanism searches for the relevant entry by CONDITION and transfers control to the page PATH.
The component on the PATH page figures out the requested page and recovers variables hidden in the path.
Attention! It is mandatory that the set of path templates of this component regarding each path template is unique, not taking into account the parameters and variables. It must be checked when saving the page in a visual editor.
I.e., the path template set
"section" => "section/#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#",
"element" => "element/#ELEMENT_ID#.php"
is acceptable, but the path template set
"section" => "#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#",
"element" => "#ELEMENT_ID#.php"
is not acceptable.
List of links on the subject:
Frequent Errors
Cannot Find Component Call Code
There are various reasons to this error:
- Component call code is not placed between separate
<? ?>
.
Solution: Check separation of the component code from another php code on the page.
I.e. if you have php code on the page like this:
<?
php code
component
php code
?>
it will return an error.
The code shall be written as follows:
<?
php code
?>
<?
component
?>
<?
php code
?>
- Errors in html code on the page.
Solution: Check the validity of the html code.
- Inconsistency of file encoding with the project in general.
Solution: Check the encoding. A quite versatile solution is to write the following strings in the file .htaccess:
For the website with the windows-1251 encoding:
php_value mbstring.func_overload 0
php_value mbstring.internal_encoding cp1251
For the website with the UTF-8 encoding:
php_value mbstring.func_overload 2
php_value mbstring.internal_encoding utf-8
- Inconsistency between file the owner and user under which the system edits the files.
Solution: Check the user’s rights.
Component Caching
One of the types of caching in Bitrix Framework is component caching.
Components must use caching in order to process the client’s request faster and to reduce the server load. As a rule, the information that is not dependent on a specific visitor must be cached. For example, the list of website news is the same for all visitors. That is why it makes no sense to select the data from the database each time.
All dynamic components that are used in order to create web pages have an embedded support of cache control. In order to use the technique, it is sufficient to activate auto cache by clicking a button on the administrative panel. This feature comes in handy at the developing stage when auto cache can be deactivated to make the work easier, and activated again before delivery of the project. In this case, all of the components with auto cache mode activated in their settings will create caches and will entirely switch to a work mode that does not involve database queries.
Attention! When using the Autocache mode (Components cache), the information retrieved by the components is updated according to the parameters of separate components.
Auto cache control is located on the tab Component caching (Control Panel > Settings > System settings > Cache Settings).
Note: When component auto cache mode is activated, the components with the caching setting Auto + Managed will be switched to a work mode with caching.
In order to update the contents of cached objects on a page, you can:
- Go to the required page and update its contents using the button Refresh Cache on the control panel.
- In Edit Mode use the flush cache buttons on panels of specific components.
- Use automatic cache reset upon the expiry of caching time; for this, select the caching mode Cache or Auto + Managed in the component settings.
- Use automatic cache reset when data are changed; for this, select the caching mode Auto + Managed in the component settings.
- Go to the settings of the selected components and switch them to the work mode without caching.
Managed (auto) cache mode
Managed cache mode (Components cache) appeared in version 6 of the product replacing the standard component cache. The difference is that auto cache may be globally deactivated for the entire website using one button (Disable Caching) in the administrative part on the page Cache Settings Settings > System settings > Cache Settings.
As a rule, it is deactivated at the development stage and is activated before the delivery of the project. Do not expect Bitrix Framework itself to choose the caching time and the right moment to do so for a cache flush. Only the developer can do that based on the actual needs of each project: the caching time that is suitable for the information update frequency must be indicated in the component settings.
Structure and Storage Place
Cache of the components is stored in the files of the folder /bitrix/cache
.
Component cache identifier is generated based on the following parameters:
- ID of the current website which determines the path to the cache file (alternative storage means may be used, but it does not affect the work with components),
- Component name,
- Component template name,
- Component parameters,
- External conditions determined in the component (e.g., list of groups to which the current user is connected).
In knowing all of these parameters, it is possible to flush the cache of any component. Otherwise, the cache will be reset upon the expiry of the cache time, by the click of a button on the control panel of the public part, or by performing complete cache flush from the administrative part.
Note: When you reset the cache of a page using the “Refresh Cache” button, please keep in mind that the component may use the binding to groups for cache storage; in this case, non-registered users will continue seeing an outdated page.
The cache of public components is not reset immediately after adding/changing an infoblock element in the administrative part. This is because, for example, the News information block "does not know", where the news is displayed in the public part and how many components display it. It causes no problems if the caching time is set correctly.
Component Workflow Structure
$arParams contains a set of component parameters, component.php works with request input parameters and database, generates a result to the array$arResult. Component template converts results into HTML text.
Upon the first hit, the generated HTML goes to cache. With subsequent hits, no (or very few) queries are made to the database since the data are read from the cache.
Attention! Please keep in mind the order of execution; in this case, the component template code and result_modifier.php are not executed.
Frequent error: in the component template, deferred functions are retrieved: $APPLICATION->SetTitle()
, $APPLICATION->AddChainItem()
etc. In this case, they work only if caching is off.
When developing the templates of own components, the developer shall follow a simple rule: the template’s task is to generate HTML text on return based on the input array $arResult.
The generated cache ID of own components must be capable of unambiguously determining the resulting html. However, avoid sending too much data to the cache ID. That results in wasting disc space and reduces cache hits (thereby making cache less efficient).
Cache Dependencies (Tagged Cache)
Starting from the main module of version 9.1.0, cache tags are supported. Cache can be marked with tags and reset also by tags. The cache of the infoblock components can be reset when the information contained in them changes.
Note. For large data amount that are often updated, tagged caching is not justified.
Cache Tagging Base Code:
01: $cache_id = md5(serialize($arParams));
02: $cache_dir = "/tagged_getlist";
03:
04: $obCache = new CPHPCache;
05: if($obCache->InitCache(36000, $cache_id, $cache_dir))
06: {
07: $arElements = $obCache->GetVars();
08: }
09: elseif(CModule::IncludeModule("iblock") && $obCache->StartDataCache())
10: {
11: $arElements = array();
12: $rsElements = CIBlockElement::GetList($arParams["order"], $arParams["filter"]);
13:
14: global $CACHE_MANAGER;
15: $CACHE_MANAGER->StartTagCache($cache_dir);
16: while($arElement = $rsElements->Fetch())
17: {
18: $CACHE_MANAGER->RegisterTag("iblock_id_".$arElement["ID"]);
19: $arElements[] = $arElement;
20: }
21: $CACHE_MANAGER->RegisterTag("iblock_id_new");
22: $CACHE_MANAGER->EndTagCache();
23:
24: $obCache->EndDataCache($arIBlocks);
25: }
26: else
27: {
28: $arElements = array();
29: }
Line 01 initializes the unique cache file identifier. Then, the catalog is defined from /bitrix/cache
where the cache files are stored with different values of $arParams. It is important that this path start from slash but not end with it. When using memcached or APC as the cache it will be of critical importance for the cache reset.
Lines 04-05 initialize the cached object. If the caching time is not expired, line 07 will be executed and we will obtain the data from the cache file.
The condition in line 09 will be true nearly always. Here, the module is connected and caching starts.
Line 12 provides for a database query. It is important that all of the parameters on which a selection result depends “participate” in the cache identifier ($cache_id).
In line 14, the access to the variable $CACHE_MANAGER. is set. This object will control tags.
Line 15 – all subsequently allocated tags will be bound to the catalog $cache_dir. When the cache is reset in one of them, the contents of this catalog will be deleted. StartTagCache may be used recursively. For example:
$CACHE_MANAGER->StartTagCache($cache_dir1);
$CACHE_MANAGER->StartTagCache($cache_dir2);
$CACHE_MANAGER->StartTagCache($cache_dir3);
$CACHE_MANAGER->EndTagCache();
$CACHE_MANAGER->EndTagCache();
$CACHE_MANAGER->EndTagCache();
It is important that the calls StartTagCache and EndTagCache are balanced. The object $CACHE_MANAGER creates and tracks the stack of cache catalogs. In this case, the tags allocated for the catalog $cache_dir3 бwill also be connected with $cache_dir2 and $cache_dir1. The tags allocated for cache_dir2 will be also connected with $cache_dir1.
Line 18 provides for tagging the cache by using the method RegisterTag. The body length may not exceed 100 characters. Tag duplicates are deleted automatically when the RegisterTag method is used.
Line 22 writes catalog tags to the database table. The count is one insert per tag.
Cache reset:
$CACHE_MANAGER->ClearByTag("iblock_id_7");
Infoblock Components
To launch the mechanism, a constant in the file dbconn.php must be defined.
define("BX_COMP_MANAGED_CACHE", true);
If the method StartResultCache is used, the entry will be retrieved by StartTagCache with a path to the component cache (depending on the page).
If the method EndResultCache is used (which, in its turn, is retrieved from IncludeComponentTemplate) - by EndTagCache.
In the infoblock module CIBlockElement::GetList and CIBlockSection::GetList return the object of the CIBlockResult class.
The method Fetch/GetNext of this object will retrieve $CACHE_MANAGER->RegisterTag("iblock_id_".$res["IBLOCK_ID"]);
.
If the selection does not contain any elements, the value of the infoblock identifier will be retrieved from the filter.
Cache reset is called from the methods Add/Update/Delete for elements, sections, and infoblocks. When the properties are changed, for example, using SetPropertyValueCode there will be no flushing. In this case, the following code can be used to clear cache:
if(defined('BX_COMP_MANAGED_CACHE'))
$GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_'.$arParams['IBLOCK_ID']);
The use of this tagged cache mechanism is not recommended in case of the frequent update of elements or sections. On the other hand, it must be convenient for content managers: all changes are immediately displayed on the website.
Adding an Own Tag to Component Caches
When performing instructions of this lesson, it is assumed that you have tagged caching activated.
Solution 1
Add the following code in the component body:
if ($this->StartResultCache(......))
{
if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
{
$GLOBALS['CACHE_MANAGER']->RegisterTag('my_custom_tag');
}
// do something
$this->IncludeComponentTemplate();
}
else
{
$this->AbortResultCache();
}
Solution 2
Add the following code to the component template (в result_modifier.php):
<?
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
{
$cp =& $this->__component;
if (strlen($cp->__cachePath))
{
$GLOBALS['CACHE_MANAGER']->RegisterTag('my_custom_tag');
}
}
?>
To reset all of the caches marked with your tag, execute the following code:
if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
$GLOBALS['CACHE_MANAGER']->ClearByTag('my_custom_tag');
Note: The same cache can be marked with several tags. For example, if you mark the cache of the component bitrix:news.list with your tag, the cache will have two tags: the standard "iblock_id_XX" and your "my_custom_tag". Accordingly, the cache will be reset both in case of adding/changing an element in the infoblock XX (standard functionality) and in case of resetting cache manually through ClearByTag('my_custom_tag')
.
Working with Components
A component is the primary way to retrieve information in Bitrix Framework. Accordingly, it is the work with the component that opens the maximum possibilities to change data display conditions and change (add) system functionalities.
he following task/solution ratio is recommended:
- To solve tasks that are aimed at changing the data display form (design) modify the component template.
- In order to change and expand cached data displayed by the component, please use the options of the file result_modifer.php.
- In order to implement the logic executed upon each call of the component irrespective of cache, use the options of the file component_epilog.php.
- In order to make additions and implicit changes (without interfering with the code) of component operation logic the technique of Events can be used.
- To expand the component operation logic, copy the component to its namespace and change it.
- To create new logic and new options, create a new component.
Quite often a task has to be solved using a combination of methods. I.e., for example, editing the template and adding a code in result_modifier.php.
In the chapters that follow, we will review the work with components in the indicated order of tasks and solutions.
Attention! When performing any actions while working with components, please do not forget about caching. Heavy coding introduced in component_epilog.php not be subject to cache. However, there are situations when it is better to customize the component and it may result in better performance (especially when a heavy code is used on a home or a most frequently visited page).
Template Customization
Template editing is one of the means to obtain the result of the component work that is appropriate for each specific website. Editing the system template is not recommended because after the very first update, the system template will recover its initial state. Only user template can be edited and used.
When starting to customize a template, please remember:
All logic must be located in the component, and the template must contain only the display representation of the data obtained!
As a rule, component template customization is aimed at:
- Adjusting the component data display form according to the website design;
- Arranging for component data display in a way unavailable as a standard option.
User templates of a component are the templates that are changed to meet the requirements of a specific project. They must be located in the portal template folders (i.e. in /bitrix/templates/site_template/
). When copying component template using system means they will be located at: /bitrix/templates/site_template/components/namespace/component_name/template_name
.
A template can be copied as follows:
- Within the file system – by coping the folder
/bitrix/components/bitrix/_required_component_/templates/
to the folder /bitrix/templates/website_template/components/namespace/component_name/_template_name
.
- Using system interface means using the command Copy component template (with Edit Mode activated):
When copying the template, its application to a component can be set and a template editing form can be opened at once:
It is possible not to apply the copied template to a component at once, but rather to do it later on by selecting the template in the component settings.
Important! If during component template editing you add new identifiers of language messages, please remember that the identifiers must be unique throughout the entire product.
Template editing admits adding action logic, but such a modification should be made in the files result_modifier.php and component_epilog.php (which must be located in the template folder) for a more complex change of work result.
The chapter provides for some examples of template customization.
Modification of a Simple Component Template within a Composite Component
A composite component ensures the interaction of simple components with the general subject. Simple components contain the code of immediate work with data. For example, it is inconvenient to set up the components of a social network one by one.
However, if you only seek to change the external appearance of some elements, it can be easily done without refusing other standard templates.
For example, you need to change the form of the news list, and the entire news section is built using a composite component. The list of news is built using the simple component List of news (news.list) which, similarly to the component templates for building a detailed news page, feedback, search, etc., forms part of the composite component template News (news). When copying the News template to the site template by using the option Copy component template of the context menu of the component, all of the files of the component template are copied to the website template. This means that when the News component template is updated through the Site Update you, by using a customized template, will lose the template update of all other templates included in the composite component (do not worry, standard templates and components are getting updated as usual). In order to customize only the component template List of news (news.list) and to make sure the composite component News connects it but uses the other standard (updated) templates of the templates included into the composite component, do the following:
- Create the folder news (or, depending on the composite component, a part of the template you need to customize) manually through file structure of the website in the website template folder (.default or current)
- Copy the template of the simple component so that its path resulted as follows:
/bitrix/templates/site_template/components/bitrix/news/.default/bitrix/news.list/.default/
- Edit the template obtained
The same approach can be used to edit the templates of simple components connected from the templates of composite components (i.e. when a template of a simple component does not form part of a composite component, and when a simple component is connected in the script of a composite component using the method CMain::IncludeComponent()).
For example, you have to change the form of creating/editing a group. In a social template Bitrix24 this form is built using the component Create new group in modal dialog box (socialnetwork.group_create.ex) to be connected from the composite component Social Network (socialnetwork_group).
If no modification of other parts of the composite component is needed, it will suffice to copy the component template socialnetwork.group_create.ex to the website template (/bitrix/templates/site_template/components/bitrix/socialnetwork_group/.default/bitrix/socialnetwork.group_create.ex/.default/
) and modify it there.
In this case, all remaining code will remain standard, i.e. it will be updated and supported by Bitrix, Inc.
Template Modification or Creation of result_modifier?
Quite often there are several solutions available to solve the same task. It is up to the developer to choose the final option based on the task at hand. Let us consider the examples of solving a task for which two solutions are available.
How to embed a video when posting news on a website? It may seem difficult at first. However, it is actually quite easy. The idea consists in connecting the component Media Player (bitrix:player) for a file attached to the news. The component News details (bitrix:news.detail) will be used to display the news.
Whatever solution you choose, you will have to create a property of the File type in the news infoblock.
Solution Involving Template Editing
- Copy the component template news.detail to the website template. You will not have to change the component itself.
- Create a new page using visual editor and place the component Media Player (bitrix:player) on it. Indicate basic settings (do not enter the path to the video file at this stage). Copy the following code from the obtained:
<?$APPLICATION->IncludeComponent(
"bitrix:player",
"",
Array(
"PLAYER_TYPE" => "auto",
"USE_PLAYLIST" => "N",
"PATH" => "",
"WIDTH" => "400",
"HEIGHT" => "300",
"FULLSCREEN" => "Y",
"SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins",
"SKIN" => "bitrix.swf",
"CONTROLBAR" => "bottom",
"WMODE" => "transparent",
"HIDE_MENU" => "N",
"SHOW_CONTROLS" => "Y",
"SHOW_STOP" => "N",
"SHOW_DIGITS" => "Y",
"CONTROLS_BGCOLOR" => "FFFFFF",
"CONTROLS_COLOR" => "000000",
"CONTROLS_OVER_COLOR" => "000000",
"SCREEN_COLOR" => "000000",
"AUTOSTART" => "N",
"REPEAT" => "N",
"VOLUME" => "90",
"DISPLAY_CLICK" => "play",
"MUTE" => "N",
"HIGH_QUALITY" => "Y",
"ADVANCED_MODE_SETTINGS" => "N",
"BUFFER_LENGTH" => "10",
"DOWNLOAD_LINK_TARGET" => "_self"
)
);?>
- In the component template, set up the media player connection instead of the movie property. Find the strings for property display:
30 <?foreach($arResult["DISPLAY_PROPERTIES"] as $pid=>$arProperty):?>
31
32 <?=$arProperty["NAME"]?>:
33 <?if(is_array($arProperty["DISPLAY_VALUE"])):?>
34 <?=implode(" / ", $arProperty["DISPLAY_VALUE"]);?>
35 <?else:?>
36 <?=$arProperty["DISPLAY_VALUE"];?>
37 <?endif?>
38 <br />
39 <?endforeach;?>
- Insert checking and replacement. The result:
<?foreach($arResult["DISPLAY_PROPERTIES"] as $pid=>$arProperty):?>
<?if ($arProperty["CODE"]=='movie' && $arProperty["DISPLAY_VALUE"]) {?>
<?$APPLICATION->IncludeComponent(
"bitrix:player",
"",
Array(
"PLAYER_TYPE" => "auto",
"USE_PLAYLIST" => "N",
"PATH" => CFile::GetPath($arProperty["VALUE"]),
"WIDTH" => "400",
"HEIGHT" => "300",
"FULLSCREEN" => "Y",
"SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins",
"SKIN" => "bitrix.swf",
"CONTROLBAR" => "bottom",
"WMODE" => "transparent",
"HIDE_MENU" => "N",
"SHOW_CONTROLS" => "Y",
"SHOW_STOP" => "N",
"SHOW_DIGITS" => "Y",
"CONTROLS_BGCOLOR" => "FFFFFF",
"CONTROLS_COLOR" => "000000",
"CONTROLS_OVER_COLOR" => "000000",
"SCREEN_COLOR" => "000000",
"AUTOSTART" => "N",
"REPEAT" => "N",
"VOLUME" => "90",
"DISPLAY_CLICK" => "play",
"MUTE" => "N",
"HIGH_QUALITY" => "Y",
"ADVANCED_MODE_SETTINGS" => "N",
"BUFFER_LENGTH" => "10",
"DOWNLOAD_LINK_TARGET" => "_self"
),
$component
);?>
<? } else {?>
<?=$arProperty["NAME"]?>:
<?if(is_array($arProperty["DISPLAY_VALUE"])):?>
<?=implode(" / ", $arProperty["DISPLAY_VALUE"]);?>
<?else:?>
<?=$arProperty["DISPLAY_VALUE"];?>
<?endif?>
<?}?>
<br />
<?endforeach;?>
Note: Please pay attention to the following:
- The system call CFile::GetPath is used to obtain the path to the file from the ID.
- When connecting components, the fourth parameter $component is indicated so that its parameters cannot be changed from the public part
Solution Using result_modifier.php
If you want to continue using the updated component the task should be solved using result_modifier.php.
- Create the file result_modifier.php with the code:
<?
// transfer property value using the link:
$arProperty = &$arResult['DISPLAY_PROPERTIES'][$arParams['PROPERTY_VIDEO']];
if ($arProperty['DISPLAY_VALUE']) // verify whether the property is set
{
global $APPLICATION;
ob_start(); // activate buffering to catch component retrieval
$APPLICATION->IncludeComponent(
"bitrix:player",
"",
Array(
"PLAYER_TYPE" => "auto",
"USE_PLAYLIST" => "N",
"PATH" => CFile::GetPath($arProperty["VALUE"]),
"WIDTH" => "400",
"HEIGHT" => "300",
"FULLSCREEN" => "Y",
"SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins",
"SKIN" => "bitrix.swf",
"CONTROLBAR" => "bottom",
"WMODE" => "transparent",
"HIDE_MENU" => "N",
"SHOW_CONTROLS" => "Y",
"SHOW_STOP" => "N",
"SHOW_DIGITS" => "Y",
"CONTROLS_BGCOLOR" => "FFFFFF",
"CONTROLS_COLOR" => "000000",
"CONTROLS_OVER_COLOR" => "000000",
"SCREEN_COLOR" => "000000",
"AUTOSTART" => "N",
"REPEAT" => "N",
"VOLUME" => "90",
"DISPLAY_CLICK" => "play",
"MUTE" => "N",
"HIGH_QUALITY" => "Y",
"ADVANCED_MODE_SETTINGS" => "N",
"BUFFER_LENGTH" => "10",
"DOWNLOAD_LINK_TARGET" => "_self"
)
);
$arProperty['DISPLAY_VALUE'] = ob_get_contents(); // substitute $arResult
ob_clean(); // clean our buffer so that the player do not appear twice
ob_end_clean(); // close the buffer
}
?>
The symbol code of the property can be made the parameter of the component to avoid the strict connection to a specific infoblock. To do so, the file .parameters.php of the News details component located in the copied component template must be adjusted.
- Change the code of the file .parameters.php:
<?
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
$arProps = array();
$rs=CIBlockProperty::GetList(array(),array("IBLOCK_ID"=>$arCurrentValues['IBLOCK_ID'],"ACTIVE"=>"Y"));
while($f = $rs->Fetch())
$arProps[$f['CODE']] = $f['NAME'];
$arTemplateParameters = array(
"DISPLAY_DATE" => Array(
"NAME" => GetMessage("T_IBLOCK_DESC_NEWS_DATE"),
"TYPE" => "CHECKBOX",
"DEFAULT" => "Y",
),
"DISPLAY_NAME" => Array(
"NAME" => GetMessage("T_IBLOCK_DESC_NEWS_NAME"),
"TYPE" => "CHECKBOX",
"DEFAULT" => "Y",
),
"DISPLAY_PICTURE" => Array(
"NAME" => GetMessage("T_IBLOCK_DESC_NEWS_PICTURE"),
"TYPE" => "CHECKBOX",
"DEFAULT" => "Y",
),
"DISPLAY_PREVIEW_TEXT" => Array(
"NAME" => GetMessage("T_IBLOCK_DESC_NEWS_TEXT"),
"TYPE" => "CHECKBOX",
"DEFAULT" => "Y",
),
"PROPERTY_VIDEO" => Array(
"NAME" => "Property where the video is stored",
"TYPE" => "LIST",
"VALUES" => $arProps
),
);
?>
As a result, a new parameter will appear in the component settings.
Do not forget to indicate a property where the video is stored in the component connection parameters. Otherwise, it will not be displayed.
Component Customization
Standard component customization - means copying a standard component to the own namespace and changing its operation logic to change/add a functionality.
Most tasks in Bitrix Framework are implemented through components, and in the component template you use the $arResult
arrays that constitute the work result of the component (data) and $arParams
that are input parameters.
The following steps must be taken in order to customize a standard component:
- Create a new component namespace in the folder
/bitrix/components/
; for example, create the catalog /bitrix/components/my_components/
.
- A folder with component you wish to change must be copied to the created folder (copy is to be made from the folder
/bitrix/components/bitrix/
).
- Change the component according to the tasks at hand.
- Change component description to your description in the files .description.php and
/lang/en/.description.php
;
- Correct the files .parameters.php and component.php by modifying (adding necessary) functionality using API product;
- Edit the component template according to the tasks at hand.
- Clear the cache of the visual editor. As a result, the visual editor will display the customized component.
Note: Cache of the visual editor is updated in the tab
Components:
Important! While customizing a component, please remember that all of the keys in $MESS containing the name, description, and parameters of a component and also the identifiers of component branches in the component tree of the visual editor must be unique throughout the entire product.
It is preferable to refrain from component customization unless it is really necessary. In this case:
- No updates are lost;
- It is easier to solve problems through helpdesk (helpdesk does not solve the problems occurring in operation of a customized code unless an error in API is clearly identified);
- AJAX is implemented in composite components as a standard.
Simple Example of a Component Customization
If there are too many elements, the component news.list may significantly slow down the generation of a page. The component must be optimized. You may consider deleting the link to the detailed news text in the preview description as one of the optimization options (a link with the name of the news will remain).
- Copy the component to the namespace (/bitrix/components/my_components/).
- Delete the following lines in the code of the component copied (/bitrix/components/my_components/news.list/component.php):
"DETAIL_TEXT",
"DETAIL_TEXT_TYPE",
and
if($bGetProperty)
$arSelect[]="PROPERTY_*";
- Save the changes made.
- Apply your own component instead of the standard on.
As a result, there will be more database queries but the page will be formed faster.
Modifying a Simple Component as a Part of a Composite One
When working with a composite component, one or more simple components may be modified, leaving the remaining simple components unchanged.
For example, if you want to increase the length of a group description to be displayed from 50 to 100 characters in the component socialnetwork.user_groups, which forms part of the composite component socialnetwork_group and displays a list of groups, then perform the following.
- Copy the template of the composite component.
Now, the template of the composite component is located in the website template. If we go there, we will see lots of files in the folder /bitrix/templates/<website template>/components/bitrix/socialnetwork_group/.default
.
Each file is invoked on a specific page of the social network and connects the simple components that are required.
Now, the file that connects this component must be found and changed. In this case, the file is index.php.
The rest of the files in the template of the composite component located in the website template can be deleted. The composite component will connect these files from the core. This means that they will be updated.
- Now in the remaining file, we replace
$APPLICATION->IncludeComponent(
"bitrix:socialnetwork.user_groups",
with
$APPLICATION->IncludeComponent(
"custom:socialnetwork.user_groups",
- Copy the folder
/bitrix/components/bitrix/socialnetwork.user_groups
to /bitrix/components/custom/socialnetwork.user_groups
.
- In the file
/bitrix/components/custom/socialnetwork.user_groups/component.php
replace
"GROUP_DESCRIPTION" => SubStr($arGroups["GROUP_DESCRIPTION"], 0, 50)."...",
with
"GROUP_DESCRIPTION" => SubStr($arGroups["GROUP_DESCRIPTION"], 0, 100)."...",
Now, all of the functional capacity of the social network remains standard, except for the component socialnetwork.user_groups.
Creating Components
As a matter of fact, today own component may have to be written only when an absolutely new functionality for the website is needed. Since the set of standard components is quite extensive, in most cases just expanding the functionality of the already available components will suffice and there is no need to write new components.
However, sooner or later a developer must learn to create their own components.
Standard Sequence of Operations
Component Creation Procedure
Make the required php code into a separate file in order to use it later as an invoked file. However, the component must also be connected to the system using a description file identifiable by the core of Bitrix Framework. As a result, a user can see the icon with the component name in the visual editor and can set up the component properties.
Remember that a component consists of a php code with terminated functionality made into a separate file, a file of component registration in the system and its parameter descriptions, and also localization files.
- Component registration
- Detachment of a required php code into a separate file.
- Create the description file .description.php
- Locate files in a folder in the own namespace.
- Setting up parameters in the component code
- Localization
- Prepare files with text constants for the component and registration file:
/lang/en/<component_name>/<component_name>.php
and /lang/en/<component_name>/.description.php
- make changes in the code of both files of the component in order to use these constants (the localization file is connected using the function IncludeTemplateLangFile).
Attention! All keys in $MESS containing the name, description, and parameters of the component and also the identifiers of component branches in the component tree of the visual editor must be unique for the entire product.
Additional Methods
Additional Methods Available in Components and Templates
Additional methods from the class CComponentEngine can be used in components and templates.
string CComponentEngine::MakePathFromTemplate($pageTemplate, $arParams);
where::
$pageTemplate - a template of the type /catalog/#IBLOCK_ID#/section/#SECTION_ID#.php or catalog.php?BID=#IBLOCK_ID#&SID=#SECTION_ID#,
$arParams - an associative array of reparametrization where the parameter name is the key and the parameter value is the value. It returns a path based on the path template $pageTemplate and reparametrization.
Example:
$url = CComponentEngine::MakePathFromTemplate
("/catalog/#IBLOCK_ID#/section/#SECTION_ID#.php",
array(
"IBLOCK_ID" => 21,
"SECTION_ID" => 452
)
);
Organizing an Explicit Connection among the Components on the One Page of a Composite Component
Explicit connection among the components can be organized through return values and incoming parameters of these components.
If data from the component comp1 must be transferred to the component comp2, in the end of the component code comp1 must be written: return data;
The component comp1 must be connected as follows:
$result = $APPLICATION->IncludeComponent(comp1, ...);
Now the data are located in the variable $result , and they can be transferred as incoming parameters into another component comp2.
Redefinition of Incoming Variables
Each component has a set of variables in which it receives codes or other attributes of requested data from the outside. For example, the component bitrix:catalog.section has variables IBLOCK_ID and SECTION_ID in which it receives and processes codes of the catalog and the product group, accordingly.
All components that form part of a composite component must have a single set of variables. For example, the composite component bitrix:catalog and all simple components (bitrix:catalog.list, bitrix:catalog.section etc.), under its control work with the variables IBLOCK_ID, SECTION_ID, ELEMENT_ID, and others.
If the developer wants to redefine the component variables when placing a composite component on a page, the developer must set up the parameter VARIABLE_ALIASES among the incoming parameters of the component.
When connecting a component in the SEF mode, this parameter must look as follows:
"VARIABLE_ALIASES" => array(
"list" => array(),
"section" => array(
"IBLOCK_ID" => "BID",
"SECTION_ID" => "ID"
),
"element" => array(
"SECTION_ID" => "SID",
"ELEMENT_ID" => "ID"
),
)
Here, array codes are consistent with the codes in the path template array. For each path, their own redefinitions of variables can be set up.
When connecting a component not in the SEF mode, this parameter must be:
"VARIABLE_ALIASES" => array(
"IBLOCK_ID" => "BID",
"SECTION_ID" => "GID",
"ELEMENT_ID" => "ID",
)
Example No. 1:
Let us assume that the component bitrix:catalog connected in the file /fld/cat.php must work with the paths:
/catalog/index.php
– for a list of catalogs,
/catalog/section/group_code.php?ID=catalogue_code
– for a group of goods,
/catalog/element/goods_code.php?ID=group_code
– for detailed information about an item of goods.
The following parameters must be set up in the incoming parameters for component connection:
"SEF_MODE" => "Y",
"SEF_FOLDER" => "/catalog/",
"SEF_URL_TEMPLATES" => array(
"list" => "index.php",
"section" => "section/#SECTION_ID#.php?ID=#IBLOCK_ID#",
"element" => "element/#ELEMENT_ID#.php?ID=#SECTION_ID#"
),
"VARIABLE_ALIASES" => array(
"list" => array(),
"section" => array(
"IBLOCK_ID" => "ID"),
"element" => array(
"SECTION_ID" => "ID",),
Example No. 2:
Let us assume that the component bitrix:catalog connected in the file /fld/cat.php
must work with the paths
/fld/cat.php
– for a list of catalogs,
/fld/cat.php?BID=catalogue_code&SID=group_code
– for a group of goods,
/fld/cat.php?ID=goods_code&SID=group_code
– for detailed information about an item of goods.
The following parameters must be set up in the incoming parameters for a component connection:
"SEF_MODE" => "N",
"VARIABLE_ALIASES" => array(
"IBLOCK_ID" => "BID",
"SECTION_ID" => "SID",
"ELEMENT_ID" => "ID",
),
User-Defined Templating Engines
Components can work with any templating engines that can be connected from PHP. In order to add a new templating motor onto a website it is necessary to determine (or expand) the global variable $arCustomTemplateEngines in the file /bitrix/php_interface/init.php
. This variable contains an associative array where each element is similar to:
"templator_code" => array(
"templateExt" => array("extension1"[, "extension2"...]),
"function" => "motor_connection_function_name"
)
where:
- templator_code - an arbitrary word that is unique for the website,
- extensionN - the extension of the file that must be processed by this templating motor,
- motor_connection_function_name - the name of the function that can be invoked if the component template has the indicated extension. The function can be located in the same file
/bitrix/php_interface/init.php
.
For example, if the use of Smarty is required on the website in addition to the standard templating motor (PHP), the following code must be added to the file /bitrix/php_interface/init.php
:
global $arCustomTemplateEngines;
$arCustomTemplateEngines = array(
"smarty" => array(
"templateExt" => array("tpl"),
"function" => "SmartyEngine"
),
);
As a result, when a .tpl template is connected, the function SmartyEngine will start up instead of the standard motor PHP. SmartyEngine must connect theSmarty motor.
The syntaxis of motor connection functions is as follows:
function motor_connection_function_name ($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
where:
- $templateFile – path to the template file from the website root,
- $arResult – array of results of the component operation,
- $arParams – array of incoming parameters of the component,
- $arLangMessages – array of language messages (translations) of the template,
- $templateFolder – path to the template folder from the website root (if the template is not located in the folder, this variable is empty),
- $parentTemplateFolder - path from the website root to the composite component folder as a part of which this component is connected (if the component is connected independently, this variable is empty),
-
$template – template object.
The code of the templating motor connection function depends on the motor to be connected.
Complete Example of Smarty Motor Connection Smarty
The following code must be added to the file /bitrix/php_interface/init.php
:
global $arCustomTemplateEngines;
$arCustomTemplateEngines = array(
"smarty" => array(
"templateExt" => array("tpl"),
"function" => "SmartyEngine"
)
);
function SmartyEngine($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
{
if (!defined("SMARTY_DIR"))
define("SMARTY_DIR", "/libs/");
require_once('/libs/Smarty.class.php');
$smarty = new Smarty;
$smarty->compile_dir = "/templates_c/";
$smarty->config_dir = "/configs/";
$smarty->template_dir = "/templates/";
$smarty->cache_dir = "/cache/";
$smarty->compile_check = true;
$smarty->debugging = false;
$smarty->assign("arResult", $arResult);
$smarty->assign("arParams", $arParams);
$smarty->assign("MESS", $arLangMessages);
$smarty->assign("templateFolder", $templateFolder);
$smarty->assign("parentTemplateFolder", $parentTemplateFolder);
$smarty->display($_SERVER["DOCUMENT_ROOT"].$templateFile);
}
The line <absolute path to Smarty motor> must be replaced everywhere with the absolute path to Smarty motor. More detailed information about the installation of the motor on a website is provided in the Smarty help system.
In the sample code, the Smarty motor is registered in the array $arCustomTemplateEngines. Parameters of the motor are initialized in the SmartyEngine function in accordance with system requirements (see the Smarty documentation). Then, the variables of component operation results, incoming parameters, language messages, etc. are transferred to Smarty. And, in the end, the Smarty template processing and displaying method is invoked.
Complete Example of XML/XSLT Motor Connection
The following code must be added to the fileл /bitrix/php_interface/init.php
:
global $arCustomTemplateEngines;
$arCustomTemplateEngines = array(
"xslt" => array(
"templateExt" => array("xsl"),
"function" => "XSLTEngine"
),
);
function CreateXMLFromArray($xDoc, $xNode, $ar)
{
foreach($ar as $key=>$val)
{
if(!is_string($key) || strlen($key)<=0)
$key = "value";
$xElement = $xDoc->createElement($key);
if(is_array($val))
{
CreateXMLFromArray($xDoc, $xElement, $val);
}
else
{
$xElement->appendChild($xDoc->createTextNode(iconv(SITE_CHARSET, "utf-8", $val)));
}
$xNode->appendChild($xElement);
}
return $xNode;
}
function XSLTEngine($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
{
$arResult["PARAMS"] = array(
"templateFolder" => $templateFolder,
"parentTemplateFolder" => $parentTemplateFolder,
"arParams" => $arParams,
"arLangMessages" => $arLangMessages
);
$xDoc = new DOMDocument("1.0", SITE_CHARSET);
$xRoot = $xDoc->createElement('result');
CreateXMLFromArray($xDoc, $xRoot, $arResult);
$xDoc->appendChild($xRoot);
$xXsl = new DOMDocument();
$xXsl->load($_SERVER["DOCUMENT_ROOT"].$templateFile);
$xProc = new XSLTProcessor;
$xProc->importStyleSheet($xXsl);
echo $xProc->transformToXML($xDoc);
}
Operation of a Composite Component in SEF Mode
The SEF generating function is embedded in the composite components. These components always have an input parameter SEF_MODE that admits the values Y and N. If SEF_MODE is equal to N the component works with physical links and transfers all of the parameters through the standard parameters of HTTP query. For example:
/fld/cat.php?IBLOCK_ID=12&SECTION_ID=371
If the SEF_MODE parameter is equal to Y then the component generates and processes links based on templates. For example, the component can “understand” and process the link:
/catalog/section/371.php?IBLOCK_ID=12
even if the component itself is located in the file /fld/cat.php
.
If the SEF_MODE parameter is equal to Y, the component must also have the SEF_FOLDER, parameter that must contain a path to the folder with which the component works. This path may either coincide with the physical path or not. For example, the parameters SEF_MODE = Y and SEF_FOLDER=/catalog/ may be set up in the component bitrix:catalog connected in the file /fld/cat.php
. In this case, the component will respond to queries using the path /catalog/.... By default, the editor must set up the current physical path to the editable file.
A composite component that can work in an SEF mode must have a set of path templates by default. For example, in the composite component bitrix:catalog the following array can be defined:
$arDefaultUrlTemplatesSEF = array(
"list" => "index.php",
"section" => "section.php?IBLOCK_ID=#IBLOCK_ID#&SECTION_ID=#SECTION_ID#",
"element" => "element.php?ELEMENT_ID=#ELEMENT_ID#"
);
These path templates can be redefined using the input parameter of the composite component SEF_URL_TEMPLATES that contains a new array of all path templates or a part of them.
When saving a page with a component operating in an SEF mode, the editor creates or updates an entry in the urlrewrite system. For example, when saving the file /fld/cat.php
containing the component bitrix:catalog switched to an SEF mode with the parameter SEF_FOLDER=/catalog/, the urlrewrite system creates or updates an entry similar to:
array(
"CONDITION" => "#^/catalog/#",
"ID" => "bitrix:catalog",
"PATH" => "/fld/cat.php"
),
- The value of the parameter SEF_FOLDER is written in CONDITION between the symbols #^ and #;
- The name of the component is written in ID;
- The physical path to the file saved is written in PATH.
If an entry with such PATH and ID already exists, it gets updated and, if not, it is added.
In run-time, upon receiving a query for a physically nonexistent page, the urlrewrite mechanism searches for the appropriate entry according to CONDITION and transfers control to the page PATH.
The component located on the PATH page identifies the requested page based on path templates and recovers variables hidden in the path.
Attention! The mandatory requirement for the set of path templates for this component is the uniqueness of each path template, except parameters and variables. It must be verified when saving the page in the visual editor.
For example, a set of path templates:
"section" => "section/#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#",
"element" => "element/#ELEMENT_ID#.php"
is acceptable, and a set of path templates:
"section" => "#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#",
"element" => "#ELEMENT_ID#.php"
is not acceptable.
Ways of Data Transmission among Components
Ways of Data Transmission among Components:
- Global variables. For example:
$GLOBALS['mycomponent_variable'] = $arResult["ID"];
In addition to GLOBALS you can also use $_SESSION provided that:
- The volume of data is not big;
- Immediately after transmission, the data will be deleted from $_SESSION, otherwise, they will be “alive” so long as the session is active.
- Wrapper class, for example:
Class GarbageStorage{
private static $storage = array();
public static function set($name, $value){ self::$storage[$name] = $value;}
public static function get($name){ return self::$storage[$name];}
}
Accordingly, the use:
\GarbageStorage::set('MyCustomID', $arResult["ID"]); #set the value
\GarbageStorage::get('MyCustomID'); #obtain the value
Choose the way depending on the components, on what, exactly, you want to transmit to another component, and whether or not there are necessary data available in non-cached files (speaking of component_epilog.php). The use of the wrapper class is more difficult but far more correct.
A Simple Example of a Component Creation
As an example, we will create a component which displays current date and time. In this case, the date and time format is set in the component settings. In real life, creating such a component makes no sense, but we do it here in order to understand how a component is developed. The procedure is similar for more complex cases.
Preliminary Actions
Preparing php Code of the Component
The first thing we have to do is write a php code that performs what we need.
<?
echo date('Y-m-d');
?>
However, this code just displays the date and there is no option to choose another format. We’d better put the data display format into the variable:
<?
$format = 'Y-m-d';
echo date($format);
?>
And, as a final touch, we have to separate logics and representation:
<?
// parameters
$format = 'Y-m-d';
// logics
$d = date($format);
// representation
echo $d;
?>
Creating a Structure of Component Folders and Files
Now we have to create an own namespace, for example: dv. To do this, we have to create a folder /bitrix/components/dv
. Inside, we create a component folder — date.current. And inside this folder, in its turn, we create two mandatory files and a folder to store templates titled templates. The folder templates must contain the folder .default with the file template.php inside.
We obtain the following structure in the folder /bitrix/components/dv/date.current
:
- component.php
- .description.php
templates/.default/template.php
Implementing the Component without Input Parameters
For now, we create the component without the option to set up the input parameter – data format.
File contents:
- component.php
<? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
$arResult['DATE'] = date('Y-m-d');
$this->IncludeComponentTemplate();
?>
- .description.php
<? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die(); $arComponentDescription = array(
"NAME" => GetMessage(“Current date”),
“DESCRIPTION” => GetMessage(“Display current date”),
);
?>
templates/.default/template.php
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
As you might have noted, each component file contains the string if (!defined(“B_PROLOG_INCLUDED”) || B_PROLOG_INCLUDED!==true) die();
in the beginning. This is required so that these files cannot be invoked directly from the browser window.
The component in its simplest form is ready. It can be invoked in page codes using the structure:
<? $APPLICATION->IncludeComponent(
“dv:date.current”,
“.default”,
Array(
),
false
);?>
Implementing the Component with Input Parameters
Now, let us make it possible to add the component to a page from the visual editor and set up the date format through component parameters.
In order to make our component appear in the visual editor we have to expand the component description file.
.description.php:
<? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
$arComponentDescription = array(
"NAME" => GetMessage("Current date"),
"DESCRIPTION" => GetMessage(“Display current date"),
"PATH" => array(
"ID" => "dv_components",
"CHILD" => array(
"ID" => "curdate",
"NAME" => "Current date"
)
),
"ICON" => "/images/icon.gif",
);
?>
We have added the PATH array description element in order to place the component in the component tree. Thus, our component will be shown in a separate folder. Alternatively, a component icon may be set, and it will be shown in the tree and in the visual editor.
Let us take a closer look at the component settings. Assuming that the date template option will be set by a string, we create the file .parameters.php as follows:
<? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
$arComponentParameters = array(
"GROUPS" => array(),
“PARAMETERS” => array(
“TEMPLATE_FOR_DATE” => array(
“PARENT” => “BASE”,
“NAME” => “Template for date”,
“TYPE” => “STRING”,
“MULTIPLE” => “N”,
“DEFAULT” => “Y-m-d”,
“REFRESH” => “Y”,
),
),
);
?>
And change the file containing the component logics so that it could use the parameter we set in component.php:
<? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
$arResult['DATE'] = date($arParams["TEMPLATE_FOR_DATE"]);
$this->IncludeComponentTemplate();
?>
So, What Have We Done?
We have created a component in its simplest. We have not taken into account multiple languages, the option to create help for the component, and the option of component caching.
The majority of custom components for Bitrix Framework are created by changing the components supplied with the products. That is why it is very important to review standard components before starting programming new ones. The task you are to work on most likely has already been resolved by the developers of Bitrix, Inc.
Component Creation Example
Let us consider an example of a component creation for messages to the administrator about an error.
Using this component we can implement a functionality that would permit users to notify content managers about errors found on a website. The error will be sent as an email notice. User’s work algorithm with the component is very simple: on seeing an error on the website, the user highlights the text, presses Ctrl+Enter , and obtains the form:
The user has to complete the form, and the message is sent to a responsible person.
Creating a Mail Template
Since an error notice will be sent by email, a new email template must be created.
- Go to the page Settings > System settings > Email Events > Email event types.
- Complete the form:
- Go to the page Settings > System settings > Email Events >E-Mail templates.
- Click Add template on the context panel to open template setup form.
- Set up a template for the email event created:
Component Creation
Create a folder feedback.error in an own namespace with the following structure:
- folder images
- folder templates
- folder .default
- file script.js
- file template.php
- file .description.php
- file .parameters.php
- file component.php
The file feedback.gif is the icon that will be shown in the visual editor.
The code of the script.js file is as follows:
BX.bind(document, "keypress", SendError);
function SendError(event, formElem)
{
event = event || window.event;
if((event.ctrlKey) && ((event.keyCode == 0xA)||(event.keyCode == 0xD)))
{
var Dialog = new BX.CDialog({
title: "An error is found on the website!!",
head: "Error description",
content: '<form method="POST" id="help_form">\
<textarea name="error_desc" style="height: 78px; width: 374px;"></textarea>\
<input type="hidden" name="error_message"value="'+getSelectedText()+'">\
<input type="hidden" name="error_url" value="'+window.location+'">\
<input type="hidden" name="error_referer" value="'+document.referrer+'">\
<input type="hidden" name="error_useragent" value="'+navigator.userAgent+'">\
<input type="hidden" name="sessid" value="'+BX.bitrix_sessid()+'"></form>',
resizable: false,
height: '198',
width: '400'});
Dialog.SetButtons([
{
'title': 'Send',
'id': 'action_send',
'name': 'action_send',
'action': function(){
BX.ajax.submit(BX("help_form"));
this.parentWindow.Close();
}
},
{
'title': 'Cancel',
'id': 'cancel',
'name': 'cancel',
'action': function(){
this.parentWindow.Close();
}
}
]);
Dialog.Show();
}
}
function getSelectedText(){
if (window.getSelection){
txt = window.getSelection();
}
else if (document.getSelection) {
txt = document.getSelection();
}
else if (document.selection){
txt = document.selection.createRange().text;
}
else return;
return txt;
}
The code of the template.php file is as follows:
<?
CUtil::InitJSCore(array('window', 'ajax'));
?>
The code of the .description.php file is as follows:
<?
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
$arComponentDescription = array(
"NAME" => "Send Error",
"DESCRIPTION" => "Send Error",
"ICON" => "/images/feedback.gif",
"PATH" => array(
"ID" => "utility",
),
);
?>
The code of the .parameters.php file is as follows:
<?
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
$arComponentParameters = array();
?>
The code of the component.php file is as follows:
<?
if (check_bitrix_sessid() && $_SERVER['REQUEST_METHOD'] == "POST" && !empty($_REQUEST["error_message"]) && !empty($_REQUEST["error_url"]))
{
$arMailFields = Array();
$arMailFields["ERROR_MESSAGE"] = trim ($_REQUEST["error_message"]);
$arMailFields["ERROR_DESCRIPTION"] = trim ($_REQUEST["error_url"]);
$arMailFields["ERROR_URL"] = $_REQUEST["error_desc"];
$arMailFields["ERROR_REFERER"] = $_REQUEST["error_referer"];
$arMailFields["ERROR_USERAGENT"] = $_REQUEST["error_useragent"];
CEvent::Send("BX", SITE_ID, $arMailFields);
}
$this->IncludeComponentTemplate();
?>
Now let us take a closer look at the contents of the file feedback.error\templates\.default\script.js
which is of interest for developers.
Declaration of the handler function that is displayed onto the <body>
:
function SendError(event, formElem)
Ctrl+Enter wait:
if((event.ctrlKey) && ((event.keyCode == 0xA)||(event.keyCode == 0xD)))
Setting up the parameters of a new window and its contents:
var Dialog = new BX.CDialog({
title: "An error is found on the website!!",
head: "Error description",
content: '<form method="POST" id="help_form" action="/bitrix/templates/.default/send_error.php">\
<textarea name="error_desc" style="height: 78px; width: 374px;"></textarea>\
<input type="hidden" name="error_message"value="'+getSelectedText()+'">\
<input type="hidden" name="error_url" value="'+window.location+'">\
<input type="hidden" name="sessid" value="'+BX.bitrix_sessid()+'"></form>',
resizable: false,
height: '198',
width: '400'});
Determining a set of buttons:
Dialog.SetButtons([
{
'title': 'Send',
'id': 'action_send',
'name': 'action_send',
'action': function(){
BX.ajax.submit(BX("help_form"));
this.parentWindow.Close();
}
},
{
'title': 'Cancel',
'id': 'cancel',
'name': 'cancel',
'action': function(){
this.parentWindow.Close();
}
},
]);
Window opening:
Dialog.Show();
The function getSelectedText() receives the text marked with the mouse. And then the letter is sent in the text of the file component.php:
if (check_bitrix_sessid() && $_SERVER['REQUEST_METHOD'] == "POST" && !empty($_REQUEST["error_message"]) && !empty($_REQUEST["error_url"]))
{
$arMailFields = Array();
$arMailFields["ERROR_MESSAGE"] = trim ($_REQUEST["error_message"]);
$arMailFields["ERROR_DESCRIPTION"] = trim ($_REQUEST["error_desc"]);
$arMailFields["ERROR_URL"] = trim ($_REQUEST["error_url"]);
CEvent::Send("BX", SITE_ID, $arMailFields);
};
TinyMCE Visual Editor Integration Component
Let us create a component integrating the popular editor TinyMCE into Bitrix Framework.
Download the latest version of the editor from the manufacturer’s website.
In an own namespace, set up a structure of folders and files:
/bitrix/components/tools/
;
/bitrix/components/tools/editor.tiny.mce/
;
/bitrix/components/tools/editor.tiny.mce/templates/
;
/bitrix/components/tools/editor.tiny.mce/templates/.default/
;
/bitrix/components/tools/editor.tiny.mce/tiny_mce/
- a folder for editor installation package;
- component.php — component logics;
- .parameters.php — a file to describe input parameters.
Copy the downloaded installation package into the folder /tiny_mce
.
Add the following code in the file component.php:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
$APPLICATION->AddHeadScript($this->__path .'/tiny_mce/tiny_mce.js');
$sNameTextAria = (isset($arParams['TEXTARIA_NAME']) == false) ? 'content' : $arParams['TEXTARIA_NAME'];
$sIdTextAria = (isset($arParams['TEXTARIA_ID']) == false) ? '' : $arParams['TEXTARIA_ID'];
if ('' == trim($sIdTextAria))
$sIdTextAria = 'content';
$sEditorID = (isset($arParams['INIT_ID']) == false) ? 'textareas' : $arParams['INIT_ID'];
$iTextariaWidth = (isset($arParams['TEXTARIA_WIDTH']) == false) ? '100%' : $arParams['TEXTARIA_WIDTH'];
$iTextariaHeight = (isset($arParams['TEXTARIA_HEIGHT']) == false) ? '300' : $arParams['TEXTARIA_HEIGHT'];
$sText = (isset($arParams['TEXT']) == false) ? '' : $arParams['TEXT'];
?>
<script type="text/javascript">
<?
if($arParams['TYPE_EDITOR'] == 'TYPE_1')
{
?>
tinyMCE.init(
{
language : 'ru',
mode : "textareas",
//elements : "<?=$sEditorID?>",
editor_selector : "<?=$sEditorID?>",
theme : "advanced",
plugins : "safari, spellchecker, upload.images.komka, wordcount, fullscreen",
theme_advanced_buttons1 : "formatselect,fontselect,fontsizeselect,bold,italic,underline,link,justifyleft,justifycenter,
justifyright,pasteword,pastetext,images,|,bullist,numlist,|,undo,redo,|,spellchecker,fullscreen",
theme_advanced_buttons2 : "",
theme_advanced_buttons3 : "",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_statusbar_location : "bottom",
theme_advanced_resizing : false,
content_css : "<?=$this->__path?>/example.css",
height : "<?=$iTextariaHeight?>",
spellchecker_languages : '+Русский=ru,English=en',
spellchecker_word_separator_chars : '\\s!\"#$%&()*+,-./:;<=>?@[\]^_{|}'
}
);
<?
}
elseif($arParams['TYPE_EDITOR'] == 'TYPE_2')
{
?>
tinyMCE.init({
language : 'ru',
mode : "textareas",
editor_selector : "<?=$sEditorID?>",
theme : "advanced",
plugins : "safari,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,
iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,
fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,imagemanager,filemanager",
theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,
justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect",
theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,
numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,
insertdate,inserttime,preview,|,forecolor,backcolor",
theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,
emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,
spellchecker,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,blockquote,
pagebreak,|,insertfile,insertimage",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_statusbar_location : "bottom",
theme_advanced_resizing : true,
content_css : "<?=$this->__path?>/example.css",
height : "<?=$iTextariaHeight?>",
template_external_list_url : "js/template_list.js",
external_link_list_url : "js/link_list.js",
external_image_list_url : "js/image_list.js",
media_external_list_url : "js/media_list.js",
template_replace_values : {username : "Some User", staffid : "991234"}
}
);
<?
}
?>
</script>
<textarea id="<?=$sIdTextAria?>" class="<?=$sEditorID?>" name="<?=$sNameTextAria?>" style="width:<?=$iTextariaWidth?>"><?=$sText?></textarea>
<? $this->IncludeComponentTemplate();?>
The file .parameters.php
<? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
$arComponentParameters = array(
"PARAMETERS" => array(
"TYPE_EDITOR" => Array(
"PARENT" => "SETTINGS",
"NAME" => "Editor mode",
"TYPE" => "LIST",
"VALUES" => array('TYPE_1' => 'Simplified editor', 'TYPE_2' => 'Full editor'),
),
'INIT_ID' => array(
"PARENT" => "SETTINGS",
"NAME" => "Editor ID (unique)",
"TYPE" => "STRING",
"DEFAULT" => '',
),
'TEXT' => array(
"PARENT" => "SETTINGS",
"NAME" => "Content to be inserted to the editor",
"TYPE" => "STRING",
"DEFAULT" => $_POST['content'],
),
'TEXTARIA_NAME' => array(
"PARENT" => "SETTINGS",
"NAME" => "TEXTARIA field name",
"TYPE" => "STRING",
"DEFAULT" => 'content',
),
'TEXTARIA_ID' => array(
"PARENT" => "SETTINGS",
"NAME" => "TEXTARIA ID field",
"TYPE" => "STRING",
"DEFAULT" => 'content',
),
'TEXTARIA_WIDTH' => array(
"PARENT" => "SETTINGS",
"NAME" => "Editor width",
"TYPE" => "STRING",
"DEFAULT" => '100%',
),
'TEXTARIA_HEIGHT' => array(
"PARENT" => "SETTINGS",
"NAME" => "Editor height",
"TYPE" => "STRING",
"DEFAULT" => '300',
),
)
);
/*
array(
'TEXT' => $_POST['content'], # contents, in html
'TEXTARIA_NAME' => 'content', # field name
'TEXTARIA_ID' => 'content', # field ID
'INIT_ID' => 'textareas', # editor ID
'TEXTARIA_WIDTH' => '100%',
'TEXTARIA_HEIGHT' => '300'
)
*/
?>
Several important points in the code of the component.php must be clarified. Connection of the editor proper:
<? $APPLICATION->AddHeadScript($this->__path .’/tiny_mce/tiny_mce.js’); ?>
Connection of styles put separately in the component folder for convenience; this setting is made in js in the initialization code:
content_css : ‘<?=$this->__path?>/example.css’,
Attention! If there are two or more editors on a page we have to identify them by the class name editor_selector : ‘<?=$sEditorID?>’
.
The text area proper for which all the work is being done:
<textarea id=’<?=$sIdTextAria?>’ name=’<?=$sNameTextAria?>’ style=’width:<?=$iTextariaWidth?>’><?=$sText?></textarea>
How to Use
We get connected as follows:
<? echo $_POST['content'] ?>
<? echo $_POST['content2'] ?>
<form action="" method="post" name="">
<? $APPLICATION->IncludeComponent("tools:editor.tiny.mce", ".default", array(
"TEXT" => $_POST["content"], // contents from the request which must be inserted
"TEXTARIA_NAME" => "content", // field name
"TEXTARIA_ID" => "content", // field id
"TEXTARIA_WIDTH" => "100%", // width
"TEXTARIA_HEIGHT" => "300", // height
"INIT_ID" => "ID" // ID of the proper editor
),
false
);
?>
<input value="submit" name="sub" type="submit" />
</form>
Caching in own components
Note: In Bitrix Framework caching time is recorded in seconds.
What is the use of caching in own components
Making direct database queries through API, obtaining information, formatting it in the component template, and displaying it to the user may seem to be the simplest solution.
However, the issue is the performance of the web project in case many users work with it at the same time. If it takes the component 0.1 sec. to respond without cache and executing, say, 100 database queries, then if 100 users work simultaneously the database server load will increase, and so will the component response time up to, for example, 5-10 sec.
An equally important point to keep in mind is the speed of response of a component when receiving data from cache. If it takes the component 2 sec. to respond to each user without cache, then, with cache, it will take the component 2 sec. to respond to one user and 0.1 sec. – to the remaining 100 users over the next, say, 30 minutes.
When using cache in own components 2.0:
- Web project performance and load tolerance drastically increase because the database load drops to a minimum, and the web solution will be able to serve, for example, not just 50,000 users per day but 1,000,000 and more.
- Web pages are loaded to the user’s browser much faster (in tenths of a second) because their structural information is saved on the server and is not taken from the database.
Caching Time
The time required for caching depends on the type of caching. If the caching Auto+Management is used, the information will be supplied from cache until it is changed in the database and cache resets automatically. The caching time for this mode must be long, e.g., 1 year.
If Auto caching is used, it is recommended to set up the longest cache interval permitted with due regard to business logic. The caching time for this mode depends on the frequency of the information update. For some components, the time must be set to 24 hours. For frequently updated components, a controlled cache is recommended; alternatively, a value of, for example, 10 minutes shall be set.
Embedded Cache Support
2.0 components come with an embedded support of a typical cache algorithm. The component structure using embedded cache support will be similar to:
// Verification and initialization of input parameters
if ($arParams["ID"] <= 0)
$arParams["ID"] = 10;
// If no valid cache is available (i.e. data must be requested
// and valid cache must be created)
if ($this->StartResultCache())
{
// Data query and completion of $arResult
$arResult = array(
"ID" => rand(1, 100)
);
for ($i = 0; $i < 5; $i++)
$arResult["FIELDS"][] = rand(1, 100);
// If any condition is met, there is no need
// to cache data
if ($arParams["ID"] < 10)
$this->AbortResultCache();
// Connect output template
$this->IncludeComponentTemplate();
}
// Set up page header using deferred
// function
$APPLICATION->SetTitle($arResult["ID"]);
Comments to the Code
The method CBitrixComponent::StartResultCache()has the following description: bool $this->StartResultCache($cacheTime = False, $additionalCacheID = False, $cachePath = False)
where:
- $cacheTime - caching time (if False -
IntVal($arParams["CACHE_TIME"])
is used for substitution);
- $additionalCacheID - additional parameters on which cache depends, apart from the current website’s SITE_ID,component name, file path, and input parameters;
- $cachePath - path to cache file (if False -
"/".SITE_ID.
is used for substitution).
If a valid cache is available, the method displays its contents, completes $arResult, and returns False. If there is no valid cache, the method returns True.
If cache depends not only on the website, input parameters, component name, and a path to the current website, but also on other parameters, these parameters must be sent to the method as a second parameter in the form of a string. For example, if cache also depends on the user groups to which a current visitor belongs, the condition should be written as follows:
if ($this->StartResultCache(false, $USER->GetGroups()))
{
// There is no valid cache. We have to choose data from the base to $arResult
}
If following data selection (if no valid cache is available), it becomes evident that there is no need to cache data, the method
$this->AbortResultCache();
must be invoked. E.g., if it turns out that there is no news with such ID, caching should be interrupted by displaying a message that there is no such news. If caching is not interrupted, some intruders may clog up with cache all disc space allocated to the website by invoking the page with arbitrary (including non-existent) IDs.
The method $this->IncludeComponentTemplate();
connects the component template and saves to the cache file the output and array of results $arResult. All changes to $arResult and output will not be saved to cache after the template connection method is invoked.
If during the component code execution we have not entered into the body of the condition if ($this->StartResultCache())
, it means that there is a valid cache for this component, page, and input parameters. After this method is invoked, HTML from cache is displayed and we have a completed array $arResult. Here, we can do something. For example, set up the page header using deferred functions.
If during the execution of certain conditions the component cache must be cleared (e.g., the component “knows” that the data have been changed), the method $this->ClearResultCache($additionalCacheID = False, $cachePath = False)
can be used. Parameters of this method are consistent with the same-name parameters of the StartResultCache method.
Complex Caching
If the component requires any special caching that cannot be executed using embedded cache support, the standard class CPHPCache can be used. The component structure using the class CPHPCache will be more or less as follows:
// Verification and initialization of the input parameters
if ($arParams["ID"] <= 0)
$arParams["ID"] = 10;
$arParams["CACHE_TIME"] = IntVal($arParams["CACHE_TIME"]);
$CACHE_ID = SITE_ID."|".$APPLICATION->GetCurPage()."|";
// Cache only depends on prepared parameters without "~"
foreach ($this->arParams as $k => $v)
if (strncmp("~", $k, 1))
$CACHE_ID .= ",".$k."=".$v;
$CACHE_ID .= "|".$USER->GetGroups();
$cache = new CPageCache;
if ($cache->StartDataCache($arParams["CACHE_TIME"], $CACHE_ID, "/".SITE_ID.$this->GetRelativePath()))
{
// Request of data and formation of the array $arResult
$arResult = array("a" => 1, "b" => 2);
// Component template connection
$this->IncludeComponentTemplate();
$templateCachedData = $this->GetTemplateCachedData();
$cache->EndDataCache(
array(
"arResult" => $arResult,
"templateCachedData" => $templateCachedData
)
);
}
else
{
extract($cache->GetVars());
$this->SetTemplateCachedData($templateCachedData);
}
Comments to the Code
Cache must only depend on the prepared parameters, i.e. on the parameters that are properly initialized and reduced to a required type (e.g., using IntVal()) etc. The array $arParams contains both prepared parameters and initial parameters (with the same key but with the prefix "~"). If the cache depends on unprepared parameters, intruders will be able to clog up all the disk space allocated to the website with cache by calling a page with IDs equal to "8a", "8b", ... (which give 8 after IntVal()).
The method $this->IncludeComponentTemplate()
does not request data from the database. However, it is better to also include it into the cached area because this method performs certain disk operations.
Before calling the method for cache completion and cache saving (the method EndDataCache), it is necessary to request parameters from the template. These parameters must be used even if the template itself is not connected and data are taken from cache. In the current version, such parameters include css styles of the template that are connected by deferred functions and thus do not go to cache. The structure of the data returned by the template is not documented and has no meaning for the component. These are just data that must be placed to cache and then taken from cache and returned to the template.
In order to return to the template, the data previously saved in cache according to the template’s “wish”, the methods $this->SetTemplateCachedData($templateCachedData);
or CBitrixComponentTemplate::ApplyCachedData($templateCachedData);
can be used. One of these methods must be invoked in the component area which is executed in case there is a valid cache available. It must receive (in parameters) the data that the template “asked” to save.
Some Recommendations
If the component uses standard caching but no template is connected (because it is not necessary), the following shall be used:
$this->EndResultCache();
A possible solution where the component template is removed from the cached area. Other components can be connected in the template itself.
$cache_id = serialize(array($arParams, ($arParams['CACHE_GROUPS']==='N'? false: $USER->GetGroups())));
$obCache = new CPHPCache;
if ($obCache->InitCache($arParams['CACHE_TIME'], $cache_id, '/'))
{
$vars = $obCache->GetVars();
$arResult = $vars['arResult'];
}
elseif ($obCache->StartDataCache())
{
// code
$obCache->EndDataCache(array(
'arResult' => $arResult,
));
}
If the code is written properly and template.php нcontains no “heavy” code, this option might work well enough.
CUSTOM Parameter Type
The CUSTOM parameter type gives total freedom of customization to the developer. For example, there is a system or a third party component. Depending on the template there is a need to add specific settings to the component.
It can be done as follows:
Example | Description |
JS_FILE | The file containing JS code that is responsible for displaying the custom option |
JS_EVENT | Callback function that will be called after loading JS_FILE |
JS_DATA | Additional data transmitted to JS_EVENT |
|
Example JS_DATA:
{
data:JS_DATA, //JS_DATA from .parameters.php
oCont: td, /* the container where custom control panel can be located with the parameter */
oInput: input,//input in which the parameter value will be transmitted to server during saving
popertyID:"MAP_DATA",//parameter name
propertyParams: { /*...*/ },//The object with the same contents as the parameter array in .parameters.php
fChange:function(){ /*...*/ },//callback for call during parameter change
getElements:function(){ /*...*/ }//returns the object with all parameters of the component
}
Implementation in a Standard Component
Let us consider an example of using CUSTOM parameter type in the standard component map.google.view.
We can see the following in the file .parameters.php:
$arComponentParameters = array(
//...
'MAP_DATA' => array(
'NAME' => GetMessage('MYMS_PARAM_DATA'),
'TYPE' => 'CUSTOM',
'JS_FILE' => '/bitrix/components/bitrix/map.google.view/settings/settings.js',
'JS_EVENT' => 'OnGoogleMapSettingsEdit',
'JS_DATA' => LANGUAGE_ID.'||'.GetMessage('MYMS_PARAM_DATA_SET'),
'DEFAULT' => serialize(array(
'google_lat' => GetMessage('MYMS_PARAM_DATA_DEFAULT_LAT'),
'google_lon' => GetMessage('MYMS_PARAM_DATA_DEFAULT_LON'),
'google_scale' => 13
)),
'PARENT' => 'BASE',
)
//...
);
In the file /bitrix/components/bitrix/map.google.view/settings/settings.js
:
function JCEditorOpener(arParams)
{
this.jsOptions = arParams.data.split('||');
this.arParams = arParams;
var obButton = document.createElement('BUTTON');//creating a button
this.arParams.oCont.appendChild(obButton);// adding to container
obButton.innerHTML = this.jsOptions[1];//text from JS_DATA
obButton.onclick = BX.delegate(this.btnClick, this);//specify callback functions
this.saveData = BX.delegate(this.__saveData, this);
}
Clicking the button opens the dialog generated in /bitrix/components/bitrix/map.google.view/settings/settings.php
. The current value of MAP_DTA is transmitted in the query to settings.php.
Header
obJSPopup->ShowTitlebar();
$obJSPopup->StartDescription('bx-edit-menu');
<p><b><? echo GetMessage('MYMV_SET_POPUP_WINDOW_TITLE')?></b></p>
<p class="note"><? echo GetMessage('MYMV_SET_POPUP_WINDOW_DESCRIPTION')?></p>
Content Block
$obJSPopup->StartContent();
Buttons Block
$obJSPopup->StartButtons();
Save Button
<input type="submit" value="<?echo GetMessage('MYMV_SET_SUBMIT')?/>" onclick="return jsGoogleCE.__saveChanges();"/>
$obJSPopup->ShowStandardButtons(array('cancel'));//cancel button
$obJSPopup->EndButtons();
В __saveChanges() the data are serialized in a string and written into oInput. The serialization function in js to the php format can be looked up in bitrix/components/bitrix/map.google.view/settings/settings_lod.js
. In the component the de-serialization is performed from $arParam[~MAP_DATA]
.
Localization
The language file is located in lang/en/.parameters.php
. When using the CUSTOM parameter type, do not forget to add messages to this file.
More examples
How Can CAPTCHA Be Displayed in an Own Component?
An example of using CAPTCHA on a page.
<?
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
$APPLICATION->SetTitle("Title");
?>
<?
if (isset($submit)) {
echo 'сабмит прошел...<br>';
echo $myname.'<br>';
echo $cap.'<br>';
if (!$GLOBALS["APPLICATION"]->CaptchaCheckCode($cap, $captcha_sid))
{
$error=true;
echo 'error captcha
';
}
}
?> <form id="linkForm" name="mailForm" action="test.php" method="post">
<table cellspacing="3" cellpadding="0" width="100%" bgcolor="#eeeeee" border="0">
<tbody>
<tr><td valign="top" align="right">Name *</td><td><input size="40" value="" name="myname" /></td></tr>
<tr><td valign="top" align="right">CAPTCHA *</td><td><?
$capCode = $GLOBALS["APPLICATION"]->CaptchaGetCode();
?>
<input type="hidden" name="captcha_sid" value="<?= htmlspecialchars($capCode) ?>">
<img src="/bitrix/tools/captcha.php?captcha_sid=<?= htmlspecialchars($capCode) ?>" width="180" height="40"><br>
<input size="40" value="" name="cap" /></td></tr>
<tr><td valign="top" align="right"> </td>
<td><input type="submit" value="Sent" name="submit" /> <input type="reset" value="Сбросить" name="Reset" /></td></tr>
</tbody>
</table>
</form><?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
How to Make a Component Invisible on the Home Page and Visible on Other Pages?
Solution:
if ($curPage = $APPLICATION->GetCurPage())
{
if (($curPage != "/index.php"))
{
....
}
}
Errors When Working with Components
Cannot Find Component Access Code
This error is quite common when you try to edit component parameters in the page editing mode. Although the code contains the line $APPLICATION->IncludeComponent()
(component access), sometimes the error Cannot find component access code appears anyway. Unfortunately, there is no one-size-fits-all solution for this problem.
Possible solutions:
Events
Sometimes, there is a need to affect an API function performance. But, if you change it, these changes will be lost upon a next update. For such cases, an event system was developed. When executing some API functions, other specific functions, the so-called event handlers are called in determined points.
Note: Event handlers must be treated carefully. Due to a significant event model-related content available in Bitrix Framework, elusive errors in developer code can occur. They can significantly inhibit developer's work efficiency.
The specific event handler functions must be called at individual points (and upon which triggered events) - must be indicated by calling the function, that registers the handlers. Presently, there are two available: Bitrix\Main\EventManager::addEventHandler и Bitrix\Main\EventManager::registerEventHandler. The set of events for each module is described in the documentation dedicated to each module. Here's, for example, a link to main module events.
registerEventHandler - function for registering the handlers, located in modules and used for interaction between system modules. This function must be called once when installing the module, after that, handler function will be automatically called at a specific moment, preliminarily connecting the module itself.
Deleted via Bitrix\Main\EventManager::unRegisterEventHandler when deleting the module.
Example
$eventManager = \Bitrix\Main\EventManager::getInstance();
// compression module handler functions are connected twice - at the start and at the end of each page
$eventManager->registerEventHandler("main", "OnPageStart", "compression", "CCompress", "OnPageStart", 1);
$eventManager->registerEventHandler("main", "OnAfterEpilog", "compression", "CCompress", "OnAfterEpilog");
// module installer registers an empty handler
// advertisement module will be directly connecting at each page when the event OnBeforeProlog triggers
// allows to execute its API functions without preliminary connecting in the page body
$eventManager->registerEventHandler("main", "OnBeforeProlog", "advertising");
Each module can provide interface to other modules for indirect communication - in a form of set of events. Such interaction allows making modules maximally independent from each other. Module knows nothing about the other module operation, but will interact with it via event interface.
AddEventHandler - function is designed to register arbitrary handlers that are not located in modules. It must be called before event triggering at the pages, where event handler is processed. For example, when event must be processed on all pages, where it occurs, the function can be called in
/bitrix/php_interface/init.php
.
Example
// handler registering in /bitrix/php_interface/init.php
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandlerCompatible("main", "OnBeforeUserLogin", "MyOnBeforeUserLoginHandler");
function MyOnBeforeUserLoginHandler($arFields)
{
if(strtolower($arFields["LOGIN"])=="guest")
{
global $APPLICATION;
$APPLICATION->throwException("User with Guest login name cannot be authorized.");
return false;
}
}
Anonymous functions are suitable for "quick and painful solution" approach. To avoid overloading the code by various functions or classes, proceed as follows:
use Bitrix\Main\EventManager;
$eventManager = EventManager::getInstance();
$eventManager->addEventHandlerCompatible("main", "OnAfterUserLogin", function(&$fields) {
// Мой код
});
Differences in functions
Actions that you will perform via the events must be physically written and must be registered to be performing upon triggering of necessary events.
registerEventHandler registers in the database, and AddEventHandler registers in the init.php file. It means that using the first function leads to additional load on the database. It is best used in situations, where executed actions must be registered once and for all specifically in database.
Usually, events are divided into those occurring at a specific location and designation into the following groups:
- Intended for cancelling further method execution. For example, event OnBeforeUserDelete allows cancelling user deleting upon specified conditions (available associated objects of critical significance), event OnBeforeUserLogin - restrict authorization for a user;
- Allowed to be executed in specific methods, upon completed method execution. For example, OnAfterUserLogin - after the login name and password check, event OnUserDelete - before direct deletion of user from database; allows deleting associated objects;
- Occurring during executing a page, to enable custom code into specific locations on the page. For example, OnBeforeProlog, (see details at page execution sequence).
Note for developers:
When it is required to expand an event log with new event types (for example, upon actions with iblocks), use the event handler OnEventLogGetAuditTypes. Array that it will return is added to the standard array in the admin section. |
List and description of events, available to individual modules is located in Documentation for developers.
Events
Event system has been changed for the core D7. Requirements for data belonging to a code triggering the event are leveled down. An example of an event sending:
$event = new Bitrix\Main\Event("main", "OnPageStart");
$event->send();
If necessary, for a sending party there is a possibility to receive the result of an event handled by accepting parties.
foreach ($event->getResults() as $eventResult)
{
switch($eventResult->getType())
{
case \Bitrix\Main\EventResult::ERROR:
// error handling
break;
case \Bitrix\Main\EventResult::SUCCESS:
// successful
$handlerRes = $eventResult->getParameters(); // getting the result, returned by the event handler
break;
case \Bitrix\Main\EventResult::UNDEFINED:
/* the handler returned undefined result instead of a \Bitrix\Main\EventResult class object
its result is still available via getParameters
*/
break;
}
}
In order to reduce the code quantity Bitrix\Main\Event class successors may be created for specific event types. For example, Bitrix\Main\Entity\Event makes it more convenient to send events connected with the modification of entities.
Modules
Bitrix Framework has a module structure. Each module is responsible for managing certain website elements and parameters – website information contents and structure, forums, advertising, mailings, distribution of rights among user groups, collection of visiting statistics, evaluation of advertising campaign efficiency, etc.
Module - is a data model and API for access to these data. Static methods of module classes can be accessed in components, templates, and other modules. In addition, class instances can be created inside Bitrix Framework.
System modules mostly work independently from one another. However, in a number of cases the functionality of certain modules depends on the capabilities of other modules. For example:
- The module Commercial Catalog expands the capacity of the Information Block module and permits you to set up goods prices depending on various conditions and apply a surcharge and discounts to goods, etc.
- The Workflow module permits to organize consecutive team work with contents of the modules Information Block and Site Explorer.
After the system is installed, the list of modules used can be viewed on the page Module Management (Settings > System settings > Modules) in the administrative section of the system:
It is recommended to delete unused modules in order to save disc space. There is always a possibility to reinstall any module if necessary. During the deinstallation of certain modules, the system offers to save the data accumulated by the module (module tables). If you intend to use these data later on, do not forget to mark up this option when deleting a module.
The level of users’ rights to access system modules is managed separately for each module on its setup page. The general parameters of module operation are managed on the same page.
The setup page of a specific module may have a different amount of tabs and fields, depending on module functionality. It can be accessed as follows:
- Using the administrative menu: Settings > System settings > Module settings > module_name;
- Using the button Settings, located on the administrative panel. This button permits access to the settings of the module which pages (forms) are currently open in the main work area.
Note: Before using the API of a module it is necessary to make sure it is installed and connect it using the following structure:
<?
if(CModule::IncludeModule("******"))
{
//module functions and classes can be used here
}
?>
where
**** - module identifier.
Modules and Components
Modules in Bitrix Framework represent models and controllers of a low level (in terms of MVC) and components – controllers of high level which include representations based on the hierarchy of the website file structure. As a rule, all functional capacity of any website is implemented using standard modules, but components have to be customized (or own components have to be written) in order to generate and display pages; these components have to be connected on the relevant website pages.
Module Development
Bitrix Framework permits developing user modules.
File Structure
Module files are located in the module ID folder. Folder structure:
Description and Parameters
Description
Each module must be properly described in the system. That way, the system will “know” how to work with such a module. Improperly described modules can cause the complete or partial unserviceability of the system (e.g., the update system might fail).
The main file used by the system in order to manipulate the module is /bitrix/modules/module ID/install/index.php. (in this case, module ID - full code of partner module that is set in the following format: partner_code.module_code.) The main purpose of this file is the placement of a class in it with a name that coincides with the module ID. (module ID is used here in the format partner_code.module_code, Because, period sign is unavailable for the class name.)
Example:
01 <?
02 Class mymodule extends CModule
03 {
04 var $MODULE_ID = "mymodule";
05 var $MODULE_NAME;
06
07 function DoInstall()
08 {
09 global $DB, $APPLICATION, $step;
10 $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"), $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/mymodule/install/step1.php");
11 }
12
13 function DoUninstall()
14 {
15 global $DB, $APPLICATION, $step;
16 $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"), $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/mymodule/install/unstep1.php");
17
18 }
19 }
20 ?>
Mandatory methods of this class:
DoInstall
- launches upon clicking the button Install on the page Module Management of the administrative section and installs the module.
DoUninstall
- aunches upon clicking the button Remove on the page Module Management of the administrative section and uninstalls the module.
Optional method for this class:
Mandatory properties of an object of this class:
MODULE_ID
- stores module ID;
MODULE_VERSION
- current version of the module in the format XX.XX.XX;
MODULE_VERSION_DATE
- the line containing the date of the module version; the date must be set in the format YYYY-MM-DD HH:MI:SS;
MODULE_NAME
- module name;
MODULE_DESCRIPTION
- module description;
MODULE_GROUP_RIGHTS
- if the method GetModuleRightList
is set, this property must contain Y
.
Examples
Example of the file with the description of the module Web Forms:
<?
global $MESS;
$PathInstall = str_replace("\\", "/", __FILE__);
$PathInstall = substr($PathInstall, 0, strlen($PathInstall)-strlen("/index.php"));
IncludeModuleLangFile($PathInstall."/install.php");
include($PathInstall."/version.php");
if(class_exists("form")) return;
Class form extends CModule
{
var $MODULE_ID = "form";
var $MODULE_VERSION;
var $MODULE_VERSION_DATE;
var $MODULE_NAME;
var $MODULE_DESCRIPTION;
var $MODULE_GROUP_RIGHTS = "Y";
function form()
{
$this->MODULE_VERSION = FORM_VERSION;
$this->MODULE_VERSION_DATE = FORM_VERSION_DATE;
$this->MODULE_NAME = GetMessage("FORM_MODULE_NAME");
$this->MODULE_DESCRIPTION = GetMessage("FORM_MODULE_DESCRIPTION");
}
function DoInstall()
{
global $DB, $APPLICATION, $step;
$FORM_RIGHT = $APPLICATION->GetGroupRight("form");
if ($FORM_RIGHT=="W")
{
$step = IntVal($step);
if($step<2)
$APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"),
$_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/step1.php");
elseif($step==2)
$APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"),
$_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/step2.php");
}
}
function DoUninstall()
{
global $DB, $APPLICATION, $step;
$FORM_RIGHT = $APPLICATION->GetGroupRight("form");
if ($FORM_RIGHT=="W")
{
$step = IntVal($step);
if($step<2)
$APPLICATION->IncludeAdminFile(GetMessage("FORM_UNINSTALL_TITLE"),
$_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/unstep1.php");
elseif($step==2)
$APPLICATION->IncludeAdminFile(GetMessage("FORM_UNINSTALL_TITLE"),
$_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/unstep2.php");
}
}
function GetModuleRightList()
{
global $MESS;
$arr = array(
"reference_id" => array("D","R","W"),
"reference" => array(
GetMessage("FORM_DENIED"),
GetMessage("FORM_OPENED"),
GetMessage("FORM_FULL"))
);
return $arr;
}
}
?>
Example of the file indicating module version
<?
$arModuleVersion = array(
"VERSION" => "11.0.4",
"VERSION_DATE" => "2011-11-17 14:00:00"
);
?>
Parameters
Module parameters are available for change in the administrative interface on the page Module Settings (Settings > System settings > Module settings). When choosing a module on this page, the system connects the file /bitrix/modules/module ID/options.php intended for controlling module parameters, setting up rights to the module, etc.
Module parameters are stored in the database.
When receiving module parameters, a default value can be used that is set in the file /bitrix/modules/module ID/default_option.php. In this file, the array $ID module_default_option is defined which stores the default values.
Example of the file /bitrix/modules/module ID/default_option.php:
<?
$support_default_option = array(
"SUPPORT_DIR" => "#SITE_DIR#support/",
"SUPPORT_MAX_FILESIZE" => "100",
"ONLINE_INTERVAL" => "900",
"DEFAULT_VALUE_HIDDEN" => "N",
"NOT_IMAGE_EXTENSION_SUFFIX" => "_",
"NOT_IMAGE_UPLOAD_DIR" => "support/not_image",
"DEFAULT_AUTO_CLOSE_DAYS" => "7"
);
?>
Example of use:
<?
// We set up a string parameter
COption::SetOptionString("my_module_id", "MY_PARAMETER_ID", "VALUE");
// We will obtain a string parameter
$value = COption::GetOptionString("my_module_id", "MY_PARAMETER_ID", "DEFAULT_VALUE");
?>
The class COption is intended for work with module parameters.
Administrative Scripts
Administrative Scripts - are the scripts used by the module in the administrative part of the system. They must be located in the catalog /bitrix/modules/module ID/admin/.
It must be taken into account that administrative scripts cannot be accessed directly in the browser (like any scripts of the /bitrix/modules/). That is why additional homonymous access scripts are used in order to access administrative scripts. Access scripts are located in the catalog /bitrix/admin/. As a rule, they only contain the connection of the administrative script of the same name:
<?
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/module ID/admin/script");
?>
When installing the module, access scripts must be copied from the catalog /bitrix/modules/module ID/install/admin/ to the catalog /bitrix/admin/. Upon deinstallation of the module access scripts must be deleted from this catalog.
Note: It must be taken into account that access scripts of all installed modules are located in the same catalog /bitrix/admin/; that is why it is recommended that their names start with a prefix characteristic only for the relevant module to avoid duplication.
Each administrative script must have a defined constant ADMIN_MODULE_NAME
, before connecting a visual part of administrative prologue. This constant is required to generate an icon above the page header. The constant is also required to jump quickly to the module settings when clicking the Settings button. It is usually set in prolog.php.
Example of the definitions of such constants:
define("ADMIN_MODULE_NAME", "statistic");
define("ADMIN_MODULE_ICON", "<a href=\"stat_list.php?lang=".LANGUAGE_ID."\">
<img src=\"/bitrix/images/statistic/statistic.gif\"
width=\"48\" height=\"48\" border=\"0\" alt=\"".GetMessage("STAT_MODULE_TITLE")."\"
title=\"".GetMessage("STAT_MODULE_TITLE")."\"></a>");
Language Files for Administrative Scripts of the Module
Language files must be located in the catalog /bitrix/modules/module ID/lang/language ID/.
The particularity of their location inside this catalog is that they must be located strictly following the same path from the catalog /bitrix/modules/module ID/ as the homonymous files in which they are connected. In this case, language files can only be connected using the function IncludeModuleLangFile.
For example, for the script /bitrix/modules/module ID/admin/body/my_script.php the language file must be located here: /bitrix/modules/module ID/lang/language ID/admin/body/my_script.php.
And connected using the code:
IncludeModuleLangFile(__FILE__);
Administrative Menu
The menu of the administrative part is displayed by the standard function CMain::GetMenuHtmlEx.
The menu template is stored in the file /bitrix/modules/main/interface/.left.menu_template.php
The main file collecting menu options is /bitrix/modules/main/interface/.left.menu.php
. Here, all the files contained in /bitrix/modules/module ID/admin/menu.php
are examined. Each such file contains a definition of the array $aModuleMenuLinks containing the menu options of the relevant module. All of these arrays will be later unified into a standard array $arMenuSections that contains information about all the menu options.
Sample menu structure using the example of \bitrix\modules\main\admin\menu.php
$aMenu[] = array(
"parent_menu" => "global_menu_settings",
"sort" => 1800,
"text" => GetMessage("MAIN_MENU_TOOLS"),
"title" => GetMessage("MAIN_MENU_TOOLS_TITLE"),
"url" => "tools_index.php?lang=".LANGUAGE_ID,
"icon" => "util_menu_icon",
"page_icon" => "util_page_icon",
"items_id" => "menu_util",
"items" => array(
array(
"text" => GetMessage("MAIN_MENU_SITE_CHECKER"),
"url" => "site_checker.php?lang=".LANGUAGE_ID,
"more_url" => array(),
"title" => GetMessage("MAIN_MENU_SITE_CHECKER_ALT"),
),
array(
"text" => GetMessage("MAIN_MENU_FILE_CHECKER"),
"url" => "file_checker.php?lang=".LANGUAGE_ID,
"more_url" => array(),
"title" => GetMessage("MAIN_MENU_FILE_CHECKER_ALT"),
),
array(
"text" => GetMessage("MAIN_MENU_PHPINFO"),
"url" => "phpinfo.php?test_var1=AAA&test_var2=BBB",
"more_url" => array("phpinfo.php"),
"title" => GetMessage("MAIN_MENU_PHPINFO_ALT"),
),
array(
"text" => GetMessage("MAIN_MENU_SQL"),
"url" => "sql.php?lang=".LANGUAGE_ID."&del_query=Y",
"more_url" => array("sql.php"),
"title" => GetMessage("MAIN_MENU_SQL_ALT"),
),
array(
"text" => GetMessage("MAIN_MENU_PHP"),
"url" => "php_command_line.php?lang=".LANGUAGE_ID."",
"more_url" => array("php_command_line.php"),
"title" => GetMessage("MAIN_MENU_PHP_ALT"),
),
array(
"text" => GetMessage("MAIN_MENU_AGENT"),
"url" => "agent_list.php?lang=".LANGUAGE_ID,
"more_url" => array("agent_list.php", "agent_edit.php"),
"title" => GetMessage("MAIN_MENU_AGENT_ALT"),
),
array(
"text" => GetMessage("MAIN_MENU_DUMP"),
"url" => "dump.php?lang=".LANGUAGE_ID,
"more_url" => array("dump.php", "restore_export.php"),
"title" => GetMessage("MAIN_MENU_DUMP_ALT"),
),
(strtoupper($DBType) == "MYSQL"?
Array(
"text" => GetMessage("MAIN_MENU_REPAIR_DB"),
"url" => "repair_db.php?lang=".LANGUAGE_ID,
"more_url" => array(),
"title" => GetMessage("MAIN_MENU_REPAIR_DB_ALT"),
)
:null
),
($USER->CanDoOperation('view_event_log')?
Array(
"text" => GetMessage("MAIN_MENU_EVENT_LOG"),
"url" => "event_log.php?lang=".LANGUAGE_ID,
"more_url" => array(),
"title" => GetMessage("MAIN_MENU_EVENT_LOG_ALT"),
)
:null
),
),
);
An item of administrative menu can be added via the event OnBuildGlobalMenu.
If you are writing your own module, you can use /bitrix/modules/ID_module/admin/menu.php
to add arbitrary options to the administrative menu.
Old method of generating a menu
|
Array structure $aModuleMenuLinks:
Array
(
[0] => Array
(
[0] => menu item header
[1] => menu item link
[2] => Array
(
[0] => additional link to Highlight menu item 1
[1] => additional link to Highlight menu item 2
...
[N] => additional link to Highlight menu item N
)
[3] => Array
(
[ALT] => popup hint at the menu item
[SECTION_ID] => unique ID for menu section,
accepts value equal to module ID,
or one of the following:
FAVORITE - "Favourites" section
GENERAL - "Users" section
MAIN - "System settings" section
[SEPARATOR] => "Y" - menu item that is menu section header
[SORT] => menu section sorting
for other menu sections (only when SEPARATOR=Y)
[ICON] => link to small icon used
in the menu section header (only when SEPARATOR=Y)
[BIG_ICON] => link to large icon for use
on the "Desktop" page (only when SEPARATOR=Y)
[INDEX_PAGE] => link at the BIG_ICON (only when SEPARATOR=Y)
)
)
[1] => Array( -//- )
[2] => Array( -//- )
...
[M] => Array( -//- )
)
Example of file /bitrix/modules/support/admin/menu.php that determines "Technical support" module menu:
<?
// connect language file
IncludeModuleLangFile(__FILE__);
// determine access permissions for current user
$SUP_RIGHT = $APPLICATION->GetGroupRight("support");
// when access permissions are not restricted
if($SUP_RIGHT>"D")
{
// add menu items depending on access permissions
$aModuleMenuLinks[] = Array(
GetMessage("SUP_M_SUPPORT"),
"",
Array(),
Array(
"SEPARATOR" => "Y",
"SORT" => 1000,
"ICON" => "/bitrix/images/support/mnu_support.gif",
"BIG_ICON" => "/bitrix/images/support/support.gif",
"INDEX_PAGE" => "/bitrix/admin/ticket_desktop.php?lang=".LANGUAGE_ID."&set_default=Y"
)
);
if ($SUP_RIGHT>="T")
{
$aModuleMenuLinks[] = Array(
GetMessage("SUP_M_REPORT_TABLE"),
"/bitrix/admin/ticket_desktop.php?lang=".LANGUAGE_ID."&set_default=Y",
Array("/bitrix/admin/ticket_desktop.php"),
Array("ALT"=>GetMessage("SUP_M_REPORT_TABLE_ALT"))
);
}
$aModuleMenuLinks[] = Array(
GetMessage("SUP_M_TICKETS"),
"/bitrix/admin/ticket_list.php?lang=".LANGUAGE_ID."&set_default=Y",
Array(
"/bitrix/admin/ticket_list.php",
"/bitrix/admin/ticket_edit.php",
"/bitrix/admin/ticket_message_edit.php"
),
Array("ALT"=>GetMessage("SUP_M_TICKETS_ALT"))
);
if ($SUP_RIGHT>="T")
{
$aModuleMenuLinks[] = Array(
GetMessage("SUP_M_REPORT_GRAPH"),
"/bitrix/admin/ticket_report_graph.php?lang=".LANGUAGE_ID."&set_default=Y",
Array("/bitrix/admin/ticket_report_graph.php"),
Array("ALT"=>GetMessage("SUP_M_REPORT_GRAPH_ALT"))
);
}
if ($SUP_RIGHT>="V")
{
$aModuleMenuLinks[] = Array(
GetMessage("SUP_M_CATEGORY"),
"/bitrix/admin/ticket_dict_list.php?lang=".LANGUAGE_ID."&find_type=C&set_filter=Y",
Array(
"/bitrix/admin/ticket_dict_edit.php?find_type=C",
"/bitrix/admin/ticket_dict_list.php?find_type=C"
)
);
}
}
?> |
Interaction of Modules
Modules can interact among them in two ways: explicitly (by direct query) and inwardly (through event system).
Explicit Interaction
Explicit interaction means:
- Module connection using the function
CModule::IncludeModule
;
- Direct query of a class method or a module function.
Example of an explicit interaction:
<?
// we connect the module mymodule
if (CModule::IncludeModule("mymodule"))
{
// we execute its method
CMyModuleClass::DoIt();
}
?>
Interaction through Events
Event - is an arbitrary action at the time of execution of which (before or after) all the handlers of such an event are collected and executed one by one.
The entity of an event permits making modules independent from one another to a maximum degree. The module “knows” nothing about the particulars of functioning of another module, but it can interact with it through the interface of events.
The events operating procedure is as follows. The module initializing an event must perform the following operations at the place within the code where this event occurs:
- Collect all the registered handlers using the function
GetModuleEvents
.
- Perform them one by one using the function
ExecuteModuleEvent
processing the values returned by the handlers accordingly.
In its turn, the module that “wants” to perform any actions to this event must:
- Register its handler using the function
RegisterModuleDependences
at the time of installation.
- Accordingly, this handler function must be available, and you have to make sure that the script where this function is located is connected in the file /bitrix/modules/module ID/include.php.
Example of Interaction
The perfect example of such an interaction is the interaction of system modules with the Search module. This module has no information about the data of other modules, their storage and processing particulars. It only provides an interface for data indexing. Any system model to be indexed registers a handler for the event OnReindex
. upon installation. Each such handler, in its turn, returns data for the indexing to be used by the Search module for filling its base.
Sample codes of examples of interaction through events:
<?
// handler registration:
// when the event OnSomeEvent occurs in the module init_module
// the method CMyModuleClass::Handler of the module handler_module will be called
RegisterModuleDependences(
"init_module", "OnSomeEvent",
"handler_module", "CMyModuleClass", "Handler"
);
?>
<?
// arbitrary function of the module init_module
// where the event is generated
function MyFunction()
{
// the arbitrary code is located here
// it represents the event
// after that, registered handlers are collected
$rsHandlers = GetModuleEvents("anothermodule", "OnSomeEvent");
while($arHandler = $rsHandlers->Fetch())
{
// and executed one by one
if(!ExecuteModuleEvent($arHandler, $param1, $param2))
{
// if the handler returns false,
// then, for example, we return the phrase "I can't do it..."
return "I can't do it...";
}
}
return "I have done it!";
}
?>
<?
// handler
class CMyModuleClass
{
function Handler($param1, $param2)
{
if($param1=="delete all")
return false;
return true;
}
}
?>
Installation and Deletion
Module Installation
The module is installed in the administrative interface on the page Settings > System settings > Modules by clicking the button Install. In this case, the method of the DoInstall class will be called with a name coinciding with the module ID. This class must be described in the file /bitrix/modules/ID модуля/install/index.php.
During module installation, the following steps must be performed without fail:
- Register the module using the function RegisterModule.
- If the module has administrative scripts then access scripts must be copied to the catalog /bitrix/admin/ in order to access such administrative scripts.
- All images used by the module must be copied in the catalog /bitrix/images/module ID/.
Module Deletion
The module is uninstalled by clicking the button Remove. In this case, the method of the DoUninstall class will be called with a name coinciding with the module ID. This class must be described in the file /bitrix/modules/module ID/install/index.php.
During the module uninstallation, the following steps must be performed without fail:
- Deregister the module using the function UnRegisterModule
- If the module has administrative scripts then access scripts must be deleted from the catalog /bitrix/admin/.
- All images used by the module must be deleted from the catalog /bitrix/images/module ID/.
Module Customization
Creation of a new module or change of operation of a standard module is rarely required in Bitrix Framework since the majority of tasks are solved using components and their customization.
Attention! Module customization is a modification of the system kernel with all the consequences. There is a risk that the system will become inoperable after an update or you may lose the right to refer to the technical support service.
That is why module customization is an operation that is not recommended and almost prohibited. However, there is a technical possibility to do it, and in this chapter we will study an example of a simple customization.
Example of Changing Module Operation
Attention! Prior to modifying module operation (i.e. modifying system kernel) you must be sure that there is no other way to resolve your task. All changes that you add will be eliminated upon the next update of the product, and you will have to make them again.
Let us solve the task of downloading contacts from a corporate portal to Outlook 2003. Please remember that Bitrix24 Self-hosted in a standard package is intended for interaction with Outlook 2007.
The file /bitrix/modules/intranet/classes/general/ws_contacts.php is responsible for forming and preparing data for Outlook.
There are two standard problems:
- Avatars are not downloaded to Outlook 2003 - and an error occurs at once.
- Sometimes the companies, where the users work, are not downloaded (the field Organization in Outlook).
We solve the first problem using the function __getRow($arRes, $listName, &$last_change)
. Commenting on the strings of image attribute setup:
/*if ($this->bGetImages && $arRes['PERSONAL_PHOTO'] > 0)
{
$arImage = CIntranetUtils::InitImage($arRes['PERSONAL_PHOTO'], 100, 100);
$obRow->setAttribute('ows_Attachments', ';#'.($APPLICATION->IsHTTPS() ? 'https://' : 'http://')
.$_SERVER['HTTP_HOST'].$arImage['CACHE']['src'].';#'.CIntranetUtils::makeGUID(md5($arRes['PERSONAL_PHOTO'])).',1;#');
$obRow->setAttribute('ows_MetaInfo_AttachProps', '<File Photo="-1">'.$arImage['FILE']['FILE_NAME'].'</File>');
}
else
{*/
$obRow->setAttribute('ows_Attachments', 0);
//}
We solve the second problem using the function GetListItemChangesSinceToken($listName, $viewFields = '', $query = '', $rowLimit = 0, $changeToken = '')
. In the cycle while while ($arUser = $obUsers->NavNext())
we comment on all strings starting with $arUser['WORK_COMPANY']
(i.e. where the value of this attribute changes).
Note: In the function GetList
the attribute diagram is formed. If before the line: return array('GetListResult' => $data);
the array $data
is displayed, we will have a chance to see the downloading diagram.
Programming in Bitrix Framework
Programming in Bitrix Framework is relatively easy. It is a normal PHP-based programming using API provided by Bitrix Framework.
The particulars of programming in Bitrix Framework are reviewed in this chapter.
Golden Rules
Before starting work in Bitrix Framework you have to understand the main rules that will help you to avoid many mistakes:
Attention! The files that cannot be accessed directly (they must not be executed by direct access using an address in the browser) must contain the following verification code in the beginning:
<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
These files include all the files that operate inside the product, e.g. website templates, component templates, files .parameters.php and .description.php.
PHP command line
There are some situations, when a specific code must be executed quickly, calling the Bitrix Framework API functions without creating new pages at the site. In this case, a simple and convenient tool is available — PHP command line. It allows to launch arbitrary PHP code with function calls.
This tool is located in the site's Control Panel at the following path: Settings > Tools > PHP command line and has the address: /bitrix/admin/php_command_line.php
.
Here's how the result of executed code looks like, using the class functions CUser of the Main modules:
Using the tab with "+" you can create new tabs and save them most frequently used PHP code inside them. To rename the tab, use //title:***
at the start of your code.
The file init.php
init.php - is an optional file within the
Bitrix Framework file structure. It is automatically connected in the
prologue.
The file may contain the initialization of event handlers and connection of additional functions that are common for all websites. In this case, it shall be located in the path /bitrix/php_interface/init.php
. Each particular website may have its own similar file. In this case, it shall be located in the path /bitrix/php_interface/website ID/init.php
. If both files are there, the system will connect them both, but the file /bitrix/php_interface/init.php
will be the first.
Note: The file /bitrix/php_interface/website ID/init.php
is not connected in the administrative section because there is no notion of a website. Please also take into account that SITE_ID is equal to the current language and, consequently, a wrong file can get connected.
A code in init.php should be located according to logical grouping by files and classes.
The following very general rules should be followed:
- init.php contains only file connections. These files are better connected through __autoload. It can be done as follows using the standards means
CModule::AddAutoloadClasses(
'', // we do not indicate the name of the module
array(
// key – a class name, value – a path from the website root to the file with the class
'CMyClassName1' => '/path/cmyclassname1file.php',
'CMyClassName2' => '/path/cmyclassname2file.php',
)
);
- If the functionality is used only on one of the websites in the system, it should be placed in its own init.php;
- Event handlers should be grouped in the same file and accompanied with a detailed note indicating where they are used and what task they solve.
How to avoid problems during editing init.php without ftp/ssh access
An error in the file init.php results in the complete inoperability of the website and it is impossible to correct anything without access to the file through ftp/ssh. This may occur in the following cases:
- The client has a Windows-based hosting, and you have a “gray” IP and ftp will not work.
- The hosting is Linux-based, but php and ftp work from different users, and the file is unavailable for editing through ftp.
- The client has its own server that is denying you access through ftp.
If the access is available only through Web, one of the simplest ways is to place all of your code in an external file and connect it like this:
if (isset($_GET['noinit']) && !empty($_GET['noinit']))
{
$strNoInit = strval($_GET['noinit']);
if ($strNoInit == 'N')
{
if (isset($_SESSION['NO_INIT']))
unset($_SESSION['NO_INIT']);
}
elseif ($strNoInit == 'Y')
{
$_SESSION['NO_INIT'] = 'Y';
}
}
if (!(isset($_SESSION['NO_INIT']) && $_SESSION['NO_INIT'] == 'Y'))
{
if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/functions.php"))
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/functions.php");
}
Note: The parameter in the address line noinit=Y turns the connection off, noinit=N turns the connection on. Own functionality must be located (in the example) in
/bitrix/php_interface/functions.php
.
It is recommended to use an own key name. For example, nomysuperinit, because the training course is in open access, and anyone can get to know this method, including those who have malign purposes.
With such an approach you will be able to safely edit and make errors in the file functions.php without fear of getting an inoperable website that cannot be recovered without ftp.
init.php or Own Module?
A project developer has two ways to use already created solutions: own module or the file init.php. Both options have their advantages and disadvantages.
init.php. When you have classes that are common for several websites (in case of multiple websites or on the same server), the files containing classes are located in the same folder for the sake of convenience. Later on, symbolic links are created.
In case of multiple websites, there is one more way: to use already created folders. For example, folders with templates.
Both in the second and the first ways the code include $_SERVER["DOCUMENT_ROOT"]."/bitrix/templates/..."
will lead to the same place for all websites where the files that are common for all projects can be located. In addition, own folder may be created for own classes, and connect classes from there by accessing /bitrix/templates/my_classes/...
.
Module. The use of init.php is preferable if you create projects that are sure to be located on the same server throughout the entire existence of such projects, and you want to keep the expenses associated with the support of custom libraries to a minimum. These libraries are better supported by the same person. However, if you plan to use these solutions for other projects, a separate module should be created.
The module suits more for distributed APIs. In this case, their interface will also have to be supported along with response format and other parameters. Otherwise, an occasional update on one of the websites using the module may be fatal in case of change of interfaces or API response formats. On the one hand, you win; on the other hand, you must create guarantees for all of the websites that use the module API.
Organizing development
This chapter describes methods and tools for organizing development both by a group and an individual developer.
/local folder
Folder for custom development
To make developer's life more convenient, main user project files were moved from the folder /bitrix
into the folder /local
starting starting from the code D7 main module version 14.0.1. Essentially, a single folder /bitrix
is enough to be added into exceptions.
Which folders are processed in /local
?
- activities - workflow activities;
- components - components;
- modules - modules;
- php_interface - init.php, folder user_lang;
- php_interface - files init.php and [dw]dbconn.php[/dw][di]Starting from Main module version 24.100.0.[/di], folder: user_lang;
- templates - site templates, component templates, page templates;
- blocks - Sites24 blocks;
- routes - file with configuration for routing paths;
- js - scripts for custom solutions.
When processing folders, priority is always given to the /local
folder before /bitrix
. It means that if /local/templates/
and /bitrix/templates/
contain site templates with the same name, the system connects the template from /local
.
Note: It's not recommended to copy entities with the same name into both folders. The more correct solution - is to move entities from one folder to another. When copying, the system will still operate correctly; however there is a significant likelihood to create confusion in developer's work.
Moving a project
How to move an old project into the /local
folder.
You have an old and already running project. Who modified and updated it - is unknown, where adjustments were made - is unknown as well. Your task is to clean it up.
- First, use Project Quality Control that has check for kernel files modification. This test helps to determine, which files were updated.
- Next, create the
/local
folder, and do not create the file /local/php_interface/init.php, but only create /local/php_interface/constants.php
, /local/php_inteface/events.php
, autoloader for your classes and etc., and connect these files in /bitrix/php_interface/init.php
.
- Start gradually moving your modified templates into
/local/templates/.default
, with the following actions: copy into local with another name, work with test page (if there are no test server available). Next, replace name and delete (or rename) old template from your template folder. This way, new template (cleaned up and ready for use) will gradually be assembled in local. As soon as all templates and components are moved into /local; init.php also can be moved.
- Component handling is different: if these components are your native components, they can be moved as is (nothing will change from it). Modified standard components must be copied first from main core and then modifications compared and updates introduced.
- Direct links in templates and components must be replaced to
/bitrix/templates/**
, /bitrix/components/**
and /local/**
. Also, attentively check modules as well and, if required, move them as well.
It's impossible to state, how much time is required for this process. It all depends on how the project is "undermaintained". However, existing developer experience stipulates that these actions can take up to several weeks.