Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F785039
D442.id1148.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Tue, Feb 11, 7:52 PM
Size
18 KB
Mime Type
text/x-diff
Expires
Wed, Feb 12, 7:52 PM (5 h, 37 m)
Engine
blob
Format
Raw Data
Handle
564105
Attached To
D442: INP-1837 - Record code fragment in the System Log backtrace
D442.id1148.diff
View Options
Index: core/admin_templates/logs/system_logs/system_log_edit.tpl
===================================================================
--- core/admin_templates/logs/system_logs/system_log_edit.tpl
+++ core/admin_templates/logs/system_logs/system_log_edit.tpl
@@ -56,12 +56,13 @@
font-size: 12px;
}
- .highlight-area {
+ div.highlight-area {
border: 1px solid black;
padding: 8px;
font-family: serif;
background-color: #F6F6F6;
overflow: auto;
+ margin-top: 4px;
}
.log_backtrace {
@@ -69,8 +70,12 @@
padding-left: 25px;
}
- .log_backtrace div {
- margin: 4px 0;
+ div.expandable-area {
+ margin: 4px 0 4px 0;
+ }
+
+ div.expandable-area div.highlight-area:first-child {
+ margin-bottom: 4px;
}
</style>
@@ -84,13 +89,27 @@
</inp2:m_RenderElement>
</inp2:m_DefineElement>
-<inp2:m_DefineElement name="backtrace_element">
+<inp2:m_DefineElement name="backtrace_element" expandable="">
<li>
<inp2:m_if check="m_Param" name="has_args">
- <a href="#" title="<inp2:m_Phrase name='hint:la_LogBacktraceFunction' html_escape='1'/>" trace_index="<inp2:m_Param name='index'/>"><inp2:m_Phrase name="la_LogBacktraceFunction"/>:</a> <inp2:m_Param name="file_info"/>
+ <inp2:m_SetParam expandable="1"/>
+ <inp2:m_elseif check="m_Param" name="has_code_fragment"/>
+ <inp2:m_SetParam expandable="1"/>
+ </inp2:m_if>
- <div class="highlight-area" style="display: none;" id="trace_args_<inp2:m_Param name='index'/>">
- <inp2:m_Param name="args"/>
+ <inp2:m_if check="m_Param" name="expandable">
+ <a href="#" class="expandable" title="<inp2:m_Phrase name='hint:la_LogBacktraceFunction' html_escape='1'/>" trace_index="<inp2:m_Param name='index'/>"><inp2:m_Phrase name="la_LogBacktraceFunction"/>:</a> <inp2:m_Param name="file_info"/>
+
+ <div style="display: none;" class="expandable-area">
+ <inp2:m_if check="m_Param" name="has_args">
+ <inp2:m_Phrase name="la_text_Arguments"/>:
+ <div class="highlight-area"><inp2:m_Param name="args"/></div>
+ </inp2:m_if>
+
+ <inp2:m_if check="m_Param" name="has_code_fragment">
+ <a href="#" class="expandable"><inp2:m_Phrase name="la_btn_ToggleCode"/></a>
+ <div class="highlight-area expandable-area" style="display: none;"><inp2:m_Param name="code_fragment"/></div>
+ </inp2:m_if>
</div>
<inp2:m_else/>
<inp2:m_Phrase name="la_LogBacktraceFunction"/>: <inp2:m_Param name="file_info"/>
@@ -144,6 +163,11 @@
<inp2:m_RenderElement design="form_row" prefix="system-log" field="LogSourceFilename">
<td class="control-cell" valign="top">
<strong><inp2:$prefix_Filename/></strong> <inp2:m_Phrase name="la_OnLine"/> <strong><inp2:$prefix_Field field="LogSourceFileLine"/></strong>
+
+ <inp2:m_if check="{$prefix}_Field" name="LogCodeFragment" db="db">
+ <a href="#" class="expandable"><inp2:m_Phrase name="la_btn_ToggleCode"/></a>
+ <div class="highlight-area expandable-area" style="display: none;"><inp2:$prefix_PrintCodeFragment/></div>
+ </inp2:m_if>
</td>
</inp2:m_RenderElement>
@@ -155,7 +179,7 @@
<inp2:m_RenderElement design="form_row" prefix="system-log" field="LogBacktrace">
<td class="control-cell" valign="top">
<ol class="log_backtrace">
- <inp2:$prefix_PrintBacktrace render_as="backtrace_element" include_args="1"/>
+ <inp2:$prefix_PrintBacktrace render_as="backtrace_element" include_args="1" include_code_fragment="1"/>
</ol>
</td>
</inp2:m_RenderElement>
@@ -183,10 +207,10 @@
<script type="text/javascript">
$(document).ready(function () {
- $('a', '.log_backtrace').click(function () {
- $('#trace_args_' + $(this).attr('trace_index')).toggle();
+ $(document).on('click', 'a.expandable', function ($e) {
+ $(this).siblings('.expandable-area:first').toggle();
- return false;
+ $e.preventDefault();
});
});
</script>
Index: core/install/english.lang
===================================================================
--- core/install/english.lang
+++ core/install/english.lang
@@ -60,6 +60,7 @@
<PHRASE Label="la_btn_SetValue" Module="Core" Type="1">U2V0IFZhbHVl</PHRASE>
<PHRASE Label="la_btn_ShowStructure" Module="Core" Type="1">U2hvdyBTdHJ1Y3R1cmU=</PHRASE>
<PHRASE Label="la_btn_Synchronize" Module="Core" Type="1">U3luY2hyb25pemU=</PHRASE>
+ <PHRASE Label="la_btn_ToggleCode" Module="Core" Type="1">VG9nZ2xlIENvZGUuLi4=</PHRASE>
<PHRASE Label="la_btn_Unselect" Module="Core" Type="1">VW5zZWxlY3Q=</PHRASE>
<PHRASE Label="la_btn_Up" Module="Core" Type="1">VXA=</PHRASE>
<PHRASE Label="la_btn_UseDraft" Module="Core" Type="1">VXNl</PHRASE>
@@ -211,6 +212,7 @@
<PHRASE Label="la_config_SoftMaintenanceTemplate" Module="Core" Type="1" Hint="VGhpcyB0ZW1wbGF0ZSB3aWxsIGJlIHNob3duIHRvIHRoZSBGcm9udCBFbmQgdXNlcnMgd2hlbiBTb2Z0IE1haW50ZW5hbmNlIG1vZGUgaXMgYWN0aXZlLg==">VGVtcGxhdGUgZm9yIFNvZnQgTWFpbnRlbmFuY2U=</PHRASE>
<PHRASE Label="la_config_ssl_url" Module="Core" Type="1">U1NMIEZ1bGwgVVJMIChodHRwczovL3d3dy5kb21haW4uY29tL3BhdGgp</PHRASE>
<PHRASE Label="la_config_StickyGridSelection" Module="Core" Type="1">VXNlIFN0aWNreSBHcmlkIFNlbGVjdGlvbg==</PHRASE>
+ <PHRASE Label="la_config_SystemLogCodeFragmentPathFilterRegExp" Module="Core" Type="1">UmVjb3JkIGNvZGUgZnJhZ21lbnRzIGluICJTeXN0ZW0gTG9nIiBmb3IgdGhlc2UgcGF0aHMgKHJlZ2V4cCk=</PHRASE>
<PHRASE Label="la_config_SystemLogNotificationEmail" Module="Core" Type="1" Hint="RW1haWwgYWRkcmVzcyB3aGVyZSBVc2VyLWRlZmluZWQgbWVzc2FnZXMgKG1hbnVhbGx5IHNldHVwIGluIHRoZSBjb2RlKSB3aWxsIGJlIGVtYWlsZWQgdG8uIE5vdGUgdGhhdCBhbGwgZGVmYXVsdCAiU3lzdGVtIExvZyIgbWVzc2FnZXMgKG5vbmUgdXNlci1kZWZpbmVkKSBhcmUgTk9UIGVtYWlsZWQgYnkgZGVmYXVsdC4=">U2VuZCBVc2VyLWRlZmluZWQgIlN5c3RlbSBMb2ciIG1lc3NhZ2VzIHRv</PHRASE>
<PHRASE Label="la_config_SystemLogRotationInterval" Module="Core" Type="1" Hint="VGhpcyBzZXR0aW5nIGFsbG93cyB5b3UgdG8gY29udHJvbCBmb3IgaG93IGxvbmcgIlN5c3RlbSBMb2ciIG1lc3NhZ2VzIHdpbGwgYmUgc3RvcmVkIGluIHRoZSBsb2cgYW5kIHRoZW4gYXV0b21hdGljYWxseSBkZWxldGVkLiBVc2Ugb3B0aW9uICJGb3JldmVyIiB3aXRoIGNhdXRpb24gc2luY2UgaXQgd2lsbCBjb21wbGV0ZWx5IGRpc2FibGUgYXV0b21hdGljIGxvZyBjbGVhbnVwIGFuZCBjYW4gbGVhZCB0byBsYXJnZSBzaXplIG9mIGRhdGFiYXNlIHRhYmxlIHRoYXQgc3RvcmVzIGxvZyBtZXNzYWdlcy4=">S2VlcCAiU3lzdGVtIExvZyIgZm9y</PHRASE>
<PHRASE Label="la_config_ThumbnailImageHeight" Module="Core" Type="1">VGh1bWJuYWlsIEhlaWdodA==</PHRASE>
@@ -1327,6 +1329,7 @@
<PHRASE Label="la_Text_Admin" Module="Core" Type="1">QWRtaW4=</PHRASE>
<PHRASE Label="la_text_advanced" Module="Core" Type="1">QWR2YW5jZWQ=</PHRASE>
<PHRASE Label="la_Text_All" Module="Core" Type="1">QWxs</PHRASE>
+ <PHRASE Label="la_text_Arguments" Module="Core" Type="1">QXJndW1lbnRz</PHRASE>
<PHRASE Label="la_text_AutoRefresh" Module="Core" Type="1">QXV0by1SZWZyZXNo</PHRASE>
<PHRASE Label="la_Text_BackupComplete" Module="Core" Type="1">QmFjayB1cCBoYXMgYmVlbiBjb21wbGV0ZWQuIFRoZSBiYWNrdXAgZmlsZSBpczo=</PHRASE>
<PHRASE Label="la_Text_backup_access" Module="Core" Type="1">SW4tUG9ydGFsIGRvZXMgbm90IGhhdmUgYWNjZXNzIHRvIHdyaXRlIHRvIHRoaXMgZGlyZWN0b3J5</PHRASE>
Index: core/install/install_data.sql
===================================================================
--- core/install/install_data.sql
+++ core/install/install_data.sql
@@ -104,6 +104,7 @@
INSERT INTO SystemSettings VALUES(DEFAULT, 'EmailLogRotationInterval', '2419200', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsLogs', 'la_config_EmailLogRotationInterval', 'select', NULL, '86400=la_opt_OneDay||604800=la_opt_OneWeek||1209600=la_opt_TwoWeeks||2419200=la_opt_OneMonth||7257600=la_opt_ThreeMonths||29030400=la_opt_OneYear||-1=la_opt_EmailLogKeepForever', 65.02, 0, 0, 'hint:la_config_EmailLogRotationInterval');
INSERT INTO SystemSettings VALUES(DEFAULT, 'SystemLogRotationInterval', '2419200', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsLogs', 'la_config_SystemLogRotationInterval', 'select', NULL, '86400=la_opt_OneDay||604800=la_opt_OneWeek||1209600=la_opt_TwoWeeks||2419200=la_opt_OneMonth||7257600=la_opt_ThreeMonths||29030400=la_opt_OneYear||-1=la_opt_SystemLogKeepForever', 65.03, 0, 1, 'hint:la_config_SystemLogRotationInterval');
INSERT INTO SystemSettings VALUES(DEFAULT, 'SystemLogNotificationEmail', '', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsLogs', 'la_config_SystemLogNotificationEmail', 'text', 'a:5:{s:4:"type";s:6:"string";s:9:"formatter";s:10:"kFormatter";s:6:"regexp";s:85:"/^([-a-zA-Z0-9!\\#$%&*+\\/=?^_`{|}~.]+@[a-zA-Z0-9]{1}[-.a-zA-Z0-9_]*\\.[a-zA-Z]{2,6})$/i";s:10:"error_msgs";a:1:{s:14:"invalid_format";s:18:"!la_invalid_email!";}s:7:"default";s:0:"";}', NULL, 65.04, 0, 1, 'hint:la_config_SystemLogNotificationEmail');
+INSERT INTO SystemSettings VALUES(DEFAULT, 'SystemLogCodeFragmentPathFilterRegExp', '#(modules/custom|system/cache/modules/custom)/.*#', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsLogs', 'la_config_SystemLogCodeFragmentPathFilterRegExp', 'text', NULL, NULL, 65.05, 0, 1, NULL);
INSERT INTO SystemSettings VALUES(DEFAULT, 'CSVExportDelimiter', '1', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsCSVExport', 'la_config_CSVExportDelimiter', 'select', NULL, '0=la_opt_Tab||1=la_opt_Comma||2=la_opt_Semicolon||3=la_opt_Space||4=la_opt_Colon', 70.01, 0, 1, NULL);
INSERT INTO SystemSettings VALUES(DEFAULT, 'CSVExportEnclosure', '0', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsCSVExport', 'la_config_CSVExportEnclosure', 'radio', NULL, '0=la_Doublequotes||1=la_Quotes', 70.02, 0, 1, NULL);
INSERT INTO SystemSettings VALUES(DEFAULT, 'CSVExportSeparator', '0', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsCSVExport', 'la_config_CSVExportSeparator', 'radio', NULL, '0=la_Linux||1=la_Windows', 70.03, 0, 1, NULL);
Index: core/install/install_schema.sql
===================================================================
--- core/install/install_schema.sql
+++ core/install/install_schema.sql
@@ -447,6 +447,8 @@
LogBacktrace longtext,
LogSourceFilename varchar(255) NOT NULL DEFAULT '',
LogSourceFileLine int(11) DEFAULT NULL,
+ LogCodeFragment longtext,
+ LogCodeFragmentsRotated tinyint(4) NOT NULL DEFAULT '0',
LogProcessId bigint(20) unsigned DEFAULT NULL,
LogMemoryUsed bigint(20) unsigned NOT NULL,
LogUserData longtext NOT NULL,
@@ -454,7 +456,8 @@
PRIMARY KEY (LogId),
KEY LogLevel (LogLevel),
KEY LogType (LogType),
- KEY LogNotificationStatus (LogNotificationStatus)
+ KEY LogNotificationStatus (LogNotificationStatus),
+ KEY `TIMESTAMP_CODE_FRAGMENTS_ROTATED` (`LogTimestamp`,`LogCodeFragmentsRotated`) USING BTREE
);
CREATE TABLE SystemCache (
Index: core/install/upgrades.sql
===================================================================
--- core/install/upgrades.sql
+++ core/install/upgrades.sql
@@ -2958,3 +2958,9 @@
ALTER TABLE Semaphores CHANGE MainIDs MainIDs TEXT NULL;
ALTER TABLE Users ADD KEY Email (Email);
+
+INSERT INTO SystemSettings VALUES(DEFAULT, 'SystemLogCodeFragmentPathFilterRegExp', '#(modules/custom|system/cache/modules/custom)/.*#', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsLogs', 'la_config_SystemLogCodeFragmentPathFilterRegExp', 'text', NULL, NULL, 65.05, 0, 1, NULL);
+ALTER TABLE SystemLog
+ ADD COLUMN `LogCodeFragment` longtext NULL AFTER `LogSourceFileLine`,
+ ADD COLUMN `LogCodeFragmentsRotated` tinyint(4) NOT NULL DEFAULT '0' AFTER `LogCodeFragment`;
+ALTER TABLE SystemLog ADD INDEX `TIMESTAMP_CODE_FRAGMENTS_ROTATED` (`LogTimestamp`,`LogCodeFragmentsRotated`) USING BTREE;
Index: core/kernel/utility/logger.php
===================================================================
--- core/kernel/utility/logger.php
+++ core/kernel/utility/logger.php
@@ -458,6 +458,15 @@
$this->_logRecord['LogSourceFileLine'] = $line;
}
+ $code_fragment = $this->getCodeFragment(
+ $this->_logRecord['LogSourceFilename'],
+ $this->_logRecord['LogSourceFileLine']
+ );
+
+ if ( $code_fragment !== null ) {
+ $this->_logRecord['LogCodeFragment'] = $code_fragment;
+ }
+
return $this;
}
@@ -521,6 +530,14 @@
if ( isset($trace_info['args']) ) {
$trace[$trace_index]['args'] = $this->_implodeObjects($trace_info['args']);
}
+
+ if ( isset($trace_info['file'], $trace_info['line']) ) {
+ $code_fragment = $this->getCodeFragment($trace_info['file'], $trace_info['line']);
+
+ if ( $code_fragment !== null ) {
+ $trace[$trace_index]['code_fragment'] = $code_fragment;
+ }
+ }
}
$this->_logRecord['LogBacktrace'] = serialize($this->_removeObjectsFromTrace($trace));
@@ -529,6 +546,57 @@
}
/**
+ * Returns a code fragment.
+ *
+ * @param string $file Filename.
+ * @param integer $line Line.
+ *
+ * @return string|null
+ */
+ protected function getCodeFragment($file, $line)
+ {
+ static $path_filter_regexp;
+
+ // Lazy detect path filter regexp, because it's not available at construction time.
+ if ( $path_filter_regexp === null ) {
+ $application =& kApplication::Instance();
+ $path_filter_regexp = $application->ConfigValue('SystemLogCodeFragmentPathFilterRegExp');
+ }
+
+ if ( strpos($file, 'eval()\'d code') !== false
+ || ($path_filter_regexp && !preg_match($path_filter_regexp, $file))
+ ) {
+ return null;
+ }
+
+ $from_line = max(1, $line - 10);
+ $to_line = $line + 10;
+
+ // Prefix example: ">>> 5. " or " 5. ".
+ $prefix_length = 4 + strlen($to_line) + 2;
+
+ $cmd_parts = array(
+ 'sed',
+ '-n',
+ escapeshellarg($from_line . ',' . $to_line . 'p'),
+ escapeshellarg($file),
+ );
+ $command = implode(' ', $cmd_parts);
+
+ $ret = array();
+ $code_fragment = preg_replace('/(\r\n|\n|\r)$/', '', shell_exec($command), 1);
+
+ foreach ( explode("\n", $code_fragment) as $line_offset => $code_fragment_part ) {
+ $line_number = $from_line + $line_offset;
+ $line_indicator = $line_number == $line ? '>>> ' : ' ';
+
+ $ret[] = str_pad($line_indicator . $line_number . '.', $prefix_length) . $code_fragment_part;
+ }
+
+ return implode("\n", $ret);
+ }
+
+ /**
* Remove objects from trace, since before PHP 5.2.5 there wasn't possible to remove them initially
*
* @param Array $trace
Index: core/units/logs/system_logs/system_log_eh.php
===================================================================
--- core/units/logs/system_logs/system_log_eh.php
+++ core/units/logs/system_logs/system_log_eh.php
@@ -117,4 +117,48 @@
$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
}
}
+
+ /**
+ * Removes old code fragments.
+ *
+ * @param kEvent $event Event.
+ *
+ * @return void
+ */
+ protected function OnRotateCodeFragmentsScheduledTask(kEvent $event)
+ {
+ $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
+ $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+
+ $sql = 'SELECT LogBacktrace, ' . $id_field . '
+ FROM ' . $table_name . '
+ WHERE LogTimestamp < ' . strtotime('-1 month') . ' AND LogCodeFragmentsRotated = 0
+ LIMIT 0,500';
+ $records = $this->Conn->GetColIterator($sql, $id_field);
+
+ foreach ( $records as $system_log_id => $trace ) {
+ if ( $trace ) {
+ $trace = unserialize($trace);
+
+ foreach ( $trace as $index => $trace_info ) {
+ if ( array_key_exists('code_fragment', $trace_info) ) {
+ unset($trace[$index]['code_fragment']);
+ }
+ }
+
+ $trace = serialize($trace);
+ }
+
+ $this->Conn->doUpdate(
+ array(
+ 'LogBacktrace' => $trace,
+ 'LogCodeFragment' => null,
+ 'LogCodeFragmentsRotated' => 1,
+ ),
+ $table_name,
+ $id_field . ' = ' . $system_log_id
+ );
+ }
+ }
+
}
Index: core/units/logs/system_logs/system_log_tp.php
===================================================================
--- core/units/logs/system_logs/system_log_tp.php
+++ core/units/logs/system_logs/system_log_tp.php
@@ -118,6 +118,7 @@
$ret = '';
$trace = unserialize($value);
$include_args = isset($params['include_args']) ? $params['include_args'] : false;
+ $include_code_fragment = isset($params['include_code_fragment']) ? $params['include_code_fragment'] : false;
$strip_tags = isset($params['strip_tags']) ? $params['strip_tags'] : false;
$block_params = $this->prepareTagParams($params);
@@ -139,11 +140,21 @@
}
$block_params['has_args'] = isset($trace_info['args']);
+ $block_params['has_code_fragment'] = isset($trace_info['code_fragment']);
if ( $include_args ) {
$block_params['args'] = $block_params['has_args'] ? $this->highlightString(print_r($trace_info['args'], true)) : '';
}
+ if ( $include_code_fragment ) {
+ if ( $block_params['has_code_fragment'] ) {
+ $block_params['code_fragment'] = $this->highlightString($trace_info['code_fragment']);
+ }
+ else {
+ $block_params['code_fragment'] = '';
+ }
+ }
+
$ret .= $this->Application->ParseBlock($block_params);
}
@@ -151,6 +162,27 @@
}
/**
+ * Prints a code fragment.
+ *
+ * @param array $params Tag params.
+ *
+ * @return string
+ */
+ protected function PrintCodeFragment(array $params)
+ {
+ /** @var kDBItem $object */
+ $object = $this->getObject($params);
+
+ $code_fragment = $object->GetDBField('LogCodeFragment');
+
+ if ( $code_fragment ) {
+ return $this->highlightString($code_fragment);
+ }
+
+ return '';
+ }
+
+ /**
* Prints backtrace record index
*
* @param Array $params
Index: core/units/logs/system_logs/system_logs_config.php
===================================================================
--- core/units/logs/system_logs/system_logs_config.php
+++ core/units/logs/system_logs/system_logs_config.php
@@ -34,6 +34,7 @@
'ScheduledTasks' => Array (
'system_log_notifications' => Array ('EventName' => 'OnSendNotifications', 'RunSchedule' => '0 * * * *'),
'rotate_system_logs' => Array ('EventName' => 'OnRotate', 'RunSchedule' => '0 0 * * *'),
+ 'rotate_system_log_code_fragments' => Array ('EventName' => 'OnRotateCodeFragmentsScheduledTask', 'RunSchedule' => '0 0 * * 0'),
),
'IDField' => 'LogId',
@@ -156,6 +157,13 @@
'LogBacktrace' => Array ('type' => 'string', 'default' => NULL),
'LogSourceFilename' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''),
'LogSourceFileLine' => Array ('type' => 'int', 'default' => NULL),
+ 'LogCodeFragment' => Array ('type' => 'string', 'default' => NULL),
+ 'LogCodeFragmentsRotated' => Array (
+ 'type' => 'int',
+ 'formatter' => 'kOptionsFormatter',
+ 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1,
+ 'not_null' => 1, 'default' => 0,
+ ),
'LogProcessId' => Array ('type' => 'int', 'default' => NULL),
'LogMemoryUsed' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
'LogUserData' => Array ('type' => 'string', 'not_null' => 1, 'default' => ''),
Event Timeline
Log In to Comment