Index: branches/RC/core/install/install_schema.sql =================================================================== diff -u -N -r10024 -r10294 --- branches/RC/core/install/install_schema.sql (.../install_schema.sql) (revision 10024) +++ branches/RC/core/install/install_schema.sql (.../install_schema.sql) (revision 10294) @@ -460,3 +460,37 @@ `IsPrimary` int(1) NOT NULL default '0', PRIMARY KEY (`SkinId`) ); + +CREATE TABLE ChangeLogs ( + ChangeLogId bigint(20) NOT NULL auto_increment, + PortalUserId int(11) NOT NULL default '0', + SessionLogId int(11) NOT NULL default '0', + `Action` tinyint(4) NOT NULL default '0', + OccuredOn int(11) NOT NULL default '0', + Prefix varchar(255) NOT NULL default '', + ItemId bigint(20) NOT NULL default '0', + Changes text NOT NULL, + MasterPrefix varchar(255) NOT NULL default '', + MasterId bigint(20) NOT NULL default '0', + PRIMARY KEY (ChangeLogId), + KEY PortalUserId (PortalUserId), + KEY SessionLogId (SessionLogId), + KEY `Action` (`Action`), + KEY OccuredOn (OccuredOn), + KEY Prefix (Prefix), + KEY MasterPrefix (MasterPrefix) +); + +CREATE TABLE SessionLogs ( + SessionLogId bigint(20) NOT NULL auto_increment, + PortalUserId int(11) NOT NULL default '0', + SessionId int(10) NOT NULL default '0', + `Status` tinyint(4) NOT NULL default '1', + SessionStart int(11) NOT NULL default '0', + SessionEnd int(11) default NULL, + IP varchar(15) NOT NULL default '', + AffectedItems int(11) NOT NULL default '0', + PRIMARY KEY (SessionLogId), + KEY SessionId (SessionId), + KEY `Status` (`Status`) +); Index: branches/RC/core/units/general/inp_ses_storage.php =================================================================== diff -u -N -r9639 -r10294 --- branches/RC/core/units/general/inp_ses_storage.php (.../inp_ses_storage.php) (revision 9639) +++ branches/RC/core/units/general/inp_ses_storage.php (.../inp_ses_storage.php) (revision 10294) @@ -81,9 +81,9 @@ { $time = adodb_mktime(); // Update LastAccessed only if it's newer than 1/10 of session timeout - perfomance optimization to eliminate needless updates on every click - if ($time - $this->DirectVars['LastAccessed'] > $this->SessionTimeout/10) { - $this->SetField($session, $this->TimestampField, $time); - } +// if ($time - $this->DirectVars['LastAccessed'] > $this->SessionTimeout/10) { + $this->SetField($session, $this->TimestampField, $time + $this->SessionTimeout); +// } } @@ -103,7 +103,7 @@ function GetExpiredSIDs() { - $query = ' SELECT '.$this->IDField.' FROM '.$this->TableName.' WHERE '.$this->TimestampField.' < '.(adodb_mktime()-$this->SessionTimeout); + $query = ' SELECT '.$this->IDField.' FROM '.$this->TableName.' WHERE '.$this->TimestampField.' < '.(adodb_mktime()); $ret = $this->Conn->GetCol($query); if($ret) { $this->DeleteEditTables(); Index: branches/RC/core/admin_templates/logs/change_logs/change_log_list.tpl =================================================================== diff -u -N --- branches/RC/core/admin_templates/logs/change_logs/change_log_list.tpl (revision 0) +++ branches/RC/core/admin_templates/logs/change_logs/change_log_list.tpl (revision 10294) @@ -0,0 +1,86 @@ + + + + + + + + + + + +
+ + + + + + +
+ +
+
+ + + + + + row-create + + + row-update + + row-delete + + + + + + + + + "> + + + + + + + + + Index: branches/RC/core/units/pdf/pdf_table.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/pdf_table.php (revision 0) +++ branches/RC/core/units/pdf/pdf_table.php (revision 10294) @@ -0,0 +1,335 @@ +GetCSSProperty('height'); + if ($row_height == 'auto') { + $row_height = 0; + } + $baseline = 0; + foreach ($this->Children as $elem) { + if ($elem->GetCSSProperty('vertical-align') == 'baseline') { +// $ascent = $elem->FindChild('_LINE_')->Ascent; + $ascent = $elem->FirstChild->Ascent; + if ($ascent > $baseline) { + $baseline = $ascent; + } + } + $dim = $elem->GetBoxDimensions(); + $elem_height = $dim[1]; + if ($elem_height > $row_height) { + $row_height = $elem_height; + } + } + + foreach ($this->Children as $elem) { + $vertical_align = $elem->GetCSSProperty('vertical-align'); + $dim = $elem->GetBoxDimensions(); + $elem_height = $dim[1]; + $add_top = 0; + $add_bottom = 0; + if ($vertical_align == 'top') { + $add_bottom = $row_height - $elem_height; + } + elseif ($vertical_align == 'middle') { + $add_top = ($row_height - $elem_height)/2; + $add_bottom = $add_top; + } + elseif ($vertical_align == 'bottom') { + $add_top = $row_height - $elem_height; + } + else { // baseline and all other + if ($elem->Ascent < $baseline) { + $add_top = $baseline - $elem->Ascent; + } + $add_bottom = $row_height - $elem_height - $add_top; + } + + $elem->SetCSSProperty('padding-top', $elem->GetCSSProperty('padding-top') + $add_top); + $elem->SetCSSProperty('padding-bottom', $elem->GetCSSProperty('padding-bottom') + $add_bottom); + } + } + +} + +class kPDFTable extends kPDFElement { + public $InitialMode = null; + + public $Table = array(); + public $TableBuild = false; + public $CurCol = 0; + public $CurRow = 0; + + public $ColWidths = array(); + + function __construct($node, $helper) + { + parent::__construct($node, $helper); + } + + function Init() + { + parent::Init(); + $this->InitialMode = $this->Helper->DimensionsMode; + $this->Helper->DimensionsMode = kPDFHelper::DM_SKIP; + } + + function Closed() + { + $this->BuildTable($this); + parent::Closed(); + /*$this->Helper->DimensionsMode = kPDFHelper::DM_NORMAL ; +// $this->LayoutChildren(); + $this->Helper->DimensionsMode = $this->InitialMode;*/ + } + + function ComputeWidthAndMargins() + { + if (!$this->TableBuild || !$this->GetContainingBlock()->WidthComputed || $this->WidthComputed) return ; + $this->ComputeCSSProperty('WIDTH', $this->CSSSpecifiedProperties['WIDTH']); + + if ($this->GetCSSProperty('table-layout') == 'fixed') { + $this->CalculateWidthsFixed(); + } + if ($this->GetCSSProperty('table-layout') == 'auto') { + $this->CalculateWidthsAuto(); //CalculateWidthsAuto(); + } + $this->WidthComputed = true; + } + + function CalculateWidthsFixed() + { + if (count($this->Table) == 0) return ; + + $specified_width = 0; + $specified_count = 0; + $i = 0; + foreach ($this->Table[1] as $cell) { + $i++; + $cur_w = $cell->GetCSSProperty('width'); + $colspan = isset($cell->Node->Attributes['COLSPAN']) ? $cell->Node->Attributes['COLSPAN'] : 1; + if ($cur_w != 'auto') { + $specified_width += $cur_w; + $specified_count += $colspan; + $cur_w = $cur_w/$colspan; + } + for ($col=$i; $col < $i+$colspan; $col++) { + $this->ColWidths[$col] = $cur_w; + } + $i += $colspan-1; + } + $max_cols = $i; + $remaining_w = $this->GetCSSProperty('width') - $specified_width; + $auto_width = $remaining_w/($max_cols-$specified_count); + foreach ($this->ColWidths as $i => $width) { + if ($width == 'auto') { + $this->ColWidths[$i] = $auto_width; + } + } + + $this->SetColWidths($max_cols); + } + + function SetColWidths($max_cols) + { + foreach ($this->Table as $row) { + $i = 0; + foreach ($row as $cell) { + $i++; + if ($i > $max_cols) { + break; + } + $cell_extra_width = + $cell->GetCSSProperty('border-left-width') + + $cell->GetCSSProperty('padding-left') + + $cell->GetCSSProperty('padding-right') + + $cell->GetCSSProperty('border-right-width'); + if (isset($cell->Node->Attributes['COLSPAN'])) { + $colspan = $cell->Node->Attributes['COLSPAN']; + $z = $i; + $width = 0; + do { + $width += $this->ColWidths[$z]; + } while ($z++ < $i+$colspan-1); +// $width = array_sum( array_slice($this->ColWidths, $i-1, $colspan) ); + $i += $colspan-1; + } + else { + $width = $this->ColWidths[$i]; + } + $cell->SetCSSProperty('width', $width - $cell_extra_width); + $cell->WidthComputed = true; + } + } + $this->SetCSSProperty('width', array_sum($this->ColWidths)); + } + + function BuildTable($node) + { + $this->ColWidths = array(); + foreach ($node->Children as $child) { + if ($child->GetCSSProperty('display') == 'table-row') { + $this->CurRow++; + $this->CurCol = 0; + } + if ($child->GetCSSProperty('display') == 'table-cell') { + $this->CurCol++; + $this->Table[$this->CurRow][$this->CurCol] = $child; + if (isset($child->Node->Attributes['COLSPAN'])) { + $this->CurCol += $child->Node->Attributes['COLSPAN']-1; + } + } + else { // otherwise it will find all nested table cells + $this->BuildTable($child); + } + } + $this->TableBuild = true; + } + + function CalculateWidthsAuto() + { + if (count($this->Table) == 0) return ; + + $min_col_widths = array(); + $max_col_widths = array(); + $specified_widths = array(); + $percent_widths = array(); + + $row = 0; + $max_cols = 0; + foreach ($this->Table as $row) + { + $row++; + $col = 0; + foreach ($row as $cell) { + $col++; + $cell_w = $cell->GetCSSProperty('width'); + $percent_w = false; + if (preg_match('/([.0-9]+)%/', $cell_w, $regs)) { + $percent_w = $regs[1]; + } + $min_cw = $cell_w == 'auto' && !$percent_w ? $cell->MinContentWidth : max($cell_w, $cell->MinContentWidth); + $max_cw = $cell->MaxContentWidth; + + if ($cell_w != 'auto' && is_numeric($cell_w)) { + if ($cell_w < $min_cw) { + $cell_w = $min_cw; + } + if (!isset($specified_widths[$col]) || $cell_w > $specified_widths[$col]) { + $specified_widths[$col] = $cell_w; + } + } + + $colspan = isset($cell->Node->Attributes['COLSPAN']) ? $cell->Node->Attributes['COLSPAN'] : 1; + if ($colspan == 1) { + if (!isset($min_col_widths[$col]) || $min_cw > $min_col_widths[$col]) { + $min_col_widths[$col] = $min_cw; + } + if (!isset($max_col_widths[$col]) || $max_cw > $max_col_widths[$col]) { + $max_col_widths[$col] = $max_cw; + } + if ($percent_w && (!isset($percent_widths[$col]) || $percent_w > $percent_widths[$col])) { + $percent_widths[$col] = $percent_w; + } + } + else { + $spans_min = array_slice($min_col_widths, $col-1, $colspan); + $spans_max = array_slice($max_col_widths, $col-1, $colspan); + $spans_percent = array_slice($percent_widths, $col-1, $colspan); + + $min_diff = ($min_cw - array_sum($spans_min)); + if ($min_diff > 0) { + for($i=$col; $i < $col+$colspan; $i++) { + $min_col_widths[$i] += $min_diff/$colspan; + } + } + + $max_diff = ($max_cw - array_sum($spans_max)); + if ($max_diff > 0) { + for($i=$col; $i < $col+$colspan; $i++) { + $max_col_widths[$i] += $max_diff/$colspan; + } + } + + $percent_diff = ($percent_w - array_sum($spans_percent)); + if ($percent_diff > 0) { + for($i=$col; $i < $col+$colspan; $i++) { + $percent_widths[$i] += $percent_diff/$colspan; + } + } + + $col += $colspan-1; + } + } + if ($col > $max_cols) { + $max_cols = $col; + } + } + $table_min_width = array_sum($min_col_widths); + $table_max_width = array_sum($max_col_widths) - $this->GetCSSProperty('border-left-width') - $this->GetCSSProperty('border-right-width'); + $table_specifid_width = array_sum($specified_widths); + + $cb_width = $this->GetContainingBlock()->GetCSSProperty('width'); + $computed_width = $this->ComputeCSSProperty('WIDTH', $this->GetCSSProperty('width')); + if ($computed_width == 'auto') { + $computed_width = $cb_width; + } + else { + $computed_width -= $this->GetCSSProperty('border-left-width') + $this->GetCSSProperty('border-right-width'); + } + if ($this->CSSSpecifiedProperties['WIDTH'] == 'auto' && $table_max_width < $computed_width) { + $width = $table_max_width; + $this->ColWidths = $max_col_widths; + } + else { + $width = max($table_min_width, $computed_width); + + // try to satisfy percent widths + $reset_to_min = false; + $percent_sum = 0; + for ($i=1; $i<=$max_cols; $i++) { + if (isset($percent_widths[$i])) { + $adjusted_percent = $reset_to_min ? 0 : min($percent_widths[$i], 100-$percent_sum); + $percent_sum += $adjusted_percent; + if ($adjusted_percent > 0) { + $min_col_widths[$i] = $width*$adjusted_percent/100; + } + } + if ($percent_sum >= 100) { + $reset_to_min = true; + } + } + $distribute = $width - array_sum($min_col_widths); // recalc min width here, because of % changes + + $total_max = 0; + for ($i=1; $i<=$max_cols; $i++) { + if (isset($specified_widths[$i])) { + $this->ColWidths[$i] = $specified_widths[$i]; +// $distribute -= $specified_widths[$i]; + continue; + } + else { + $total_max += $max_col_widths[$i]; + } + } + + for ($i=1; $i<=$max_cols; $i++) { + if (isset($this->ColWidths[$i])) { + continue; + } + $coeff = isset($percent_widths[$i]) ? $percent_widths[$i] / $percent_sum : $max_col_widths[$i]/$total_max; + $add = $coeff * $distribute; + $this->ColWidths[$i] = $min_col_widths[$i] + $add; + } + } + + $this->SetColWidths($max_cols); + } +} \ No newline at end of file Index: branches/RC/core/admin_templates/logs/session_logs/session_log_list.tpl =================================================================== diff -u -N --- branches/RC/core/admin_templates/logs/session_logs/session_log_list.tpl (revision 0) +++ branches/RC/core/admin_templates/logs/session_logs/session_log_list.tpl (revision 10294) @@ -0,0 +1,74 @@ + + + + + + + + + + + +
+ + + + + + +
+ +
+
+ + + + + + row-active + + + row-expired + + + + + + + + "> + + "> + + "> + + + + + + + + \ No newline at end of file Index: branches/RC/core/units/users/users_event_handler.php =================================================================== diff -u -N -r10156 -r10294 --- branches/RC/core/units/users/users_event_handler.php (.../users_event_handler.php) (revision 10156) +++ branches/RC/core/units/users/users_event_handler.php (.../users_event_handler.php) (revision 10294) @@ -215,6 +215,7 @@ $this->Application->StoreVar('super_admin', 1); } + $this->Application->HandleEvent($dummy, 'session-log:OnStartSession'); $this->processLoginRedirect($event, $password); return true; } @@ -251,6 +252,8 @@ $this_login = (int)$this->Application->RecallPersistentVar('ThisLogin'); $this->Application->StorePersistentVar('LastLogin', $this_login); $this->Application->StorePersistentVar('ThisLogin', adodb_mktime()); + + $this->Application->HandleEvent($dummy, 'session-log:OnStartSession'); } else { $object->Load(-2); @@ -381,6 +384,8 @@ $sync_manager =& $this->Application->recallObjectP('UsersSyncronizeManager', null, Array(), 'InPortalSyncronize'); $sync_manager->performAction('LogoutUser'); + $this->Application->HandleEvent($dummy, 'session-log:OnEndSession'); + $session =& $this->Application->recallObject('Session'); $session->SetField('PortalUserId', -2); $this->Application->SetVar('u.current_id', -2); Index: branches/RC/core/units/logs/change_logs/changes_formatter.php =================================================================== diff -u -N --- branches/RC/core/units/logs/change_logs/changes_formatter.php (revision 0) +++ branches/RC/core/units/logs/change_logs/changes_formatter.php (revision 10294) @@ -0,0 +1,39 @@ + $data) { + $fld_translation = $this->Application->Phrase('la_fld_'.$field); + + // remove translation link (added in debug mode) + $fld_translation = preg_replace('/(.*?)<\/a>/', '\\2', $fld_translation); + + if ($fld_translation == '!'.strtoupper('la_fld_'.$field).'!') { + // when phrase is not translated use field name as label + $fld_translation = $field; + } + + if (is_array($data) && isset($data['old']) && isset($data['new'])) { + $res .= "$fld_translation: {$data['old']} => {$data['new']}
\n"; + } + else { + $res .= "$fld_translation: {$data['new']}
\n"; + } + } + return $res; + } + +} \ No newline at end of file Index: branches/RC/core/install/upgrades.sql =================================================================== diff -u -N -r10156 -r10294 --- branches/RC/core/install/upgrades.sql (.../upgrades.sql) (revision 10156) +++ branches/RC/core/install/upgrades.sql (.../upgrades.sql) (revision 10294) @@ -180,4 +180,39 @@ INSERT INTO ConfigurationValues VALUES (DEFAULT, 'u_ThumbnailImageWidth', 120, 'In-Portal:Users', 'in-portal:configure_users'); INSERT INTO ConfigurationValues VALUES (DEFAULT, 'u_ThumbnailImageHeight', 120, 'In-Portal:Users', 'in-portal:configure_users'); INSERT INTO ConfigurationValues VALUES (DEFAULT, 'u_FullImageWidth', 450, 'In-Portal:Users', 'in-portal:configure_users'); -INSERT INTO ConfigurationValues VALUES (DEFAULT, 'u_FullImageHeight', 450, 'In-Portal:Users', 'in-portal:configure_users'); \ No newline at end of file +INSERT INTO ConfigurationValues VALUES (DEFAULT, 'u_FullImageHeight', 450, 'In-Portal:Users', 'in-portal:configure_users'); + +# ===== v 4.3.0 ===== +CREATE TABLE ChangeLogs ( + ChangeLogId bigint(20) NOT NULL auto_increment, + PortalUserId int(11) NOT NULL default '0', + SessionLogId int(11) NOT NULL default '0', + `Action` tinyint(4) NOT NULL default '0', + OccuredOn int(11) NOT NULL default '0', + Prefix varchar(255) NOT NULL default '', + ItemId bigint(20) NOT NULL default '0', + Changes text NOT NULL, + MasterPrefix varchar(255) NOT NULL default '', + MasterId bigint(20) NOT NULL default '0', + PRIMARY KEY (ChangeLogId), + KEY PortalUserId (PortalUserId), + KEY SessionLogId (SessionLogId), + KEY `Action` (`Action`), + KEY OccuredOn (OccuredOn), + KEY Prefix (Prefix), + KEY MasterPrefix (MasterPrefix) +); + +CREATE TABLE SessionLogs ( + SessionLogId bigint(20) NOT NULL auto_increment, + PortalUserId int(11) NOT NULL default '0', + SessionId int(10) NOT NULL default '0', + `Status` tinyint(4) NOT NULL default '1', + SessionStart int(11) NOT NULL default '0', + SessionEnd int(11) default NULL, + IP varchar(15) NOT NULL default '', + AffectedItems int(11) NOT NULL default '0', + PRIMARY KEY (SessionLogId), + KEY SessionId (SessionId), + KEY `Status` (`Status`) +); \ No newline at end of file Index: branches/RC/core/units/pdf/pdf_image.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/pdf_image.php (revision 0) +++ branches/RC/core/units/pdf/pdf_image.php (revision 10294) @@ -0,0 +1,48 @@ +Node->Attributes['SRC']; + if ($tmp = preg_replace('/^'.preg_quote(PROTOCOL.SERVER_NAME.(defined('PORT')?':'.PORT : '').rtrim(BASE_PATH, '/'), '/').'/', FULL_PATH, $src)) { + $this->ImageFile = $tmp; + $this->ImageInfo = getimagesize($this->ImageFile); + $this->ImageInfo[0] *= 72 / 96; + $this->ImageInfo[1] *= 72 / 96; + } + else { + + } + parent::Closed(); + } + + function CheckDimensions() + { + $elem = $this; + do { + $elem->SetCSSProperty('width', $elem->GetCSSProperty('width') + $this->ImageInfo[0]); + $elem->SetCSSProperty('height', $this->ImageInfo[1]); + $elem = $elem->Parent; + } while ($elem && $elem->GetDisplayLevel() == 'inline'); + $this->GetLineBox()->CurX += $this->ImageInfo[0]; + } + + function CalcMinMaxContentWidth() + { + $this->MinContentWidth = $this->ImageInfo[0]; + $this->MaxContentWidth = $this->ImageInfo[0]; + parent::CalcMinMaxContentWidth(); + } + + function DrawAt($page, $x=0, $y=0, $spacer_w=0) + { + if ($this->ImageFile) { + if (defined('PDF_DEBUG_NO_TEXT')) return ; + $page->DrawImage($this->ImageFile, $x, $y, $this->GetCSSProperty('width'), $this->GetCSSProperty('height')); + } + } + +} \ No newline at end of file Index: branches/RC/core/units/logs/session_logs/session_logs_config.php =================================================================== diff -u -N --- branches/RC/core/units/logs/session_logs/session_logs_config.php (revision 0) +++ branches/RC/core/units/logs/session_logs/session_logs_config.php (revision 10294) @@ -0,0 +1,133 @@ + 'session-log', + 'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'), + 'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'), + 'EventHandlerClass' => Array ('class' => 'SessionLogEventHandler', 'file' => 'session_log_eh.php', 'build_event' => 'OnBuild'), + 'TagProcessorClass' => Array ('class' => 'kDBTagProcessor', 'file' => '', 'build_event' => 'OnBuild'), + + 'AutoLoad' => true, + + 'QueryString' => Array ( + 1 => 'id', + 2 => 'Page', + 3 => 'event', + 4 => 'mode', + ), + + 'IDField' => 'SessionLogId', + 'StatusField' => Array ('Status'), + + 'TableName' => TABLE_PREFIX.'SessionLogs', + + 'PermSection' => Array('main' => 'in-portal:session_logs'), + + // don't forget to add corresponding permissions to install script + // INSERT INTO Permissions VALUES (0, 'in-portal:session_logs.view', 11, 1, 1, 0), (0, 'in-portal:session_logs.delete', 11, 1, 1, 0); + 'Sections' => Array ( + 'in-portal:session_logs' => Array ( + 'parent' => 'in-portal:reports', + 'icon' => 'conf_general', + 'label' => 'la_tab_SessionLogs', + 'url' => Array('t' => 'logs/session_logs/session_log_list', 'pass' => 'm'), + 'permissions' => Array('view', 'delete'), + 'priority' => 0.1, + 'show_mode' => smSUPER_ADMIN, + 'type' => stTREE, + ), + ), + + 'TitleField' => 'SessionLogId', + + 'ListSQLs' => Array ( + '' => ' SELECT %1$s.* %2$s + FROM %1$s + LEFT JOIN '.TABLE_PREFIX.'PortalUser AS u ON u.PortalUserId = %1$s.PortalUserId', + ), + + 'ListSortings' => Array ( + '' => Array ( + 'Sorting' => Array ('SessionLogId' => 'desc'), + ) + ), + + 'CalculatedFields' => Array( + '' => Array( + 'UserLogin' => 'IF(%1$s.PortalUserId=-1, \'root\', u.Login)', + 'UserFirstName' => 'u.FirstName', + 'UserLastName' => 'u.LastName', + 'UserEmail' => 'u.Email', + 'Duration' => 'IFNULL(SessionEnd, UNIX_TIMESTAMP())-SessionStart', + ), + ), + + 'Fields' => Array ( + 'SessionLogId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'PortalUserId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'SessionId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'Status' => Array ( + 'type' => 'int', 'formatter' => 'kOptionsFormatter', + 'options'=> array(0=>'la_Active',1=>'la_LoggedOut',2=>'la_Expired'), + 'use_phrases' => 1, + 'not_null' => 1, 'default' => 1 + ), + 'SessionStart' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'not_null' => 1, 'time_format' => 'H:i:s', 'default' => 0), + 'SessionEnd' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'time_format' => 'H:i:s', 'default' => NULL), + 'IP' => Array ('type' => 'string', 'max_len' => 15, 'not_null' => 1, 'default' => ''), + 'AffectedItems' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + ), + + 'VirtualFields' => Array( + 'Duration' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'not_null' => 1, 'date_format' => '', 'time_format' => 'H:i:s', 'use_timezone' => false, 'default' => 0), + ), + + 'Grids' => Array ( + 'Default' => Array ( + 'Fields' => Array ( + 'SessionLogId' => Array ('title' => 'la_col_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', ), + 'PortalUserId' => Array ('title' => 'la_col_PortalUserId', 'filter_block' => 'grid_like_filter',), + 'UserLogin' => Array ('title' => 'la_col_Username', 'filter_block' => 'grid_like_filter',), + 'UserFirstName' => Array ('title' => 'la_col_FirstName', 'filter_block' => 'grid_like_filter',), + 'UserLastName' => Array ('title' => 'la_col_LastName', 'filter_block' => 'grid_like_filter',), + 'SessionStart' => Array ('title' => 'la_col_SessionStart', 'filter_block' => 'grid_date_range_filter', ), + 'SessionEnd' => Array ('title' => 'la_col_SessionEnd', 'filter_block' => 'grid_date_range_filter', ), + 'Duration' => Array ('title' => 'la_col_Duration', 'filter_block' => 'grid_range_filter', ), + 'Status' => Array ('title' => 'la_col_Status', 'filter_block' => 'grid_options_filter', ), + 'IP' => Array ('title' => 'la_col_IP', 'filter_block' => 'grid_like_filter',), + 'AffectedItems' => Array ('title' => 'la_col_AffectedItems', 'data_block' => 'affected_td'), + ), + ), + ), + + 'ConfigMapping' => Array( + 'PerPage' => 'Perpage_SessionLogs', + ), + ); + + +/* !!! Copy the rest of the file to appropriate files and templates + * !!! DON'T FORGET TO CREAT FIELDS AND GRIDS USING SYSTEM TOOLS SECTION !!! + + /* + +Don't forget to: + +- Add table create statement to install_schema.sql + CREATE TABLE SessionLogs ( + `SessionLogId` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , + `Title` VARCHAR( 255 ) NOT NULL , + `Description` TEXT NULL , + `Email` VARCHAR( 255 ) NOT NULL , + `Type` TINYINT NOT NULL , + `Phone` VARCHAR( 50 ) NOT NULL , + `Qty` DOUBLE NOT NULL , + `Status` TINYINT NOT NULL , + `CreatedOn` INT NOT NULL , + `Good` TINYINT NOT NULL +) + +- Add permissions for admin gorup to install script (see 'Sections' key above) + + +*/ \ No newline at end of file Index: branches/RC/core/units/pdf/pdf_renderer_zend.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/pdf_renderer_zend.php (revision 0) +++ branches/RC/core/units/pdf/pdf_renderer_zend.php (revision 10294) @@ -0,0 +1,117 @@ +PDF = new Zend_Pdf(); + $this->PDF->pages[] = ($page1 = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4)); + $this->CurPage = $page1; + } + + function GetWidth() + { + return $this->CurPage->getWidth(); + } + + function GetHeight() + { + return $this->CurPage->getHeight(); + } + + function SetFont($font, $size) + { + $mapping = array( + 'helvetica' => Zend_Pdf_Font::FONT_HELVETICA, + ); + $use_font = Zend_Pdf_Font::FONT_TIMES; + foreach ($mapping as $pattern => $zend_font) { + if (preg_match('/^'.$pattern.'$/i', $font)) { + $use_font = $zend_font; + break; + } + } + $this->CurFont = Zend_Pdf_Font::fontWithName($use_font); + $this->CurFontSize = $size; + return $this->CurPage->setFont($this->CurFont, $size); + } + + function SetFontSize($size) + { + $this->SetFont($this->CurFont, $size); + } + + function SetFillColor($color) + { + return $this->CurPage->setFillColor( new Zend_Pdf_Color_HTML($color) ); + } + + function SetLineColor($color) + { + return $this->CurPage->setLineColor( new Zend_Pdf_Color_HTML($color) ); + } + + function SetLineWidth($width) + { + return $this->CurPage->setLineWidth($width); + } + + function DrawLine($x1, $y1, $x2, $y2) + { + return $this->CurPage->drawLine($x1, $y1, $x2, $y2); + } + + function DrawRectangle($x1, $y1, $x2, $y2, $mode) + { + return $this->CurPage->drawRectangle($x1, $y1, $x2, $y2, $mode); + } + + function DrawText($x, $y, $text) + { + return $this->CurPage->drawText($x, $y, $text); + } + + function GetPDFString() + { + return $this->PDF->render(); + } + + function GetAscent() + { + return ($this->CurFont->getAscent() / $this->CurFont->getUnitsPerEm()) * $this->CurFontSize; + } + + function GetDescent() + { + return ($this->CurFont->getDescent() / $this->CurFont->getUnitsPerEm()) * $this->CurFontSize; + } + + function GetLineGap() + { + return ($this->CurFont->getLineGap() / $this->CurFont->getUnitsPerEm()) * $this->CurFontSize; + } + + function GetStringWidth($string) + { + $drawingString = iconv('UTF-8', 'UTF-16BE//IGNORE', $string); + $characters = array(); + for ($i = 0; $i < strlen($drawingString); $i++) { + $characters[] = (ord($drawingString[$i++]) << 8) | ord($drawingString[$i]); + } + $glyphs = $this->CurFont->cmap->glyphNumbersForCharacters($characters); + $widths = $this->CurFont->widthsForGlyphs($glyphs); + return (array_sum($widths) / $this->CurFont->getUnitsPerEm()) * $this->CurFontSize; + } +} \ No newline at end of file Index: branches/RC/core/admin_templates/incs/form_blocks.tpl =================================================================== diff -u -N -r10096 -r10294 --- branches/RC/core/admin_templates/incs/form_blocks.tpl (.../form_blocks.tpl) (revision 10096) +++ branches/RC/core/admin_templates/incs/form_blocks.tpl (.../form_blocks.tpl) (revision 10294) @@ -613,7 +613,10 @@ + + + Index: branches/RC/core/units/logs/change_logs/change_logs_config.php =================================================================== diff -u -N --- branches/RC/core/units/logs/change_logs/change_logs_config.php (revision 0) +++ branches/RC/core/units/logs/change_logs/change_logs_config.php (revision 10294) @@ -0,0 +1,158 @@ + 'change-log', + 'ItemClass' => Array('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'), + 'ListClass' => Array('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'), + 'EventHandlerClass' => Array ('class' => 'kDBEventHandler', 'file' => '', 'build_event' => 'OnBuild'), + 'TagProcessorClass' => Array ('class' => 'kDBTagProcessor', 'file' => '', 'build_event' => 'OnBuild'), + + 'RegisterClasses' => Array ( + Array ('pseudo' => 'kChangesFormatter', 'class' => 'kChangesFormatter', 'file' => 'changes_formatter.php', 'build_event' => '', 'require_classes' => 'kFormatter'), + ), + + 'AutoLoad' => true, + + 'QueryString' => Array ( + 1 => 'id', + 2 => 'Page', + 3 => 'event', + 4 => 'mode', + ), + + 'IDField' => 'ChangeLogId', + 'StatusField' => Array ('Status'), + 'TableName' => TABLE_PREFIX.'ChangeLogs', + + 'TitlePresets' => Array ( + 'default' => Array ( + 'new_status_labels' => Array ('change-log' => '!la_title_AddingChangeLog!'), + 'edit_status_labels' => Array ('change-log' => '!la_title_EditingChangeLog!'), + ), + + 'changelog_edit' => Array ('prefixes' => Array('change-log'), 'format' => '#change-log_status# #change-log_titlefield#',), + ), + + 'PermSection' => Array ('main' => 'in-portal:change_logs'), + + // don't forget to add corresponding permissions to install script + // INSERT INTO Permissions VALUES (0, 'in-portal:change_logs.view', 11, 1, 1, 0), (0, 'in-portal:change_logs.add', 11, 1, 1, 0), (0, 'in-portal:change_logs.edit', 11, 1, 1, 0), (0, 'in-portal:change_logs.delete', 11, 1, 1, 0); + 'Sections' => Array ( + 'in-portal:change_logs' => Array ( + 'parent' => 'in-portal:reports', + 'icon' => 'in-portal:change_logs', + 'label' => 'la_tab_ChangeLog', + 'url' => Array('t' => 'logs/change_logs/change_log_list', 'pass' => 'm'), + 'permissions' => Array('view', 'edit', 'delete'), + 'priority' => 0.2, + 'show_mode' => smSUPER_ADMIN, + 'type' => stTREE, + ), + ), + + 'TitleField' => 'ChangeLogId', + + 'ListSQLs' => Array ( + '' => ' SELECT %1$s.* %2$s + FROM %1$s + LEFT JOIN '.TABLE_PREFIX.'PortalUser AS u ON u.PortalUserId = %1$s.PortalUserId', + ), + + 'ListSortings' => Array ( + '' => Array ( + 'Sorting' => Array ('OccuredOn' => 'desc'), + ) + ), + + 'CalculatedFields' => Array ( + '' => Array ( + 'UserLogin' => 'IF(%1$s.PortalUserId = -1, \'root\', u.Login)', + 'UserFirstName' => 'u.FirstName', + 'UserLastName' => 'u.LastName', + 'UserEmail' => 'u.Email', + ), + ), + + 'Fields' => Array ( + 'ChangeLogId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'PortalUserId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'SessionLogId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'Action' => Array ( + 'type' => 'int', 'formatter' => 'kOptionsFormatter', + 'options' => array (clCREATE => 'la_opt_ActionCreate', clUPDATE => 'la_opt_ActionUpdate', clDELETE => 'la_opt_ActionDelete'), + 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), + 'OccuredOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'time_format' => 'H:i:s', 'not_null' => 1, 'default' => 0), + 'Prefix' => Array ( + 'type' => 'string', 'formatter' => 'kOptionsFormatter', + 'options_sql' => 'SELECT DISTINCT %s FROM '.TABLE_PREFIX.'ChangeLogs ORDER BY Phrase', + 'option_key_field' => 'Prefix', + 'option_title_field' => 'CONCAT(\'la_prefix_\', Prefix) AS Phrase', + 'use_phrases' => 1, + 'max_len' => 255, 'not_null' => 1, 'default' => '' + ), + 'ItemId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'Changes' => Array ('type' => 'string', 'formatter' => 'kChangesFormatter', 'not_null' => 1, 'default' => ''), + 'MasterPrefix' => Array ( + 'type' => 'string', 'formatter' => 'kOptionsFormatter', + 'options_sql' => 'SELECT DISTINCT %s FROM '.TABLE_PREFIX.'ChangeLogs ORDER BY Phrase', + 'option_key_field' => 'MasterPrefix', + 'option_title_field' => 'CONCAT(\'la_prefix_\',MasterPrefix) AS Phrase', + 'use_phrases' => 1, + 'max_len' => 255, 'not_null' => 1, 'default' => '' + ), + 'MasterId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + ), + + 'Grids' => Array ( + 'Default' => Array ( + 'Fields' => Array ( + 'ChangeLogId' => Array ('title' => 'la_col_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter',), + 'PortalUserId' => Array ('title' => 'la_col_PortalUserId', 'filter_block' => 'grid_like_filter',), + 'UserLogin' => Array ('title' => 'la_col_Username', 'filter_block' => 'grid_like_filter',), + 'UserFirstName' => Array ('title' => 'la_col_FirstName', 'filter_block' => 'grid_like_filter',), + 'UserLastName' => Array ('title' => 'la_col_LastName', 'filter_block' => 'grid_like_filter',), + 'SessionLogId' => Array ('title' => 'la_col_SessionLogId', 'filter_block' => 'grid_like_filter',), + 'Action' => Array ('title' => 'la_col_Action', 'filter_block' => 'grid_options_filter', ), + 'OccuredOn' => Array ('title' => 'la_col_OccuredOn', 'filter_block' => 'grid_date_range_filter',), + 'MasterPrefix' => Array ('title' => 'la_col_MasterPrefix', 'filter_block' => 'grid_options_filter', ), + 'MasterId' => Array ('title' => 'la_col_MasterId', 'filter_block' => 'grid_range_filter',), + 'Prefix' => Array ('title' => 'la_col_ItemPrefix', 'filter_block' => 'grid_options_filter', ), + 'ItemId' => Array ('title' => 'la_col_ItemId', 'filter_block' => 'grid_range_filter',), + 'Changes' => Array ('title' => 'la_col_Changes', 'data_block' => 'grid_changes_td', 'filter_block' => 'grid_like_filter',), + ), + ), + ), + + 'ConfigMapping' => Array( + 'PerPage' => 'Perpage_ChangeLog', + ), + ); + +/* !!! Copy the rest of the file to appropriate files and templates + * !!! DON'T FORGET TO CREAT FIELDS AND GRIDS USING SYSTEM TOOLS SECTION !!! + + +/* + +Don't forget to: + +- Add table create statement to install_schema.sql + CREATE TABLE ChangeLogs ( + `ChangeLogId` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , + `Title` VARCHAR( 255 ) NOT NULL , + `Description` TEXT NULL , + `Email` VARCHAR( 255 ) NOT NULL , + `Type` TINYINT NOT NULL , + `Phone` VARCHAR( 50 ) NOT NULL , + `Qty` DOUBLE NOT NULL , + `Status` TINYINT NOT NULL , + `CreatedOn` INT NOT NULL , + `Good` TINYINT NOT NULL +) + +- Add permissions for admin gorup to install script (see 'Sections' key above) + + +*/ \ No newline at end of file Index: branches/RC/core/kernel/kbase.php =================================================================== diff -u -N -r9230 -r10294 --- branches/RC/core/kernel/kbase.php (.../kbase.php) (revision 9230) +++ branches/RC/core/kernel/kbase.php (.../kbase.php) (revision 10294) @@ -557,14 +557,26 @@ } } + /** + * Escapes fields only, not expressions + * + * @param string $field_expr + * @return string + */ + function escapeField($field_expr) + { + return preg_match('/[.(]/', $field_expr) ? $field_expr : '`'.$field_expr.'`'; + } + function PrepareFieldOptions($field_name) { $field_options =& $this->Fields[$field_name]; - if( isset($field_options['options_sql']) ) - { + if (array_key_exists('options_sql', $field_options) ) { // replace with query result $language_id = $this->Application->GetVar('m_lang'); - $select_clause = '`'.$field_options['option_title_field'].'`,`'.$field_options['option_key_field'].'`'; + + $select_clause = $this->escapeField($field_options['option_title_field']) . ',' . $this->escapeField($field_options['option_key_field']); + $sql = sprintf($field_options['options_sql'], $select_clause, $language_id); $sql = str_replace('%2$s', $language_id, $sql); // replace langauge in field names Index: branches/RC/core/units/pdf/css_defaults.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/css_defaults.php (revision 0) +++ branches/RC/core/units/pdf/css_defaults.php (revision 10294) @@ -0,0 +1,652 @@ + array( + 'initial' =>'scroll', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BACKGROUND-COLOR' => array( + 'initial' =>'transparent', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BACKGROUND-IMAGE' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BACKGROUND-POSITION' => array( + 'initial' =>'0% 0%', + 'applies' => '', + 'inherited' => false, + 'percentages' => 'refer to the size of the box itself' + ), + 'BACKGROUND-REPEAT' => array( + 'initial' =>'repeat', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BACKGROUND' => array( + 'initial' =>'see individual properties', + 'applies' => '', + 'inherited' => false, + 'percentages' => 'allowed on background-position' + ), + 'BORDER-COLLAPSE' => array( + 'initial' =>'separate', + 'applies' => 'table and inline-table elements', + 'inherited' => true, + 'percentages' => '' + ), + 'BORDER-COLOR' => array( + 'initial' =>'see individual properties', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-SPACING' => array( + 'initial' =>'0', + 'applies' => 'table and inline-table elements�', + 'inherited' => true, + 'percentages' => '' + ), + 'BORDER-STYLE' => array( + 'initial' =>'see individual properties', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-TOP-COLOR' => array( + 'initial' =>'black', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-RIGHT-COLOR' => array( + 'initial' =>'black', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-BOTTOM-COLOR' => array( + 'initial' =>'black', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-LEFT-COLOR' => array( + 'initial' =>'black', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-TOP-STYLE' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-RIGHT-STYLE' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-BOTTOM-STYLE' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-LEFT-STYLE' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-TOP-WIDTH' => array( + 'initial' =>'medium', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-RIGHT-WIDTH' => array( + 'initial' =>'medium', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-BOTTOM-WIDTH' => array( + 'initial' =>'medium', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-LEFT-WIDTH' => array( + 'initial' =>'medium', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER-WIDTH' => array( + 'initial' =>'see individual properties', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BORDER' => array( + 'initial' =>'see individual properties', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'BOTTOM' => array( + 'initial' =>'auto', + 'applies' => 'positioned elements', + 'inherited' => false, + 'percentages' => 'refer to height of containing block' + ), + 'CAPTION-SIDE' => array( + 'initial' =>'top', + 'applies' => 'table-caption elements', + 'inherited' => true, + 'percentages' => '' + ), + 'CLEAR' => array( + 'initial' =>'none', + 'applies' => 'block-level elements', + 'inherited' => false, + 'percentages' => '' + ), + 'CLIP' => array( + 'initial' =>'auto', + 'applies' => 'absolutely positioned elements', + 'inherited' => false, + 'percentages' => '' + ), + 'COLOR' => array( + 'initial' =>'black', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'CONTENT' => array( + 'initial' =>'normal', + 'applies' => ':before and :after pseudo-elements', + 'inherited' => false, + 'percentages' => '' + ), + 'COUNTER-INCREMENT' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'COUNTER-RESET' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'CURSOR' => array( + 'initial' =>'auto', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'DIRECTION' => array( + 'initial' =>'ltr', + 'applies' => '"all elements, but see prose"', + 'inherited' => true, + 'percentages' => '' + ), + 'DISPLAY' => array( + 'initial' =>'inline', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'EMPTY-CELLS' => array( + 'initial' =>'show', + 'applies' => 'table-cell elements', + 'inherited' => true, + 'percentages' => '' + ), + 'FLOAT' => array( + 'initial' =>'none', + 'applies' => '"all, but see 9.7"', + 'inherited' => false, + 'percentages' => '' + ), + 'FONT-FAMILY' => array( + 'initial' =>'helvetica', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'FONT-SIZE' => array( + 'initial' => '10', //'medium', + 'applies' => '', + 'inherited' => true, + 'percentages' => 'refer to parent element\'s font size' + ), + 'FONT-STYLE' => array( + 'initial' =>'normal', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'FONT-VARIANT' => array( + 'initial' =>'normal', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'FONT-WEIGHT' => array( + 'initial' =>'normal', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'FONT' => array( + 'initial' =>'see individual properties', + 'applies' => '', + 'inherited' => true, + 'percentages' => 'see individual properties' + ), + 'HEIGHT' => array( + 'initial' =>'auto', + 'applies' => '"all elements but non-replaced inline elements, table columns, and column groups"', + 'inherited' => false, + 'percentages' => 'see prose' + ), + 'LEFT' => array( + 'initial' =>'auto', + 'applies' => 'positioned elements', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'LETTER-SPACING' => array( + 'initial' =>'normal', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'LINE-HEIGHT' => array( + 'initial' =>'normal', + 'applies' => '', + 'inherited' => true, + 'percentages' => 'refer to the font size of the element itself' + ), + 'LIST-STYLE-IMAGE' => array( + 'initial' =>'none', + 'applies' => 'elements with display: list-item', + 'inherited' => true, + 'percentages' => '' + ), + 'LIST-STYLE-POSITION' => array( + 'initial' =>'outside', + 'applies' => 'elements with display: list-item', + 'inherited' => true, + 'percentages' => '' + ), + 'LIST-STYLE-TYPE' => array( + 'initial' =>'disc', + 'applies' => 'elements with display: list-item', + 'inherited' => true, + 'percentages' => '' + ), + 'LIST-STYLE' => array( + 'initial' =>'see individual properties', + 'applies' => 'elements with display: list-item', + 'inherited' => true, + 'percentages' => '' + ), + 'MARGIN-RIGHT' => array( + 'initial' =>'0', + 'applies' => 'all elements except elements with table display types other than table and inline-table', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'MARGIN-LEFT' => array( + 'initial' =>'0', + 'applies' => 'all elements except elements with table display types other than table and inline-table', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'MARGIN-TOP' => array( + 'initial' =>'0', + 'applies' => 'all elements except elements with table display types other than table and inline-table', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'MARGIN-BOTTOM' => array( + 'initial' =>'0', + 'applies' => 'all elements except elements with table display types other than table and inline-table', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'MARGIN' => array( + 'initial' =>'see individual properties', + 'applies' => 'all elements except elements with table display types other than table and inline-table', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'MAX-HEIGHT' => array( + 'initial' =>'none', + 'applies' => '"all elements but non-replaced inline elements, table columns, and column groups"', + 'inherited' => false, + 'percentages' => 'see prose' + ), + 'MAX-WIDTH' => array( + 'initial' =>'none', + 'applies' => '"all elements but non-replaced inline elements, table rows, and row groups"', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'MIN-HEIGHT' => array( + 'initial' =>'0', + 'applies' => '"all elements but non-replaced inline elements, table columns, and column groups"', + 'inherited' => false, + 'percentages' => 'see prose' + ), + 'MIN-WIDTH' => array( + 'initial' =>'0', + 'applies' => '"all elements but non-replaced inline elements, table rows, and row groups"', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'ORPHANS' => array( + 'initial' =>'2', + 'applies' => 'block-level elements', + 'inherited' => true, + 'percentages' => '' + ), + 'OUTLINE-COLOR' => array( + 'initial' =>'invert', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'OUTLINE-STYLE' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'OUTLINE-WIDTH' => array( + 'initial' =>'medium', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'OUTLINE' => array( + 'initial' =>'see individual properties', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'OVERFLOW' => array( + 'initial' =>'visible', + 'applies' => '"non-replaced block-level elements, table cells, and inline-block elements"', + 'inherited' => false, + 'percentages' => '' + ), + 'PADDING-TOP' => array( + 'initial' =>'0', + 'applies' => '"all elements except elements with table display types other than table, inline-table, and table-cell"', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'PADDING-RIGHT' => array( + 'initial' =>'0', + 'applies' => '"all elements except elements with table display types other than table, inline-table, and table-cell"', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'PADDING-BOTTOM' => array( + 'initial' =>'0', + 'applies' => '"all elements except elements with table display types other than table, inline-table, and table-cell"', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'PADDING-LEFT' => array( + 'initial' =>'0', + 'applies' => '"all elements except elements with table display types other than table, inline-table, and table-cell"', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'PADDING' => array( + 'initial' =>'see individual properties', + 'applies' => '"all elements except elements with table display types other than table, inline-table, and table-cell"', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'PAGE-BREAK-AFTER' => array( + 'initial' =>'auto', + 'applies' => 'block-level elements', + 'inherited' => false, + 'percentages' => '' + ), + 'PAGE-BREAK-BEFORE' => array( + 'initial' =>'auto', + 'applies' => 'block-level elements', + 'inherited' => false, + 'percentages' => '' + ), + 'PAGE-BREAK-INSIDE' => array( + 'initial' =>'auto', + 'applies' => 'block-level elements', + 'inherited' => true, + 'percentages' => '' + ), + 'POSITION' => array( + 'initial' =>'static', + 'applies' => '', + 'inherited' => false, + 'percentages' => '' + ), + 'QUOTES' => array( + 'initial' =>'depends on user agent', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'RIGHT' => array( + 'initial' =>'auto', + 'applies' => 'positioned elements', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'TABLE-LAYOUT' => array( + 'initial' =>'auto', + 'applies' => 'table and inline-table elements', + 'inherited' => false, + 'percentages' => '' + ), + 'TEXT-ALIGN' => array( + 'initial' =>'left if direction is ltr; right if direction is rtl', + 'applies' => '"block-level elements, table cells and inline blocks"', + 'inherited' => true, + 'percentages' => '' + ), + 'TEXT-DECORATION' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => 'no (see prose)', + 'percentages' => '' + ), + 'TEXT-INDENT' => array( + 'initial' =>'0', + 'applies' => '"block-level elements, table cells and inline blocks"', + 'inherited' => true, + 'percentages' => 'refer to width of containing block' + ), + 'TEXT-TRANSFORM' => array( + 'initial' =>'none', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'TOP' => array( + 'initial' =>'auto', + 'applies' => 'positioned elements', + 'inherited' => false, + 'percentages' => 'refer to height of containing block' + ), + 'UNICODE-BIDI' => array( + 'initial' =>'normal', + 'applies' => '"all elements, but see prose"', + 'inherited' => false, + 'percentages' => '' + ), + 'VERTICAL-ALIGN' => array( + 'initial' =>'baseline', + 'applies' => 'inline-level and table-cell elements', + 'inherited' => false, + 'percentages' => 'refer to the line-height of the element itself' + ), + 'VISIBILITY' => array( + 'initial' =>'visible', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'WHITE-SPACE' => array( + 'initial' =>'normal', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'WIDOWS' => array( + 'initial' =>'2', + 'applies' => 'block-level elements', + 'inherited' => true, + 'percentages' => '' + ), + 'WIDTH' => array( + 'initial' =>'auto', + 'applies' => '"all elements but non-replaced inline elements, table rows, and row groups"', + 'inherited' => false, + 'percentages' => 'refer to width of containing block' + ), + 'WORD-SPACING' => array( + 'initial' =>'normal', + 'applies' => '', + 'inherited' => true, + 'percentages' => '' + ), + 'Z-INDEX' => array( + 'initial' =>'auto', + 'applies' => 'positioned elements', + 'inherited' => false, + 'percentages' => '' + ), + ); + + static function IsInherited($property) + { + return kCSSDefaults::$CSS_PROPERTIES[$property]['inherited']; +// return isset(kCSSDefaults::$CSS_PROPERTIES[$property]) ? : false; + } + + static function IsValid($property) + { + return isset(kCSSDefaults::$CSS_PROPERTIES[$property]); + } + + static function GetDefaultValue($property) + { + return kCSSDefaults::$CSS_PROPERTIES[$property]['initial']; +// return isset(kCSSDefaults::$CSS_PROPERTIES[$property]) ? kCSSDefaults::$CSS_PROPERTIES[$property]['initial'] : false; + } + +} \ No newline at end of file Index: branches/RC/core/units/pdf/pdf_styles.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/pdf_styles.php (revision 0) +++ branches/RC/core/units/pdf/pdf_styles.php (revision 10294) @@ -0,0 +1,877 @@ +Prepare(); + if (!$nodefaults) { + $tokens = $this->GetTokens(kCSSDefaults::$DEFAULT_STYLE); + $this->ParseTokens($tokens, kPDFStylesheet::STYLE_ORIGIN_AGENT_NORMAL); + $this->HTMLVisualPropsSelectorOrder = $this->SelectorOrder; + $this->SelectorOrder += 1000; + } + } + + public function ParseStyle($style) + { + $res = array(); + $pairs = explode(';', $style); + foreach ($pairs as $property) { + $property = trim($property); + list($name, $value) = explode(':', $property); + $res[trim($name)] = trim($value); + } + } + + /* + stylesheet : [ CDO | CDC | S | statement ]*; + statement : ruleset | at-rule; + at-rule : ATKEYWORD S* any* [ block | ';' S* ]; + block : '{' S* [ any | block | ATKEYWORD S* | ';' S* ]* '}' S*; + ruleset : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*; + selector : any+; + declaration : DELIM? property S* ':' S* value; + property : IDENT; + value : [ any | block | ATKEYWORD S* ]+; + any : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING + | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES + | DASHMATCH | FUNCTION S* any* ')' + | '(' S* any* ')' | '[' S* any* ']' ] S*; + */ + + function ParseTokens($tokens, $origin=kPDFStylesheet::STYLE_ORIGIN_AUTHOR_NORMAL ) + { + $this->Buffer[0] = Array(); + foreach ($tokens as $token) { + if ($token['name'] == 'LBRACE') { + $this->Buffer[++$this->Level] = Array(); + $this->Openings[$this->Level] = 'LBRACE'; + } + elseif ($token['name'] == 'TEXT' && $token['data'] == '}') { + if ($this->Level == 1 && $this->Openings[$this->Level] == 'LBRACE') { + $this->AppendRule($this->Buffer[0], $this->Buffer[$this->Level], $origin); + $this->Buffer[0] = Array(); + } + $this->Level--; + } + else { + $this->Buffer[$this->Level][] = $token; + } + } + } + + protected function ConcatTokensData($tokens) + { + $res = ''; + foreach ($tokens as $token) {$res .= $token['data'];} + return $res; + } + + public function ParseDefinitionTokens($tokens) + { + $mode = 'property'; + $properties = array(); + $value = ''; + foreach ($tokens as $token) { + if ($mode == 'property') { + if ($token['name'] == 'IDENT') { + $property = $token['data']; + $mode = 'colon'; + } + } + elseif ($mode == 'colon') { + if ($token['name'] == 'TEXT' && $token['data'] == ':') { + $mode = 'value'; + } + } + elseif ($mode == 'value') { + if ($token['name'] == 'TEXT' && $token['data'] == ';') { + $properties[strtoupper($property)] = trim($value); + $value = ''; + $mode = 'property'; + } + else { + $value .= $token['data']; + } + } + } + if ($mode == 'value') { + $properties[strtoupper($property)] = trim($value); + } + if ($mode == 'colon') { + trigger_error('Error parsing CSS definition, no colon and/or value after property '.$property, E_USER_WARNING); + } + + $properties = $this->ProcessShortHands($properties); + + return $properties; + } + + public function ProcessShortHands($properties) + { + $res = array(); + foreach ($properties as $property => $value) + { + switch ($property) { + case 'MARGIN': + if (preg_match('/^([.0-9]+(?:px|pt|em|ex|%)?|auto)$/i', $value, $regs)) { + $res['MARGIN-TOP'] = $regs[1]; + $res['MARGIN-RIGHT'] = $regs[1]; + $res['MARGIN-BOTTOM'] = $regs[1]; + $res['MARGIN-LEFT'] = $regs[1]; + } + if (preg_match('/^([.0-9]+(?:px|pt|em|ex|%)?|auto) ([.0-9]+(?:px|pt|em|ex|%)?|auto)$/i', $value, $regs)) { + $res['MARGIN-TOP'] = $regs[1]; + $res['MARGIN-RIGHT'] = $regs[2]; + $res['MARGIN-BOTTOM'] = $regs[1]; + $res['MARGIN-LEFT'] = $regs[2]; + } + if (preg_match('/^([.0-9]+(?:px|pt|em|ex|%)?|auto) ([.0-9]+(?:px|pt|em|ex|%)?|auto) ([.0-9]+(?:px|pt|em|ex|%)?|auto) ([.0-9]+(?:px|pt|em|ex|%)?|auto)$/i', $value, $regs)) { + $res['MARGIN-TOP'] = $regs[1]; + $res['MARGIN-RIGHt'] = $regs[2]; + $res['MARGIN-BOTTOM'] = $regs[3]; + $res['MARGIN-LEFT'] = $regs[4]; + } + + break; + case 'BORDER-TOP': + case 'BORDER-RIGHT': + case 'BORDER-BOTTOM': + case 'BORDER-LEFT': + $parts = $this->ParseBorderShorthand($value); + if (isset($parts['style'])) { + $res[$property.'-STYLE'] = $parts['style']; + } + if (isset($parts['width'])) { + $res[$property.'-WIDTH'] = $parts['width']; + } + if (isset($parts['color'])) { + $res[$property.'-COLOR'] = $parts['color']; + } + break; + case 'BORDER': + $parts = $this->ParseBorderShorthand($value); + if (isset($parts['style'])) { + $res['BORDER-TOP-STYLE'] = $parts['style']; + $res['BORDER-RIGHT-STYLE'] = $parts['style']; + $res['BORDER-BOTTOM-STYLE'] = $parts['style']; + $res['BORDER-LEFT-STYLE'] = $parts['style']; + } + if (isset($parts['width'])) { + $res['BORDER-TOP-WIDTH'] = $parts['width']; + $res['BORDER-RIGHT-WIDTH'] = $parts['width']; + $res['BORDER-BOTTOM-WIDTH'] = $parts['width']; + $res['BORDER-LEFT-WIDTH'] = $parts['width']; + } + if (isset($parts['color'])) { + $res['BORDER-TOP-COLOR'] = $parts['color']; + $res['BORDER-RIGHT-COLOR'] = $parts['color']; + $res['BORDER-BOTTOM-COLOR'] = $parts['color']; + $res['BORDER-LEFT-COLOR'] = $parts['color']; + } + break; + case 'PADDING': + $parts = explode(' ', $value); + switch (count($parts)) { + case 1: + $res['PADDING-TOP'] = $parts[0]; + $res['PADDING-RIGHT'] = $parts[0]; + $res['PADDING-BOTTOM'] = $parts[0]; + $res['PADDING-LEFT'] = $parts[0]; + break; + case 2: + $res['PADDING-TOP'] = $parts[0]; + $res['PADDING-RIGHT'] = $parts[1]; + $res['PADDING-BOTTOM'] = $parts[0]; + $res['PADDING-LEFT'] = $parts[1]; + break; + case 3: + $res['PADDING-TOP'] = $parts[0]; + $res['PADDING-RIGHT'] = $parts[1]; + $res['PADDING-BOTTOM'] = $parts[2]; + $res['PADDING-LEFT'] = $parts[1]; + break; + case 4: + $res['PADDING-TOP'] = $parts[0]; + $res['PADDING-RIGHT'] = $parts[1]; + $res['PADDING-BOTTOM'] = $parts[2]; + $res['PADDING-LEFT'] = $parts[3]; + break; + } + break; + default: + $res[$property] = $value; + } + } + return $res; + } + + public function ParseBorderShorthand($definition) + { + $res = array(); + $parts = explode(' ', $definition); + foreach ($parts as $part) { + if (preg_match('/none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset/', $part)) { //style + $res['style'] = $part; + } + elseif (preg_match('/^(thin|medium|thick|[.0-9]+(?:px|pt|em|ex|%)?)/', $part)) { // width + $res['width'] = $part; + } + else { // color + $res['color'] = $part; + } + } + return $res; + } + + public function ParseSelectorTokens($tokens, $origin) + { + $selectors = array(); + $current = ''; + foreach ($tokens as $token) { + if ($token['name'] == 'COMMA') { + $selectors[] = trim($current); + $current = ''; + } + else { + $current .= $token['data']; + } + } + if (trim($current) != '') { + $selectors[] = trim($current); + } + return $this->IdentifySelectors($selectors, $origin); + } + + /* + + 'h' => '[0-9a-f]', + 'nonascii' => '[\\200-\\377]', + 'unicode' => '(\\{h}{1,6}(\r\n|[ \t\r\n\f])?)', + 'escape' => '(\\[^\r\n\f0-9a-f])', + 'nmstart' => '([_a-z]|{nonascii}|{escape})', + 'nmchar' => '([_a-z0-9-]|{nonascii}|{escape})', + 'string1' => '("([^\n\r\f"]|{nl}|{escape})*")', + 'string2' => '(\'([^\n\r\f\']|{nl}|{escape})*\')', + 'invalid1' => '("([^\n\r\f"]|{nl}|{escape})*?)', + 'invalid2' => '(\'([^\n\r\f\']|{nl}|{escape})*?)', + + 'ident' => '-?{nmstart}{nmchar}*', + 'name' => '{nmchar}+', + 'num' => '([0-9]+|[0-9]*\.[0-9]+)', + 'string' => '({string1}|{string2})', + 'invalid' => '({invalid1}|{invalid2})', + 'url' => '([!#$%&*-~]|{nonascii}|{escape})*', + 's' => '[ \t\r\n\f]', + 'w' => '{s}*', + 'nl' => '(\n|\r\n|\r|\f)', + + */ + + /* + + A simple selector is either a type selector or universal selector followed immediately by zero or more attribute selectors, ID selectors, + or pseudo-classes, in any order. The simple selector matches if all of its components match. + + A selector is a chain of one or more simple selectors separated by combinators. Combinators are: whitespace, ">", and "+". + Whitespace may appear between a combinator and the simple selectors around it. + + A selector's specificity is calculated as follows: + + * count 1 if the selector is a 'style' attribute rather than a selector, 0 otherwise (= a) + (In HTML, values of an element's "style" attribute are style sheet rules. These rules have no selectors, so a=1, b=0, c=0, and d=0.) + * count the number of ID attributes in the selector (= b) + * count the number of other attributes and pseudo-classes in the selector (= c) + * count the number of element names and pseudo-elements in the selector (= d) + + The specificity is based only on the form of the selector. + In particular, a selector of the form "[id=p33]" is counted as an attribute selector (a=0, b=0, c=1, d=0), + even if the id attribute is defined as an "ID" in the source document's DTD. + + Concatenating the four numbers a-b-c-d (in a number system with a large base) gives the specificity. + + */ + + function IdentifySelectors($selectors, $origin) + { + $processed = array(); + $ident = $this->Macros['ident']; + + foreach ($selectors as $selector) { + $parts = preg_split('/[ ]*([ >+])[ ]*/', $selector, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $parsed_selector = array(); + $a = 0; + $b = 0; + $c = 0; + $d = 0; + foreach ($parts as $simple_selector) { + $parsed_part = array(); + if (preg_match('/^([ >+])$/', $simple_selector, $regs)) { + $parsed_part['combinator'] = $regs[1]; + $parsed_selector[] = $parsed_part; + continue; + } + if (preg_match('/^(\*|'.$ident.')/i', $simple_selector, $regs)) { + $main = $regs[1]; + if ($regs[1] != '*') { + $d++; + } + } + else { + $main = '*'; + } + $parsed_part['main'] = strtoupper($main); + if (preg_match_all('/\.('.$ident.')/', $simple_selector, $regs)) { + $parsed_part['classes'] = $regs[1]; + $c += count($regs[1]); + } + if (preg_match_all('/\[([^\]]+)\]/', $simple_selector, $regs)) { + $atts = $regs[1]; + $c += count($regs[1]); + $parsed_atts = array(); + foreach ($atts as $attribute) { + if (preg_match('/^[^=]+$/', $attribute)) { + $parsed_atts['set'][] = strtoupper($attribute); + } + elseif (preg_match('/(.*)\\|=(.*)/', $attribute, $att_regs)) { + $parsed_atts['hypen'][strtoupper($att_regs[1])] = $att_regs[2]; + } + elseif (preg_match('/(.*)~=(.*)/', $attribute, $att_regs)) { + $parsed_atts['space'][strtoupper($att_regs[1])] = $att_regs[2]; + } + elseif (preg_match('/(.*)=(.*)/', $attribute, $att_regs)) { + $parsed_atts['equals'][strtoupper($att_regs[1])] = $att_regs[2]; + } + } + $parsed_part['atts'] = $parsed_atts; + } + if (preg_match_all('/#('.$ident.')/', $simple_selector, $regs)) { + $parsed_part['ids'] = $regs[1]; + $b += count($regs[1]); + } + if (preg_match_all('/:('.$ident.')/', $simple_selector, $regs)) { + $pseudo_classes = array(); + $pseudo_elements = array(); + foreach ($regs[1] as $pseudo) { + if (preg_match('/^(first-line|first-letter|before|after)$/i', $pseudo)) { + $pseudo_elements[] = $pseudo; + } + else { + $pseudo_classes[] = $pseudo; + } + } + if ($pseudo_classes) { + $parsed_part['pseudo_classes'] = $pseudo_classes; + } + if ($pseudo_elements) { + $parsed_part['pseudo_elements'] = $pseudo_elements; + } + $c += count($pseudo_classes); + $d += count($pseudo_elements); + } + $parsed_selector[] = $parsed_part; + } + $parsed_selector = array_reverse($parsed_selector); + + $main = array(); + $cur =& $main; + + foreach ($parsed_selector as $parts) { + if (isset($parts['combinator'])) { + switch ($parts['combinator']) { + case ' ': + $cur =& $cur['descendant_of']; + break; + case '>': + $cur =& $cur['child_of']; + break; + case '+': + $cur =& $cur['sibling_of']; + break; + } + continue; + } + $cur['main'] = $parts['main']; + if (isset($parts['classes'])) { + $cur['classes'] = $parts['classes']; + } + if (isset($parts['ids'])) { + $cur['ids'] = $parts['ids']; + } + if (isset($parts['pseudo_classes'])) { + $cur['pseudo_classes'] = $parts['pseudo_classes']; + } + if (isset($parts['pseudo_elements'])) { + $cur['pseudo_elements'] = $parts['pseudo_elements']; + } + if (isset($parts['atts'])) { + $cur['atts'] = $parts['atts']; + } + } + $main['specifity'] = intval(str_pad($a,2,0).str_pad($b,2,0).str_pad($c,2,0).str_pad($d,2,0)); + $main['order'] = $this->SelectorOrder++; + $main['origin'] = $origin; + $processed[] = $main; + } + return $processed; + } + + public function AppendRule($selector_tokens, $definition_tokens, $origin) + { + $selectors = $this->ParseSelectorTokens($selector_tokens, $origin); + $properties = $this->ParseDefinitionTokens($definition_tokens); + $definition = ''; + foreach ($properties as $property => $value) { + $definition .= "$property: $value
"; + } + + foreach ($selectors as $selector) { + $this->Mapping[strtoupper($selector['main'])][] = array('selector' => $selector, 'properties' => $properties); + } + + $this->Rules[] = array('selectors' => $selectors, 'properties' => $properties); +// echo "appending rule:
selector: ".join(',', $selectors)."
definition:
$definition

"; + } + + public function GetTokens($css) + { + $patterns = array( + '{s}+' =>'S', + + '' =>'CDC', + '~=' =>'INCLUDES', + '\\|=' =>'DASHMATCH', + + '{w}\\{' =>'LBRACE', + '{w}\\+' =>'PLUS', + '{w}\\>' =>'GREATER', + '{w},' =>'COMMA', + + '{string}' =>'STRING', + '{invalid}' =>'INVALID', /* unclosed string */ + + '{ident}' =>'IDENT', + + '#{name}' =>'HASH', + + '@import' =>'IMPORT_SYM', + '@page' =>'PAGE_SYM', + '@media' =>'MEDIA_SYM', + '@charset' =>'CHARSET_SYM', + + '!{w}important' =>'IMPORTANT_SYM', + + /*'{num}{E}{M}' =>'EMS', + '{num}{E}{X}' =>'EXS', + '{num}{P}{X}' =>'LENGTH', + '{num}{C}{M}' =>'LENGTH', + '{num}{M}{M}' =>'LENGTH', + '{num}{I}{N}' =>'LENGTH', + '{num}{P}{T}' =>'LENGTH', + '{num}{P}{C}' =>'LENGTH', + '{num}{D}{E}{G}' =>'ANGLE', + '{num}{R}{A}{D}' =>'ANGLE', + '{num}{G}{R}{A}{D}' =>'ANGLE', + '{num}{M}{S}' =>'TIME', + '{num}{S}' =>'TIME', + '{num}{H}{Z}' =>'FREQ', + '{num}{K}{H}{Z}' =>'FREQ', + '{num}{ident}' =>'DIMENSION',*/ + + '{num}em' =>'EMS', + '{num}ex' =>'EXS', + '{num}px' =>'LENGTH', + '{num}cm' =>'LENGTH', + '{num}mm' =>'LENGTH', + '{num}in' =>'LENGTH', + '{num}pt' =>'LENGTH', + '{num}pc' =>'LENGTH', + '{num}deg' =>'ANGLE', + '{num}rad' =>'ANGLE', + '{num}grad' =>'ANGLE', + '{num}ms' =>'TIME', + '{num}s' =>'TIME', + '{num}hz' =>'FREQ', + '{num}khz' =>'FREQ', + '{num}{ident}' =>'DIMENSION', + + '{num}%' =>'PERCENTAGE', + '{num}' =>'NUMBER', + + 'url\({w}{string}{w}\)' =>'URI', + 'url\({w}{url}{w}\)' =>'URI', + '{ident}\(' =>'FUNCTION', + + /*'.' =>'*yytext',*/ + ); + + $final_patterns = array(); + foreach ($patterns as $regexp => $token) { + foreach ($this->Macros as $macro => $replacement) { + $regexp = str_replace('{'.$macro.'}', $replacement, $regexp); + } + $final_patterns[$regexp] = $token; + } + + $css = preg_replace('/\\/\\*[^*]*\\*+([^\\/*][^*]*\\*+)*\\//', '', $css); + $css = preg_replace('/[ \t\r\n\f]+\\/\\*[^*]*\\*+([^\\/*][^*]*\\*+)*\\//', ' ', $css); + + $css = preg_replace('/[ \t\r\n\f]+/', ' ', $css); // remove repeated whitespace + + $matches = array(); + $token_indexes = array(); + foreach ($final_patterns as $regexp => $token) { + if (preg_match_all('/'.$regexp.'/i', $css, $res, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE)) { + $matches[$token] = $res[0]; + $token_indexes[$token] = 0; + } + } + + $tokens = array(); + $last_token_pos = 0; + $i = 0; + do { + $has_more = false; + $max_len = 0; + $min_pos = false; + foreach ($matches as $token => $data) + { + $cur_index = $token_indexes[$token]; + do { + $cur_match = isset($data[$cur_index]) ? $data[$cur_index++] : false; + } while ($cur_match && $cur_match[1] < $last_token_pos); + if ( !$cur_match ) continue; + $token_indexes[$token] = $cur_index-1; + if ( $min_pos === false || + ($cur_match[1] < $min_pos + || + ( $cur_match[1] == $min_pos && strlen( $cur_match[0] ) > $max_len ) + ) + ) { + $longest = $token; + $max_len = strlen( $cur_match[0] ); + $min_pos = $cur_match[1]; + } + $has_more = $has_more || isset($data[$token_indexes[$token]]); + } + if ($min_pos !== false) { + $token_data = $matches[$longest][$token_indexes[$longest]]; + if ($token_data[1] > $last_token_pos) { + $text_data = substr($css, $last_token_pos, $token_data[1] - $last_token_pos); + $tokens[] = array('name' => 'TEXT', 'data' => $text_data); +// echo "found token TEXT: [$text_data]
\n"; + } + $tokens[] = array('name' => $longest, 'data' => $token_data[0]); +// echo "found token $longest: {$token_data[0]} at {$token_data[1]}
\n"; +// flush(); + $last_token_pos = $token_data[1] + strlen($token_data[0]); + $token_indexes[$longest]++; + } + } while ($has_more); + if ($last_token_pos <= strlen($css)) { + $text_data = substr($css, $last_token_pos); + $tokens[] = array('name' => 'TEXT', 'data' => $text_data); +// echo "found token FINAL TEXT: [$text_data]
\n"; + } + + return $tokens; + } + + public function Prepare() + { + /*$macros = array( + 'h' => '[0-9a-f]', + 'nonascii' => '[\200-\377]', + 'unicode' => '(\\{h}{1,6}(\r\n|[ \t\r\n\f])?)', + 'escape' => '({unicode}|\\[^\r\n\f0-9a-f])', + 'nmstart' => '([_a-z]|{nonascii}|{escape})', + 'nmchar' => '([_a-z0-9-]|{nonascii}|{escape})', + 'string1' => '("([^\n\r\f"]|{nl}|{escape})*")', + 'string2' => '(\'([^\n\r\f\']|{nl}|{escape})*\')', + 'invalid1' => '("([^\n\r\f"]|{nl}|{escape})*?)', + 'invalid2' => '(\'([^\n\r\f\']|{nl}|{escape})*?)', + + 'ident' => '-?{nmstart}{nmchar}*', + 'name' => '{nmchar}+', + 'num' => '([0-9]+|[0-9]*\.[0-9]+)', + 'string' => '({string1}|{string2})', + 'invalid' => '({invalid1}|{invalid2})', + 'url' => '([!#$%&*-~]|{nonascii}|{escape})*', + 's' => '[ \t\r\n\f]', + 'w' => '{s}*', + 'nl' => '(\n|\r\n|\r|\f)', + + 'A' => 'a|\\0{0,4}(41|61)(\r\n|[ \t\r\n\f])?', + 'C' => 'c|\\0{0,4}(43|63)(\r\n|[ \t\r\n\f])?', + 'D' => 'd|\\0{0,4}(44|64)(\r\n|[ \t\r\n\f])?', + 'E' => 'e|\\0{0,4}(45|65)(\r\n|[ \t\r\n\f])?', + 'G' => 'g|\\0{0,4}(47|67)(\r\n|[ \t\r\n\f])?|\\g', + 'H' => 'h|\\0{0,4}(48|68)(\r\n|[ \t\r\n\f])?|\\h', + 'I' => 'i|\\0{0,4}(49|69)(\r\n|[ \t\r\n\f])?|\\i', + 'K' => 'k|\\0{0,4}(4b|6b)(\r\n|[ \t\r\n\f])?|\\k', + 'M' => 'm|\\0{0,4}(4d|6d)(\r\n|[ \t\r\n\f])?|\\m', + 'N' => 'n|\\0{0,4}(4e|6e)(\r\n|[ \t\r\n\f])?|\\n', + 'P' => 'p|\\0{0,4}(50|70)(\r\n|[ \t\r\n\f])?|\\p', + 'R' => 'r|\\0{0,4}(52|72)(\r\n|[ \t\r\n\f])?|\\r', + 'S' => 's|\\0{0,4}(53|73)(\r\n|[ \t\r\n\f])?|\\s', + 'T' => 't|\\0{0,4}(54|74)(\r\n|[ \t\r\n\f])?|\\t', + 'X' => 'x|\\0{0,4}(58|78)(\r\n|[ \t\r\n\f])?|\\x', + 'Z' => 'z|\\0{0,4}(5a|7a)(\r\n|[ \t\r\n\f])?|\\z', + );*/ + + $simple = array( + 'h' => '[0-9a-f]', + 'nonascii' => '[\\200-\\377]', + 'unicode' => '(\\{h}{1,6}(\r\n|[ \t\r\n\f])?)', + 'escape' => '(\\[^\r\n\f0-9a-f])', + 'nmstart' => '([_a-z]|{nonascii}|{escape})', + 'nmchar' => '([_a-z0-9-]|{nonascii}|{escape})', + 'string1' => '("([^\n\r\f"]|{nl}|{escape})*")', + 'string2' => '(\'([^\n\r\f\']|{nl}|{escape})*\')', + 'invalid1' => '("([^\n\r\f"]|{nl}|{escape})*?)', + 'invalid2' => '(\'([^\n\r\f\']|{nl}|{escape})*?)', + + 'ident' => '-?{nmstart}{nmchar}*', + 'name' => '{nmchar}+', + 'num' => '([0-9]+|[0-9]*\.[0-9]+)', + 'string' => '({string1}|{string2})', + 'invalid' => '({invalid1}|{invalid2})', + 'url' => '([!#$%&*-~]|{nonascii}|{escape})*', + 's' => '[ \t\r\n\f]', + 'w' => '{s}*', + 'nl' => '(\n|\r\n|\r|\f)', + ); + $replaced_macros = array(); + foreach ($simple as $key => $macro) { + $replaced = $macro; + foreach ($replaced_macros as $shorthand => $replacement) { + $replaced = str_replace('{'.$shorthand.'}', $replacement, $replaced); + } + $replaced_macros[$key] = $replaced; + } + + $this->Macros = $replaced_macros; + } + + + public function GetHTMLVisualPropsSelector($node) + { + if (!$node->Attributes) return false; + $non_visal_props = array( + 'ABBR', 'ACCEPT-CHARSET', 'ACCEPT', 'ACCESSKEY', 'ACTION', 'ALT', 'ARCHIVE', 'AXIS', 'CHARSET', 'CHECKED', 'CITE', 'CLASS', 'CLASSID', 'CODE', 'CODEBASE', + 'CODETYPE', 'COLSPAN', 'COORDS', 'DATA', 'DATETIME', 'DECLARE', 'DEFER', 'DIR', 'DISABLED', 'ENCTYPE', 'FOR', 'HEADERS', 'HREF', 'HREFLANG', 'HTTP-EQUIV', + 'ID', 'ISMAP', 'LABEL', 'LANG', 'LANGUAGE', 'LONGDESC', 'MAXLENGTH', 'MEDIA', 'METHOD', 'MULTIPLE', 'NAME', 'NOHREF', 'OBJECT', 'ONBLUR', 'ONCHANGE', + 'ONCLICK', 'ONDBLCLICK', 'ONFOCUS', 'ONKEYDOWN', 'ONKEYPRESS', 'ONKEYUP', 'ONLOAD', 'ONLOAD', 'ONMOUSEDOWN', 'ONMOUSEMOVE', 'ONMOUSEOUT', 'ONMOUSEOVER', + 'ONMOUSEUP', 'ONRESET', 'ONSELECT', 'ONSUBMIT', 'ONUNLOAD', 'ONUNLOAD', 'PROFILE', 'PROMPT', 'READONLY', 'REL', 'REV', 'ROWSPAN', 'SCHEME', 'SCOPE', + 'SELECTED', 'SHAPE', 'SPAN', 'SRC', 'STANDBY', 'START', 'STYLE', 'SUMMARY', 'TITLE', 'USEMAP', + 'VALUE', 'VALUETYPE', 'VERSION', + ); + if ($node->Name != 'LI' && $node->Name != 'OL' && $node->Name != 'UL') { + array_push($non_visal_props, 'TYPE'); + } + $visual_attributes = array_diff_key($node->Attributes, array_combine($non_visal_props, array_fill(0, count($non_visal_props), ''))); + if ($visual_attributes) { + $mapping = array( + 'ALIGN' => 'TEXT-ALIGN', + 'VALIGN' => 'VERTICAL-ALIGN', + 'CELLPADDING' => 'PADDING', + ); + $mapped_attributes = array(); + foreach ($visual_attributes as $key => $val) { + if ($key == 'CELLPADDING') { + $processed = $this->IdentifySelectors( array( $node->Name.'[cellpadding='.$val.'] TD' ), kPDFStylesheet::STYLE_ORIGIN_AUTHOR_NORMAL ); + $processed[0]['order'] = $this->HTMLVisualPropsSelectorOrder++; + $processed[0]['specifity'] = 0; + $this->Mapping['TD'][] = array( + 'selector' => $processed[0], + 'properties' => $this->ProcessShortHands(array( + 'PADDING' => $val.'px', + ))); + } + elseif (isset($mapping[$key])) { + $mapped_attributes[$mapping[$key]] = $val; + } + else { + $mapped_attributes[$key] = $val; + } + } + + return array( + 'selector' => array('main' => $node->Name, 'specifity' => 0, 'order' => $this->HTMLVisualPropsSelectorOrder, 'origin' => kPDFStylesheet::STYLE_ORIGIN_AUTHOR_NORMAL ), + 'properties' => $mapped_attributes, + ); + } + return false; + } + + public function GetMatchingSelectors($node) + { + $map = isset($this->Mapping[$node->Name]) ? $this->Mapping[$node->Name] : array(); + if (isset($this->Mapping['*'])) { + $map = array_merge($map, $this->Mapping['*']); + } + + $matching = array(); + $i = 0; + foreach ($map as $selector) { + $selector_data = $selector['selector']; + if ($this->SelectorMatches($selector['selector'], $node)) { + $matching[] = $selector; + } + } + + $html_visual_selector = $this->GetHTMLVisualPropsSelector($node); + if ($html_visual_selector) { + $matching[] = $html_visual_selector; + } + + usort($matching, array($this, 'CmpSelectors')); + + if (isset($node->Attributes['STYLE'])) { + $style_selector = array( + 'selector' => array('main' => '_STYLE_'), + 'properties' => $this->ParseDefinitionTokens ( $this->GetTokens( $node->Attributes['STYLE'] ) ), + ); + $matching[] = $style_selector; + } + + return $matching; + } + + public function CmpSelectors($a, $b) + { + if ($a['selector']['origin'] == $b['selector']['origin']) { + if ($a['selector']['specifity'] == $b['selector']['specifity']) { + return $a['selector']['order'] < $b['selector']['order'] ? -1 : 1; + } + return ($a['selector']['specifity'] < $b['selector']['specifity']) ? -1 : 1; + } + return $a['selector']['origin'] < $b['selector']['origin'] ? -1 : 1; + } + + public function SelectorMatches($selector_data, $node) + { + if ($selector_data['main'] != '*' && $node->Name != $selector_data['main']) { + return false; + } + + //check classes + if (isset($selector_data['classes'])) { + foreach ($selector_data['classes'] as $class) { + // (\A| )+foo( |\Z)+ + if (!isset($node->Attributes['CLASS']) || !preg_match('/(\A| )+'.preg_quote($class).'( |\Z)+/i', $node->Attributes['CLASS'])) { + return false; + } + } + } + + //check ids + if (isset($selector_data['ids'])) { + if (!isset($node->Attributes['ID']) || !in_array($node->Attributes['ID'], $selector_data['ids'])) { + return false;; + } + } + + //check atts + if (isset($selector_data['atts'])) { + if (isset($selector_data['atts']['set'])) { + foreach ($selector_data['atts']['set'] as $att) { + if (!isset($node->Attributes[$att])) { + return false;; + } + } + } + if (isset($selector_data['atts']['equals'])) { + foreach ($selector_data['atts']['equals'] as $att => $value) { + if (!isset($node->Attributes[$att]) || strtoupper($node->Attributes[$att]) != strtoupper($value)) { + return false;; + } + } + } + if (isset($selector_data['atts']['space'])) { + foreach ($selector_data['atts']['space'] as $att => $value) { + if (!isset($node->Attributes[$att]) || !preg_match('/(\A| )+'.preg_quote($value).'( |\Z)+/i', $node->Attributes[$att])) { + return false;; + } + } + } + if (isset($selector_data['atts']['hypen'])) { + foreach ($selector_data['atts']['hypen'] as $att => $value) { + if (!isset($node->Attributes[$att]) || !preg_match('/^'.preg_quote($value).'(-|\Z)+/i', $node->Attributes[$att])) { + return false;; + } + } + } + } + + //check pseudo + if (isset($selector_data['pseudo_elements'])) { + // we are not a browser - so don't know how to handle this.... + return false; + } + + if (isset($selector_data['pseudo_classes'])) { + // we are not a browser - so don't know how to handle this.... + return false; + } + + //check comibantors + if (isset($selector_data['child_of'])) { + if (!$this->SelectorMatches($selector_data['child_of'], $node->Parent)) { + return false; + } + } + + if (isset($selector_data['sibling_of'])) { + if (!$this->SelectorMatches($selector_data['sibling_of'], $node->PrevSibling())) { + return false; + } + } + + if (isset($selector_data['descendant_of'])) { + $ancestor = $node; + do { + $ancestor = $ancestor->Parent; + $matches = $this->SelectorMatches($selector_data['descendant_of'], $ancestor); + } while (!$matches && $ancestor->Parent); + if (!$matches) return false; + } + + // if we came through here, the selector matches the node + return true; + } + + public function GetAllProperties($node) + { + $selectors = $this->GetMatchingSelectors($node); + $properties = array(); + foreach ($selectors as $the_selector) { + $properties = array_merge($properties, $the_selector['properties']); + /*foreach ($the_selector['properties'] as $property => $value) { + $properties[$property] = $value; //process !important here ?? !!! + }*/ + } + return $properties; + } +} \ No newline at end of file Index: branches/RC/core/kernel/db/dbitem.php =================================================================== diff -u -N -r10122 -r10294 --- branches/RC/core/kernel/db/dbitem.php (.../dbitem.php) (revision 10122) +++ branches/RC/core/kernel/db/dbitem.php (.../dbitem.php) (revision 10294) @@ -77,9 +77,22 @@ return $this->DirtyFieldValues[$field_name]; } - function GetOriginalField($field_name) + function GetOriginalField($field_name, $formatted = false, $format=null) { - return $this->OriginalFieldValues[$field_name]; + $value = $this->OriginalFieldValues[$field_name]; + if (!$formatted) { + return $value; + } + + $options = $this->GetFieldOptions($field_name); + $res = $value; + if (array_key_exists('formatter', $options)) { + $formatter =& $this->Application->recallObject($options['formatter']); + /* @var $formatter kFormatter */ + + $res = $formatter->Format($value, $field_name, $this, $format); + } + return $res; } /** @@ -419,7 +432,7 @@ $affected = $this->Conn->getAffectedRows(); if (!$system_update && $affected == 1){ - $this->setModifiedFlag(); + $this->setModifiedFlag(clUPDATE); } $this->saveCustomFields(); @@ -772,7 +785,7 @@ $this->setID($insert_id); if (!$system_create){ - $this->setModifiedFlag(); + $this->setModifiedFlag(clCREATE); } $this->saveCustomFields(); @@ -800,7 +813,7 @@ $ret = $this->Conn->ChangeQuery($q); - $this->setModifiedFlag(); + $this->setModifiedFlag(clDELETE); if ($this->Conn->getAffectedRows() > 0) { // something was actually deleted @@ -929,6 +942,22 @@ --$new_id; $this->Conn->Query('UPDATE '.$this->TableName.' SET `'.$this->IDField.'` = '.$new_id.' WHERE `'.$this->IDField.'` = '.$this->GetID()); + + if ($this->ShouldLogChanges()) { + // Updating TempId in ChangesLog, if changes are disabled + $ses_var_name = $this->Application->GetTopmostPrefix($this->Prefix).'_changes_'.$this->Application->GetTopmostWid($this->Prefix); + $changes = $this->Application->RecallVar($ses_var_name); + $changes = $changes ? unserialize($changes) : Array (); + if ($changes) { + foreach ($changes as $key => $rec) { + if ($rec['Prefix'] == $this->Prefix && $rec['ItemId'] == $this->GetID()) { + $changes[$key]['ItemId'] = $new_id; + } + } + } + $this->Application->StoreVar($ses_var_name, serialize($changes)); + } + $this->SetID($new_id); } @@ -938,12 +967,99 @@ * @access private * @author Alexey */ - function setModifiedFlag() + function setModifiedFlag($mode = null) { $main_prefix = $this->Application->GetTopmostPrefix($this->Prefix); $this->Application->StoreVar($main_prefix.'_modified', '1'); + + if ($this->ShouldLogChanges()) { + $this->LogChanges($main_prefix, $mode); + if (!$this->IsTempTable()) { + $handler =& $this->Application->recallObject($this->Prefix.'_EventHandler'); + $ses_var_name = $main_prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix); + $handler->SaveLoggedChanges($ses_var_name); + } + } } + function ShouldLogChanges() + { + /* @todo Replace true with global LogChanges option */ + return ($this->Application->getUnitOption($this->Prefix, 'LogChanges') || true) && !$this->Application->getUnitOption($this->Prefix, 'ForceDontLogChanges'); + } + + function LogChanges($main_prefix, $mode) + { + if (!$mode) { + return ; + } + + $ses_var_name = $main_prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix); + $changes = $this->Application->RecallVar($ses_var_name); + $changes = $changes ? unserialize($changes) : array(); + + $general = array( + 'Prefix' => $this->Prefix, + 'ItemId' => $this->GetID(), + 'OccuredOn' => adodb_mktime(), + 'MasterPrefix' => $main_prefix, + 'MasterId' => $this->Prefix == $main_prefix ? $this->GetID() : $this->Application->GetVar($main_prefix.'_id'), // is that correct (Kostja)?? + 'Action' => $mode, + ); + switch ($mode) { + case clUPDATE: + $changes[] = array_merge($general, Array( + 'Changes' => serialize(array_merge($this->GetTitleField(), $this->GetChangedFields())), + )); + break; + case clCREATE: + $changes[] = array_merge($general, Array( + 'Changes' => serialize($this->GetTitleField()), + )); + break; + case clDELETE: + $changes[] = array_merge($general, Array( + 'Changes' => serialize(array_merge($this->GetTitleField(), $this->GetRealFields())), + )); + } + + $this->Application->StoreVar($ses_var_name, serialize($changes)); + } + + function GetTitleField() + { + $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); + if ($title_field && $this->GetField($title_field)) { + return Array($title_field => $this->GetField($title_field)); + } + } + + function GetRealFields() + { + if (function_exists('array_diff_key')) { + $db_fields = array_diff_key($this->FieldValues, $this->VirtualFields, $this->CalculatedFields); + } + else { + $db_fields = array(); + foreach ($this->FieldValues as $key => $value) { + if (array_key_exists($key, $this->VirtualFields) || array_key_exists($key, $this->CalculatedFields)) continue; + $db_fields[$key] = $value; + } + } + return $db_fields; + } + + function GetChangedFields() + { + $changes = array(); + + $diff = array_diff_assoc($this->GetRealFields(), $this->OriginalFieldValues); + foreach ($diff as $field => $new_value) { + $changes[$field] = array('old' => $this->GetOriginalField($field, true), 'new' => $this->GetField($field)); + } + return $changes; + } + /** * Returns ID of currently processed record * Index: branches/RC/core/units/pdf/pdf_renderer_tcpdf.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/pdf_renderer_tcpdf.php (revision 0) +++ branches/RC/core/units/pdf/pdf_renderer_tcpdf.php (revision 10294) @@ -0,0 +1,258 @@ +IncludeTCPDF(); + + $this->PDF = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true); + $this->PDF->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN)); + $this->PDF->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA)); + $this->PDF->setPrintHeader(false); + $this->PDF->setPrintFooter(false); + $this->PDF->AddPage(); + +// $this->PDF->SetX(50); +// $this->PDF->SetY(200); +// $this->PDF->wr + } + + function IncludeTCPDF() + { + define('K_TCPDF_EXTERNAL_CONFIG', true); + define('K_PATH_MAIN', FULL_PATH.'/tcpdf/'); + define('K_PATH_URL', PROTOCOL.SERVER_NAME.(defined('PORT')?':'.PORT : '').rtrim(BASE_PATH, '/').'/tcpdf/'); + define ("K_PATH_FONTS", K_PATH_MAIN."fonts/"); + define ("K_PATH_CACHE", K_PATH_MAIN."cache/"); + define ("K_PATH_URL_CACHE", K_PATH_URL."cache/"); + define ("K_PATH_IMAGES", K_PATH_MAIN."images/"); + define ("K_BLANK_IMAGE", K_PATH_IMAGES."_blank.png"); + define ("PDF_PAGE_FORMAT", "A4"); + define ("PDF_PAGE_ORIENTATION", "P"); + define ("PDF_CREATOR", "TCPDF"); + define ("PDF_AUTHOR", "TCPDF"); + define ("PDF_HEADER_TITLE", "0"); + define ("PDF_HEADER_STRING", "0"); + define ("PDF_HEADER_LOGO", "logo_example.png"); + define ("PDF_HEADER_LOGO_WIDTH", 0); + define ("PDF_UNIT", "pt"); + define ("PDF_MARGIN_HEADER", 0); + define ("PDF_MARGIN_FOOTER", 0); + define ("PDF_MARGIN_TOP", 0); + define ("PDF_MARGIN_BOTTOM", 0); + define ("PDF_MARGIN_LEFT", 0); + define ("PDF_MARGIN_RIGHT", 0); + define ("PDF_FONT_NAME_MAIN", "vera"); //vera + define ("PDF_FONT_SIZE_MAIN", 10); + define ("PDF_FONT_NAME_DATA", "vera"); //verase + define ("PDF_FONT_SIZE_DATA", 8); + define ("PDF_IMAGE_SCALE_RATIO", 4); + define("HEAD_MAGNIFICATION", 1.1); + define("K_CELL_HEIGHT_RATIO", 1.25); + define("K_TITLE_MAGNIFICATION", 1.3); + define("K_SMALL_RATIO", 2/3); + require_once(FULL_PATH.'/tcpdf/config/lang/eng.php'); + require_once(FULL_PATH.'/tcpdf/tcpdf.php'); + } + + function NextPage() + { + $this->PDF->AddPage(); + } + + function GetWidth() + { + return $this->PDF->getPageWidth(); + } + + function GetHeight() + { + return $this->PDF->getPageHeight(); + } + + function SetFont($family, $size, $weight=400, $style='normal', $variant='normal') + { + $this->CurFontSize = $size; + + $family = strtolower($family); + switch ($family) { + case 'serif': + $font = 'FreeSerif'; + break; + case 'cursive': + $font = 'FreeSerif'; + break; + case 'fantasy': + $font = 'FreeSans'; + break; + case 'monospace': + $font = 'FreeMono'; + break; + case 'sans-serif': + default: + $font = 'FreeSans'; + break; + } + + $bold = $weight >= 700 ? 'B' : ''; + $italic = preg_match('/italic|oblique/i', $style) ? 'I' : ''; + + return $this->PDF->SetFont($font, $bold.$italic, $size); + } + + function SetFontSize($size) + { + $this->CurFontSize = $size; + $this->PDF->SetFontSize($size); + } + + function ProcessHTMLColor($color) { + $mapping = array( + 'maroon' => '#800000', + 'red' => '#ff0000', + 'orange' => '#ffA500', + 'yellow' => '#ffff00', + 'olive' => '#808000', + 'purple' => '#800080', + 'fuchsia' => '#ff00ff', + 'white' => '#ffffff', + 'lime' => '#00ff00', + 'green' => '#008000', + 'navy' => '#000080', + 'blue' => '#0000ff', + 'aqua' => '#00ffff', + 'teal' => '#008080', + 'black' => '#000000', + 'silver' => '#c0c0c0', + 'gray' => '#808080', + ); + foreach ($mapping as $named_color => $value) { + $color = str_ireplace($named_color, $value, $color); + } + if ( preg_match('/^#([0-9a-f]{3})$/i', $color, $regs) ) { + $color = $color.$regs[1]; + } + if ( preg_match('/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i', $color, $regs) ) + { + return array(hexdec($regs[1]), hexdec($regs[2]), hexdec($regs[3])); + } + return array(255,255,0); + + } + + function SetFillColor($color) + { + list($r,$g,$b) = $this->ProcessHTMLColor($color); + $this->PDF->SetFillColor($r,$g,$b); + $this->PDF->SetTextColor($r,$g,$b); + } + + function SetLineColor($color) + { + list($r,$g,$b) = $this->ProcessHTMLColor($color); + return $this->PDF->SetDrawColor($r,$g,$b); + } + + function SetLineWidth($width) + { + return $this->PDF->setLineWidth($width); + } + + function DrawLine($x1, $y1, $x2, $y2) + { + return $this->PDF->Line($x1, $y1, $x2, $y2); + } + + function DrawRectangle($x1, $y1, $x2, $y2, $mode='D') + { + switch ($mode) { + case kPDFRenderer::SHAPE_DRAW_FILL: + $mode = 'F'; + break; + case kPDFRenderer::SHAPE_DRAW_STROKE : + $mode = 'D'; + break; + case kPDFRenderer::SHAPE_DRAW_FILL_AND_STROKE : + $mode = 'DF'; + break; + + } + $w = $x2-$x1; + $h = $y2-$y1; + return $this->PDF->Rect($x1, $y1, $w, $h, $mode); + } + + function DrawText($text, $x, $y) + { + return $this->PDF->text($x, $y, $text); + } + + function DrawImage($filepath, $x, $y, $w=0, $h=0) + { + $info = pathinfo($filepath); + if (preg_match('/jpg|jpeg|png/i', $info['extension'])) { + return $this->PDF->Image($filepath, $x, $y, $w, $h); + } + if (preg_match('/gif/i', $info['extension'])) { + $tmp_path = WRITEABLE.'/tmp'; + if (!file_exists($tmp_path)) { + mkdir($tmp_path); + } + $converted_filepath = $tmp_path.'/'.$info['filename'].'.png'; + if (!file_exists($converted_filepath) || filemtime($converted_filepath) < filemtime($filepath)) { + $im = @imagecreatefromgif ($filepath); + imagepng($im, $converted_filepath); + } + + return $this->PDF->Image($converted_filepath, $x, $y, $w, $h); + } + } + + function GetPDFString() + { + return $this->PDF->Output('', 'S'); + } + + function GetAscent() + { + //$this->CurrentFont['desc'] + $font = $this->PDF->GetFont(); + return ($font['desc']['Ascent'] / 1000) * $this->CurFontSize; + } + + function GetDescent() + { + $font = $this->PDF->GetFont(); + return ($font['desc']['Descent'] / 1000) * $this->CurFontSize; + } + + function GetLineGap() + { + return 0; + $font = $this->PDF->GetFont(); + return (($font['Ascent'] - $font['Descent'])*0 / 1000) * $this->CurFontSize; + } + + function GetStringWidth($string) + { + return $this->PDF->GetStringWidth($string); + } +} \ No newline at end of file Index: branches/RC/core/units/pdf/pdf_text.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/pdf_text.php (revision 0) +++ branches/RC/core/units/pdf/pdf_text.php (revision 10294) @@ -0,0 +1,246 @@ +SetData($data, 0); + } + + function SetData($data) + { + $this->Data = $data; + } + + function Init() + { + parent::Init(); + $this->SetCSSProperty('display', 'inline'); + } + + function ComputeCSSProperties() + { + parent::ComputeCSSProperties(); + // http://manual.prod.intechnic.lv/css21/text.html#q8 + + $whitespace = str_replace('-', '', $this->GetCSSProperty('white-space')); // remove "-" to avoid confusion + if ($whitespace == 'normal' || $whitespace == 'nowrap' || $whitespace == 'preline') { + $this->Data = preg_replace('/[\n]{1}[\t\\r ]{1}|[\t\\r ]{1}[\n]{1}/', "\n", $this->Data); + } + if ($whitespace == 'pre' || $whitespace == 'prewrap') { + $this->Data = preg_replace('/ /', ' ', $this->Data); + } + if ($whitespace == 'normal' || $whitespace == 'nowrap') { + $this->Data = preg_replace('/[\r\n]/', ' ', $this->Data); + } + if ($whitespace == 'normal' || $whitespace == 'nowrap' || $whitespace == 'preline') { + $this->Data = preg_replace('/\t/', ' ', $this->Data); + $this->Data = preg_replace('/ [ ]+/', ' ', $this->Data); + } + + $transform = $this->GetCSSProperty('text-transform'); + if ($transform == 'uppercase') { + $this->Data = strtoupper($this->Data); + } + if ($transform == 'lowercase') { + $this->Data = strtolower($this->Data); + } + if ($transform == 'capitalize') { + $this->Data = ucwords($this->Data); + } + } + + function CheckDimensions() + { + $font_size = $this->GetCSSProperty('font-size'); + $this->Helper->PDF->SetFont( $this->GetCSSProperty('font-family'), $font_size, $this->GetCSSProperty('font-weight'), $this->GetCSSProperty('font-style'), $this->GetCSSProperty('font-variant')); + $this->Ascent = $this->Helper->PDF->GetAscent(); + $this->Descent = -$this->Helper->PDF->GetDescent(); // ((-$font->getDescent() / $units) * $size); + $this->Gap = $this->Helper->PDF->GetLineGap(); // (($font->getLineGap() / $units) * $size); + + // proportionally fit ascent & descent into font-size. Wiered why they are greater... + if ($this->Ascent + $this->Descent > $font_size) { + $this->Ascent = $this->Ascent/($this->Ascent+$this->Descent)*$font_size; + $this->Descent = $this->Descent/($this->Ascent+$this->Descent)*$font_size; + } + + if ($this->Helper->DimensionsMode == kPDFHelper::DM_SKIP) { + return ; + } + + if ($this->Node->Name == 'BR') { + $this->ContentHeight = $this->Ascent + $this->Descent + $this->Gap; + $this->SetCSSProperty('height', $this->ContentHeight); + $this->NextLine(''); + $this->GetLineBox()->Closed(); + return ; + } + + $whitespace = str_replace('-', '', $this->GetCSSProperty('white-space')); // remove "-" to avoid confusion + + if (!$this->Data) { + parent::CheckDimensions(); + return; + } + + $this->Wrap2($whitespace); + + $this->GetLineBox()->Closed(); + parent::CheckDimensions(); + } + + function CalcMinMaxContentWidth() + { + $font_size = $this->GetCSSProperty('font-size'); + $this->Helper->PDF->SetFont( $this->GetCSSProperty('font-family'), $font_size, $this->GetCSSProperty('font-weight'), $this->GetCSSProperty('font-style'), $this->GetCSSProperty('font-variant')); + $data = $this->Data; + $this->MaxContentWidth = ceil($this->Helper->PDF->GetStringWidth($data)); + $words = preg_split('/([ \[\]\+\(\)]{1})/u', $data, null, PREG_SPLIT_NO_EMPTY); + $min_w = $this->MaxContentWidth; + foreach ($words as $word) { + $word_w = ceil($this->Helper->PDF->GetStringWidth($word)); + if ($word_w < $min_w) { + $min_w = $word_w; + } + } + $this->MinContentWidth = $min_w; + parent::CalcMinMaxContentWidth(); + } + + function Wrap2($whitespace) { + $start = $this->GetLineBox()->CurX; + + $data = $this->Data; + + if ($this->Parent->FirstChild === $this && ($whitespace == 'normal' || $whitespace == 'nowrap' || $whitespace == 'preline')) { + $data = ltrim($data); + } + + $words = preg_split('/([ \[\]\+\(\)]{1})/u', $data, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $words_count = count($words); + $i = 0; + $elem_w = 0; + $max_width = $this->GetLineBox()->GetCSSProperty('width'); + $line_break = false; + while ($i < $words_count) { + $word_w = $this->Helper->PDF->GetStringWidth($words[$i]); + if ( (floor($max_width - $start - $elem_w - $word_w) < 0) && ($i > 0 || $start > 0) ) { + $line_break = true; + break; + } + $elem_w += $word_w; + $i++; + } + // remove space at the end of line + // @todo Find a way to detect a space at the end of last line, which is NOT wrapped + // otherwise right-aligned blocks will have a space at the end of last line + if ( $words[$i-1] == ' ' && $line_break && + ($whitespace == 'normal' || $whitespace == 'nowrap' || $whitespace == 'preline') + ) { + $i--; + $elem_w -= $this->Helper->PDF->GetStringWidth($words[$i]); + array_splice($words, $i, 1); + } + $this->Data = join( array_splice($words, 0, $i) ); //$i here is the number of last word (starting with 0) + 1, so it's fine for splice + if (!$this->Data) { // nothing fitted on current line + $this->Ascent = 0; + $this->Descent = 0; + $this->Gap = 0; + $this->NextLine(join( $words )); + } + else { + $h = $this->Ascent + $this->Descent + $this->Gap; + $elem = $this; + if (preg_match_all('/( )/', $this->Data, $regs, PREG_PATTERN_ORDER)) { + $spacers = count($regs[1]); + } + else { + $spacers = 0; + } + do { + $elem->SetCSSProperty('width', $elem->GetCSSProperty('width') + $elem_w); + $elem->Spacers = $elem->Spacers + $spacers; + $elem->ContentHeight = $h; + $elem->SetCSSProperty('height', $h); + $elem = $elem->Parent; + } while ($elem && $elem->GetDisplayLevel() == 'inline'); + $elem->Spacers += $spacers; + $this->GetLineBox()->CurX = $start + $elem_w; + + if ($i < $words_count) { + $this->NextLine(join( $words )); + } + } + } + + function GetFollowingSiblings() + { + $cur_line = $this->GetLineBox(); + $elem = $this; + $cur_pos = 0; + do { + $cur_pos = $elem->Position; + $elem = $elem->Parent; + } while ( $elem && !($elem instanceof kPDFLine) ); + return $cur_line->RemoveChildren($cur_pos+1); + } + + function CreateNextLine() + { + $cur_line = $this->GetLineBox(); + $cur_line->Closed(); + if ($this->Node->Name != 'BR') $cur_line->LastLine = false; + return $cur_line->Parent->AddChild( new kPDFLine($cur_line->Parent->Node, $this->Helper), true, $cur_line); + } + + function NextLine($words) + { + $removed = $this->GetFollowingSiblings(); + $next_line = $this->CreateNextLine(); + if ($words) { + $new_elem = $next_line->AddChild( new kPDFTextElement( $words, $this->Node, $this->Helper )); + $new_elem->Closed(); + } + + foreach ($removed as $elem) { + $next_line->AddChild( $elem, false ); //don't init + } + } + + function DrawAt($page, $x, $y, $spacer_w=0) + { + $page->SetLineWidth(0.1); + + $y += $this->Ascent; //finding baseline + $this->Helper->PDF->SetFont( $this->GetCSSProperty('font-family'), $this->GetCSSProperty('font-size'), $this->GetCSSProperty('font-weight'), $this->GetCSSProperty('font-style'), $this->GetCSSProperty('font-variant')); + if ($color = $this->GetCSSProperty('color')) { + $page->SetFillColor($color); + } + + if (defined('PDF_DEBUG_NO_TEXT')) return ; + if ($spacer_w == 0) { + $data = preg_replace('/\xA0/u', ' ', $this->Data); + $page->drawText($data, $x, $y); + } + else { + $spacer_w += $page->GetStringWidth(' '); + $words = explode(' ', $this->Data); + foreach ($words as $word) { + $word = preg_replace('/\xA0/u', ' ', $word); + $page->drawText($word, $x, $y); + $x += $page->GetStringWidth($word); + $x += $spacer_w; + } + } + $this->CurX = $x; + } + +} \ No newline at end of file Index: branches/RC/core/units/pdf/pdf_helper.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/pdf_helper.php (revision 0) +++ branches/RC/core/units/pdf/pdf_helper.php (revision 10294) @@ -0,0 +1,996 @@ +Application->InitParser(); + $template_params['name'] = $template; + $xml = $this->Application->ParseBlock($template_params); + $xml_helper =& $this->Application->recallObject('kXMLHelper'); + /* @var $xml_helper kXMLHelper */ + + $doc = $xml_helper->Parse($xml_helper->ConvertHTMLEntities($xml), XML_WITH_TEXT_NODES); + if ($doc->Name == 'ERROR') { + echo 'BAD TEMPLATE'; + exit; + } + +// $this->Application->Debugger->profileStart('pdf_init', 'Initializing PDF'); + $this->InitPDF($doc); +// $this->Application->Debugger->profileFinish('pdf_init'); +// $this->Application->Debugger->profileStart('process_nodes', 'Processing nodes'); + $this->ProcessNode($doc); +// $this->Application->Debugger->profileFinish('process_nodes'); + + if (defined('PDF_DEBUG_DUMP_ONLY')) { + $this->DimensionsMode = kPDFHelper::DM_NORMAL ; + $this->Application->Debugger->profileStart('laying_out', 'Laying out nodes'); +// $this->CurPDFElem->LayoutChildren(); + $this->Application->Debugger->profileFinish('laying_out'); + $this->Application->Debugger->profileStart('rendering', 'rendering'); +// $this->Render(); + $this->Application->Debugger->profileFinish('rendering'); + +// echo $this->CurPDFElem->DumpStructure(); + exit; + } + + $this->Render(); + + return $this->PDF->GetPDFString(); +// exit; + } + + function InitPDF($doc) + { + $this->PDF = new kTCPDFRenderer(); + + $this->Stylesheet = new kPDFStylesheet(); + + $this->CurPDFElem = kPDFElemFactory::CreateFromNode($doc, $this); + $this->Document = $this->CurPDFElem; +// $this->CurPDFElem = new kPDFPage($page1, new kXMLNode('_NONE_', array('style' => 'display: block; width: '.$page1->getWidth().';')), $this); + $this->CurPDFElem->Init(); + $this->CurPDFElem->SetCSSProperty('width', $this->PDF->getWidth()); + $this->CurPDFElem->SetCSSProperty('height', $this->PDF->getHeight()); + $this->PDF->SetFont('helvetica', $this->PtPerEm); + } + + function Render() + { + $this->DimensionsMode = kPDFHelper::DM_NORMAL ; + $this->CurPDFElem->LayoutChildren(); + $this->PageBottom = $this->PDF->getHeight() - $this->Document->GetCSSProperty('margin-bottom'); // margin + $this->CurPDFElem->DrawAt($this->PDF, 0, 0); + } + + function ProcessHead($node) + { + if ($node->Name == 'STYLE') { + $css = $node->Data; + $tokens = $this->Stylesheet->GetTokens($node->Data); + $this->Stylesheet->ParseTokens($tokens); + } + foreach($node->Children as $child) { + $this->ProcessHead($child); + } + } + + function ProcessNode($node) + { + $children = count($node->Children); + foreach($node->Children as $child) { + if ($child->Name == 'HEAD') { + $this->ProcessHead($child); + continue; + } + + $elem = kPDFElemFactory::CreateFromNode($child, $this); + if (!$elem) continue; + $display = $elem->GetDisplayLevel(); + if ($display == 'none') continue; + + // http://manual.prod.intechnic.lv/css21/visuren.html#anonymous-block-level + if ($display == 'block' && $this->CurPDFElem->GetDisplayLevel() == 'inline') { + $this->CurPDFElem->Closed(); + $this->CurPDFElem = $this->CurPDFElem->GetContainingBlock(); + } + + $current = $this->CurPDFElem; + $line = false; + if ($display == 'inline' && !($this->CurPDFElem instanceof kPDFLine) ) { + $this->CurPDFElem = $this->CurPDFElem->GetLineBox(); + $line = $this->CurPDFElem; + } + + $this->CurPDFElem = $this->CurPDFElem->AddChild( $elem ); + + $this->ProcessNode($child); + $this->CurPDFElem = $current; + } + $this->CurPDFElem->Closed(); + } +} + +class kPDFElemFactory { + static function CreateFromNode($node, $helper) + { + /* @todo Create elements, based on their display, rather than tag name - pure CSS rendering */ + + switch ($node->Name) { + case '_TEXT_': +// $DOMNode = $node->Parent; + $elem = new kPDFTextElement($node->Data, $node, $helper); + if ($elem->CSSSpecifiedProperties['WHITE-SPACE'] == 'normal' && trim($node->Data) == '') { + return false; + } + return $elem; + break; + case 'TR': + return new kPDFTableRow($node, $helper); + case 'BR': + return new kPDFTextElement($node->Data, $node, $helper); + case 'TABLE': + return new kPDFTable($node, $helper); + case 'IMG': + return new kPDFImage($node, $helper); + default: + return new kPDFElement($node, $helper); + break; + } + } +} + +class kPDFElement { + public $Parent; + protected $Font; + protected $FontSize; + public $Children = array(); + + public $Position = 0; + + public $FirstChild; + public $LastChild; + + public $ContentHeight = 0; + + public $Style; + + public $CurX = 0; + public $CurY = 0; + + public $Baseline = 0; + public $Ascent; + public $Descent; + public $Gap; + + public $Node; + public $Helper; + + public $CSSSpecifiedProperties = array(); + public $CSSComputedPoperties = array(); + public $DirectStyle; + + public $IsClosed = false; + + public $LayedOut = false; + + public $WidthComputed = false; + + public $MinContentWidth = 0; + public $MaxContentWidth = 0; + + public $CurLine_MinContentWidth = 0; + public $CurLine_MaxContentWidth = 0; + + public $Spacers = 0; + + + function __construct($node, $helper) + { + $this->Node = $node; + $this->Helper = $helper; + $this->ProcessCSS(); + } + + function Init() + { + $this->ComputeCSSProperties(); + } + + function Reset() + { + $this->CurX = 0; + $this->CurY = 0; + } + + function AddChild($child, $init=true, $after=null) + { + $node_count = count($this->Children); + if (isset($after)) { + $after_pos = $after->Position; + for($i=$after_pos+1; $i<$node_count; $i++) { + $this->Children[$i]->Position++; + } + array_splice($this->Children, $after_pos+1, 0, array($child)); + $child->Position = $after_pos+1; + } + else { + $child->Position = $node_count; + $this->Children[] = $child; + } + if ($node_count == 0) { + $this->FirstChild = $child; + $this->LastChild = $child; + } + elseif ($child->Position == $node_count) { + $this->LastChild = $child; + } + + $child->Parent = $this; + if ($init) $child->Init(); + return $child; + } + + public function Closed() + { + if ($this->LastChild && $this->LastChild instanceof kPDFLine && !$this->LastChild->IsClosed) { + $this->LastChild->Closed(); + } + $this->ComputeWidthAndMargins(); + $this->CalcMinMaxContentWidth(); + $this->IsClosed = true; + return ; + } + + function RemoveChildren($start, $count=null) + { + return array_splice($this->Children, $start); + } + + function &FindChildByProperty($property, $value) + { + $property = strtoupper($property); + if (strtoupper($this->GetCSSProperty($property)) == strtoupper($value)) return $this; + foreach ($this->Children as $elem) + { + $child =& $elem->FindChildByProperty($property, $value); + if ($child !== false) + { + return $child; + } + } + $false = false; + return $false; + } + + function LayoutChildren() + { + $this->ComputeWidthAndMargins(); + $i = 0; + $this->Reset(); + while (isset($this->Children[$i])) { // can't use foreach here, because LayoutChildren() may add new children to current elem (wrapping) + $child = $this->Children[$i]; + $child->LayoutChildren(); + $i++; + } + $this->CheckDimensions(); + $this->ComputeHeight(); +// $this->ComputeBaseline(); + } + + public function ComputeHeight() + { + $display = $this->GetCSSProperty('display'); + if ($display == 'inline') return ; + + $height = 0; // $this->GetCSSProperty('height'); + /* $extra_height = + $this->GetCSSProperty('margin-top') + + $this->GetCSSProperty('margin-bottom') + + $this->GetCSSProperty('padding-top') + + $this->GetCSSProperty('padding-bottom') + + $this->GetCSSProperty('border-top-width') + + $this->GetCSSProperty('border-bottom-width');*/ + + foreach ($this->Children as $elem) { + $dim = $elem->GetBoxDimensions(); + $elem_height = $dim[1]; + if ($elem->GetDisplayLevel() == 'block' && $elem->GetCSSProperty('display') != 'table-cell') { + $height += $elem_height; + } + else { + if ($elem_height > $height) { + $height = $elem_height; + } + } + } + $this->SetCSSProperty('height', $height); + } + + function &FindChild($name) + { + $name = strtoupper($name); + if ($this->Node->Name == $name) return $this; + foreach ($this->Children as $elem) + { + $child =& $elem->FindChild($name); + if ($child !== false) + { + return $child; + } + } + $false = false; + return $false; + } + + function DumpStructure($level=0) + { + $dump = ''; + $tab = str_repeat(' ', $level*4); + $dump .= $tab . get_class($this) . ' Node: ' . $this->Node->Name . " child of ".$this->Node->Parent->Name."
\n"; + if ($this instanceof kPDFTextElement) { + $dump .= $tab . 'Data: ' . $this->Data . "
\n"; + } + $dump .= $tab . 'Width: ' . $this->GetCSSProperty('width') . ' Height: ' . $this->GetCSSProperty('height') . " Ascent: " . $this->Ascent . "
\n"; + $dump .= $tab . 'MinCW: ' . $this->MinContentWidth . ' MaxCW: ' . $this->MaxContentWidth . "
\n"; + $dump .= $tab . 'Children:' . "
\n"; + $i = 0; + foreach ($this->Children as $elem) + { + $i++; + $dump .= $tab . $i . "
\n"; + $level++; + $dump .= $elem->DumpStructure($level); + $level--; + } + return $dump; + } + + function DrawAt($page, $x=0, $y=0, $spacer_w=0) + { + if ($this->Node->Name == 'FOOTER') { + $dim = $this->GetBoxDimensions(); + $y = $this->Helper->PDF->GetHeight() - $dim[1]; + } + + if ($this->GetDisplayLevel() == 'block' ) { + $width = $this->GetCSSProperty('width'); + $margin_left = $this->GetCSSProperty('margin-left'); + $margin_right = $this->GetCSSProperty('margin-right'); + $border_left = $this->GetCSSProperty('border-left-width'); + $border_right = $this->GetCSSProperty('border-right-width'); + $padding_left = $this->GetCSSProperty('padding-left'); + $padding_right = $this->GetCSSProperty('padding-right'); + + $height = $this->GetCSSProperty('height'); + $margin_top = $this->GetCSSProperty('margin-top'); + $margin_bottom = $this->GetCSSProperty('margin-bottom'); + $border_top = $this->GetCSSProperty('border-top-width'); + $border_bottom = $this->GetCSSProperty('border-bottom-width'); + $padding_top = $this->GetCSSProperty('padding-top'); + $padding_bottom = $this->GetCSSProperty('padding-bottom'); + + $outer_width = $margin_left + $border_left + $padding_left + $width + $padding_right + $border_right + $margin_right; + $outer_height = $margin_top + $border_top + $padding_top + $height + $padding_bottom + $border_bottom + $margin_bottom; + + if (defined('PDF_DEBUG_DRAW_BOXES')) { + $page->SetFillColor( '#eeeeee' ); + if ($this instanceof kPDFLine) { + if (defined('PDF_DEBUG_DRAW_LINE_BOXES')) { + $page->SetLineWidth(0.5); + $page->SetLineColor( 'yellow' ); + $page->SetFillColor( '#efefef' ); + } + } + else { + $page->SetLineWidth(0.1); + $page->SetLineColor( '#0000ff' ); + } +// $page->DrawRectangle($x, $y, $x + $outer_width, $y - $outer_height, kPDFRenderer::SHAPE_DRAW_FILL_AND_STROKE); + $page->SetFillColor( '#000000' ); + $page->setFont('helvetica', 6); + if (defined('PDF_DEBUG_DRAW_BOX_INFO') && preg_match(PDF_DEBUG_DRAW_BOX_INFO, $this->Node->Name)) { + $page->drawText($this->Node->Name . "{$outer_width}x{$outer_height} ".get_class($this), $x+2, $y+8); + } + } + } + + if ($this->GetDisplayLevel() == 'block' && !($this instanceof kPDFLine)) { + // MARGINS + if (defined('PDF_DEBUG_DRAW_MARGINS')) { + $page->SetLineWidth(1); + $page->SetLineColor( 'green' ); + $page->DrawLine($x, $y, $x + $margin_left, $y); + $page->DrawLine($x, $y, $x, $y + $margin_bottom); + $page->SetLineWidth(0.3); + $page->DrawLine($x + $margin_left, $y, $x + $margin_left, $y + 3); + $page->DrawLine($x, $y + $margin_top, $x + 3, $y + $margin_top); + } + $x += $margin_left; + $y += $margin_top; + + // BACKGROUND + // http://manual.prod.intechnic.lv/css21/colors.html#q2 + $body = false; + if ($this->Node->Name == 'HTML' && $this->GetCSSProperty('background-color') == 'transparent') { + // get BODY color + $body = $this->FindChild('BODY'); + if ($body !== false) { + $background_color = $body->GetCSSProperty('background-color'); + $body->SetCSSProperty('background-color', 'transparent'); + } + } + else { + $background_color = $this->GetCSSProperty('background-color'); + } + if ($background_color != 'transparent') { + $page->SetFillColor( $background_color ); + $x1 = $x + $border_left + + $padding_left + + $width + + $padding_right + + $border_right; + $y1 = $y + ($border_top + + $padding_top + + $height + + $padding_bottom + + $border_bottom); + $page->DrawRectangle($x, $y, $x1, $y1, kPDFRenderer::SHAPE_DRAW_FILL); + } + + // BORDERS + $x2 = $x + $border_left + $padding_left + $width + $padding_right + $border_right; + $y2 = $y + ($border_top + $padding_top + $height + $padding_bottom + $border_bottom); + + if ($this->GetCSSProperty('border-top-style') != 'none' && $border_top > 0) { + $page->SetLineWidth($this->GetCSSProperty('border-top-width')); + $page->SetLineColor( $this->GetCSSProperty('border-top-color') ); + $page->DrawLine($x+$border_left/2, $y+$border_top/2, $x2-$border_right/2, $y+$border_top/2); + } + if ($this->GetCSSProperty('border-right-style') != 'none' && $border_right > 0) { + $page->SetLineWidth($this->GetCSSProperty('border-right-width')); + $page->SetLineColor( $this->GetCSSProperty('border-right-color') ); + $page->DrawLine($x2-$border_right/2, $y+$border_top/2, $x2-$border_right/2, $y2-$border_bottom/2); + } + if ($this->GetCSSProperty('border-bottom-style') != 'none' && $border_bottom > 0) { + $page->SetLineWidth($this->GetCSSProperty('border-bottom-width')); + $page->SetLineColor( $this->GetCSSProperty('border-bottom-color') ); + $page->DrawLine($x+$border_left/2, $y2-$border_bottom/2, $x2-$border_right/2, $y2-$border_bottom/2); + } + if ($this->GetCSSProperty('border-left-style') != 'none' && $border_left > 0) { + $page->SetLineWidth($this->GetCSSProperty('border-left-width')); + $page->SetLineColor( $this->GetCSSProperty('border-left-color')); + $page->DrawLine($x+$border_left/2, $y+$border_top/2, $x+$border_left/2, $y2-$border_bottom/2); + } + $x += $border_left; + $y += $border_top; + + // PADDING + $x += $this->GetCSSProperty('padding-left'); + $y += $this->GetCSSProperty('padding-top'); + + if (defined('PDF_DRAW_CONTENT_AREA') && $this->Parent) { + $page->SetFillColor( 'pink' ) ; + $page->DrawRectangle($x, $y, $x + $width, $y + $height, kPDFRenderer::SHAPE_DRAW_FILL); + } + + } + + $this->CurX = $x; + $this->CurY = $y; +// $max_width = $this->GetCSSProperty('max-width'); + $this->TopLine = $y; + $page->SetLineWidth(0.1); + + foreach ($this->Children as $elem) + { + $dim = $elem->GetBoxDimensions(); + $align_offset_x = 0; + $align_offset_y = 0; + if ($elem->GetDisplayLevel() == 'inline') { + if ($this->GetCSSProperty('vertical-align') == 'baseline') { + $align_offset_y = $this->Ascent - $elem->Ascent; + } + } + if (defined('PDF_DEBUG_DRAW_BASELINE')) { + $page->SetLineWidth(0.5); + $page->SetLineColor( '#ff0000' ); + $page->DrawLine($this->CurX, $this->CurY + $elem->Ascent, $this->CurX + $dim[0], $this->CurY + $elem->Ascent); + } + if ($elem instanceof kPDFLine ) { + $spacer_w = 0; + switch ($elem->GetCSSProperty('text-align')) { + case 'center': + $align_offset_x = ($dim[0] - $elem->CurX)/2; + break; + case 'right': + $align_offset_x = ($dim[0] - $elem->CurX); + break; + case 'justify': + $spacer_w = $elem->LastLine ? 0 : ($dim[0] - $elem->CurX)/$elem->Spacers; + } + } + if (defined('PDF_DEBUG_DRAW_ELEM_BORDERS')) { + $page->SetLineWidth(0.1); + $page->SetLineColor( '#bbbbbb' ); + $page->SetFillColor( '#bbbbbb' ); + $page->setFont('helvetica', 4); + $page->DrawRectangle($this->CurX, $this->CurY + $align_offset_y, $this->CurX + $dim[0], $this->CurY + $align_offset_y + $dim[1], kPDFRenderer::SHAPE_DRAW_STROKE); + if (defined('PDF_DEBUG_DRAW_BOX_INFO') && preg_match(PDF_DEBUG_DRAW_BOX_INFO, $this->Node->Name) && $elem->GetDisplayLevel() != 'block') { + $page->drawText("w:{$dim[0]}x{$dim[1]}", $this->CurX + 2, $this->CurY + $align_offset_y + 6); + } + } + $tmp_x = $this->CurX; + $this->CheckPageBreak($page, $elem, $dim); + $elem->DrawAt($page, $this->CurX + $align_offset_x, $this->CurY + $align_offset_y, $spacer_w); + if ($elem->GetDisplayLevel() == 'block' && $elem->GetCSSProperty('display') != 'table-cell') { + $this->CurY += $dim[1]; + $this->CurX = $tmp_x; + } + else { + $this->CurX = $this->CurX + $dim[0] + ($elem->Spacers*$spacer_w); + } + } + if ($this->GetCSSProperty('PAGE-BREAK-AFTER') == 'always') { + $this->Helper->PDF->NextPage(); + $this->Parent->CurY = $this->Helper->Document->GetCSSProperty('margin-top'); + } + } + + function CheckPageBreak($page, $elem, $dim) + { + if (($this->GetCSSProperty('DISPLAY') != 'block' && $this->GetCSSProperty('DISPLAY') != 'table') || $elem->Node->Name == 'BODY' || $elem->Node->Name == 'HTML') return; + + if ($this->CurY + $dim[1] > $this->Helper->PageBottom) { + $this->Helper->PDF->NextPage(); + $this->CurY = $this->Helper->Document->FindChild('BODY')->GetCSSProperty('margin-top'); + } + } + + function GetBoxDimensions() + { + return array( + $this->GetCSSProperty('margin-left') + + $this->GetCSSProperty('border-left-width') + + $this->GetCSSProperty('padding-left') + + $this->GetCSSProperty('width') + + $this->GetCSSProperty('padding-right') + + $this->GetCSSProperty('border-right-width') + + $this->GetCSSProperty('margin-right'), + + $this->GetCSSProperty('margin-top') + + $this->GetCSSProperty('border-top-width') + + $this->GetCSSProperty('padding-top') + + $this->GetCSSProperty('height') + + $this->GetCSSProperty('padding-bottom') + + $this->GetCSSProperty('border-bottom-width') + + $this->GetCSSProperty('margin-bottom'), + ); + } + + function GetLineBox($first=false) { + if ($this instanceof kPDFLine ) { + if ($first) { + return $this->Parent->FirstChild; + } + else { + return $this; + } + } + if ($this->GetDisplayLevel() == 'block') { + if ($this->LastChild instanceof kPDFLine ) { + return $this->LastChild; + } + return $this->AddChild( new kPDFLine($this->Node, $this->Helper)); + } + + //in-line box + return $this->Parent->GetLineBox(); + } + + function GetTable() { + if ($this instanceof kPDFTable) { + return $this; + } + if ($this->Parent) { + return $this->Parent->GetTable(); + } + return false; + } + + function GetDisplayLevel() + { + // http://manual.prod.intechnic.lv/css21/visuren.html#q5 + $display = $this->GetCSSProperty('display'); + if (!$display) $display = $this->CSSSpecifiedProperties['DISPLAY']; + switch ($display) { + case 'block': + case 'list-item': + case 'table': + case 'table-cell': + case 'table-row': + case 'table': + return 'block'; + case 'run-in': + // do special processing here: http://manual.prod.intechnic.lv/css21/visuren.html#run-in + return 'block'; + case 'none': + return 'none'; + } + return 'inline'; + } + + function ProcessCSS() + { + $supported_properties = array( + 'DISPLAY', + 'FONT-SIZE', // SHOULD BE FIRST, BECAUSE ALL EM UNITS RELY ON IT + 'WIDTH','MAX-WIDTH','MIN-WIDTH', + 'HEIGHT', + 'COLOR', + 'FONT-FAMILY', 'FONT-WEIGHT', 'FONT-STYLE', 'FONT-VARIANT', + 'WHITE-SPACE', + 'VERTICAL-ALIGN', + 'MARGIN-LEFT', 'MARGIN-RIGHT', 'MARGIN-TOP', 'MARGIN-BOTTOM', + 'BORDER-TOP-STYLE', 'BORDER-RIGHT-STYLE', 'BORDER-BOTTOM-STYLE', 'BORDER-LEFT-STYLE', + 'BORDER-TOP-WIDTH', 'BORDER-RIGHT-WIDTH', 'BORDER-BOTTOM-WIDTH', 'BORDER-LEFT-WIDTH', + 'BORDER-TOP-COLOR', 'BORDER-RIGHT-COLOR', 'BORDER-BOTTOM-COLOR', 'BORDER-LEFT-COLOR', + 'PADDING-TOP', 'PADDING-RIGHT', 'PADDING-BOTTOM', 'PADDING-LEFT', + 'BACKGROUND-COLOR', + 'TEXT-ALIGN', 'TEXT-TRANSFORM', + 'TABLE-LAYOUT', + 'PAGE-BREAK-AFTER' + ); + + $cascade = $this->Helper->Stylesheet->GetAllProperties($this->Node); + + foreach ($supported_properties as $property) { + $value = false; + // !!! The following needs to be fixed somehow - thereis no major need in cascading properties for kPDFTextElement and kPDFLine also... + if (!($this instanceof kPDFTextElement) || ($this instanceof kPDFTextElement && $property != 'DISPLAY')) { + $value = isset($cascade[$property]) ? $cascade[$property] : false; + } + if (!$value && kCSSDefaults::IsInherited($property)) { + $value = $this->FindInheritedValue($property); + } + if (!$value) { + $value = kCSSDefaults::GetDefaultValue($property); + } + $this->CSSSpecifiedProperties[$property] = $value; + } + $this->Node->CssProperties = $this->CSSSpecifiedProperties; + } + + function FindInheritedValue($property) + { + $node = $this->Node; + if (!$node->Parent) { + return false; + } + do { + $node = $node->Parent; + $value = isset($node->ComputedCSSProperties[$property]) ? $node->ComputedCSSProperties[$property] : false; + } while (!$value && $node->Parent); + return $value; + } + + function ComputeCSSProperties() + { + foreach ($this->CSSSpecifiedProperties as $property => $value) { + $this->SetCSSProperty($property, $this->ComputeCSSProperty($property, $value)); + } + $this->ComputeWidthAndMargins(); + /*if ($this->GetDisplayLevel() == 'inline') { + $props = array( + 'margin-top' => 0, + 'margin-right' => 0, + 'margin-bottom' => 0, + 'margin-left' => 0, + 'width' => 0, + ); + foreach ($props as $name => $value) { + $this->SetCSSProperty($name, $value); + } + }*/ + $this->Node->ComputedCSSProperties = $this->CSSComputedPoperties; + } + + function ComputeWidthAndMargins() + { + if ($this->WidthComputed) return ; + + if ($this->GetDisplayLevel() == 'inline') { + return; + } + + if (!$this->Parent) { + $this->WidthComputed = true; + return ; + } + $cb = $this->GetContainingBlock(); + $cb_width = $cb->GetCSSProperty('width'); // containing block width + if (!$cb->WidthComputed) { // this means we are inside an element which width is not yet defined (nested TABLE for instance) + return ; + } + if ($this instanceof kPDFLine) { // otherwise margins will be applied from line containing block to the line + $this->SetCSSProperty('width', $cb_width); + return; + } + + // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block + $width = $this->GetCSSProperty('width'); // may be auto + $margin_left = $this->GetCSSProperty('margin-left'); // may be auto + $margin_right = $this->GetCSSProperty('margin-right'); // may be auto + $border_left = $this->GetCSSProperty('border-left-width'); + $border_right = $this->GetCSSProperty('border-right-width'); + $padding_left = $this->GetCSSProperty('padding-left'); + $padding_right = $this->GetCSSProperty('padding-right'); + + if ($width == 'auto' && $this->Parent) { + if ($margin_left == 'auto') { + $margin_left = 0; + } + if ($margin_right == 'auto') { + $margin_right = 0; + } + $width = $cb_width - $margin_left - $border_left - $padding_left - $padding_right - $border_right - $margin_right; + } + elseif ($margin_left != 'auto' && $margin_right != 'auto') { //over-constrained + $margin_right = $cb_width - $width - $margin_left - $border_left - $padding_left - $padding_right - $border_right; + } + elseif ($margin_left == 'auto' && $margin_right == 'auto') { + $margin_left = ($cb_width - $border_left - $padding_left - $width - $padding_right - $border_right)/2; + $margin_right = $margin_left; + } + + $this->SetCSSProperty('width', $width); + $this->SetCSSProperty('margin-right', $margin_right); + $this->SetCSSProperty('margin-left', $margin_left); + $this->WidthComputed = true; + } + + function ComputeCSSProperty($property, $value) + { + if ($property == 'FONT-WEIGHT') { + if ($value == 'normal') { + return 400; + } + if ($value == 'bold') { + return 700; + } + if ($value == 'bolder') { + if (!$this->Parent) { + return 700; + } + else { + return max($this->Parent->GetCSSProperty('font-weight') + 300, 900); + } + } + if ($value == 'lighter') { + if (!$this->Parent) { + return 400; + } + else { + return min($this->Parent->GetCSSProperty('font-weight') - 300, 100); + } + } + if (!is_integer($value)) { + return 400; + } + return $value; + } + + if (preg_match('/([.0-9]+)em/i', $value, $regs)) { + $cb = $this->GetContainingBlock(); + if ($property == 'FONT-SIZE') { + if (!$cb) { + $value = $regs[1] * $this->Helper->PtPerEm; + } + else { + $value = $cb->GetCSSProperty('font-size') * $regs[1]; + } + } + else { + if ($cb) { + $value = $cb->GetCSSProperty('font-size') * $regs[1]; + } + } + } + elseif (preg_match('/([.0-9]+)(px)/i', $value, $regs)) { + $value = $regs[1] * 72 / 96; + } + elseif (preg_match('/([.0-9]+)(pt)/i', $value, $regs)) { + $value = $regs[1]; + } + elseif (preg_match('/([.0-9]+)%/i', $value, $regs)) { + $cb = $this->GetContainingBlock(); + if ($cb && ($property != 'WIDTH' || ($property == 'WIDTH' && $cb->WidthComputed))) { + $value = $regs[1]/100 * $cb->GetCSSProperty($property); + } + } + + if ($property == 'FONT-SIZE' && preg_match('/(xx-small|x-small|small|medium|large|x-large|xx-large)/i', $value)) { + switch (strtolower($value)) { + case 'xx-small': + return $this->Helper->PtPerEm * 0.5; + case 'x-small': + return $this->Helper->PtPerEm * 0.7; + case 'small': + return $this->Helper->PtPerEm * 0.8; + case 'medium': + return $this->Helper->PtPerEm; + case 'large': + return $this->Helper->PtPerEm * 1.2; + case 'x-large': + return $this->Helper->PtPerEm * 1.4; + case 'xx-large': + return $this->Helper->PtPerEm * 1.6; + } + } +// elseif ($value == 'none') { +// $value = 0; +// } + return $value; + } + + function GetContainingBlock() + { + if (!$this->Parent) return false; + $parent_display = $this->Parent->GetCSSProperty('display'); + if (preg_match('/^(block|inline-block|table|table-cell|list-item)$/i', $parent_display)) { + return $this->Parent; + } + return $this->Parent->GetContainingBlock(); + } + + function GetCSSProperty($property) + { + $property = strtoupper($property); + return isset($this->CSSComputedPoperties[$property]) ? $this->CSSComputedPoperties[$property] : false; + } + + function SetCSSProperty($name, $value) + { + $this->CSSComputedPoperties[strtoupper($name)] = $value; + } + + function CheckDimensions() + { + $this->ComputeBaseline(); + } + + function ComputeBaseline() + { + if (!$this->Parent) return ; + $display = $this->GetCSSProperty('display'); + /*$dim = $this->GetBoxDimensions(); + $this->Ascent = $dim[1];*/ + if ($display == 'inline' || $display == 'table-cell' || $this instanceof kPDFLine) { + if ($this->Parent->Ascent < $this->Ascent) { + $this->Parent->Ascent = $this->Ascent; + } + if ($this->Parent->Descent < $this->Descent) { + $this->Parent->Descent = $this->Descent; + } + if ($this->Parent->Gap < $this->Gap) { + $this->Parent->Gap = $this->Gap; + } + $this->Parent->ComputeBaseline(); + } + } + + function ContentWidthNewLine() + { + if ($this->CurLine_MinContentWidth > $this->MinContentWidth) { + $this->MinContentWidth = $this->CurLine_MinContentWidth; + } + if ($this->CurLine_MaxContentWidth > $this->MaxContentWidth) { + $this->MaxContentWidth = $this->CurLine_MaxContentWidth; + } + $this->CurLine_MinContentWidth = 0; + $this->CurLine_MaxContentWidth = 0; + } + + function CalcMinMaxContentWidth() { + if (!$this->Parent) return ; + $this->ContentWidthNewLine(); + $extra_width = + $this->GetCSSProperty('margin-left') + + $this->GetCSSProperty('margin-right') + + $this->GetCSSProperty('padding-left') + + $this->GetCSSProperty('padding-right') + + $this->GetCSSProperty('border-left-width') + + $this->GetCSSProperty('border-right-width'); +// $extra_width = 0; + $this->MinContentWidth += $extra_width; + $this->MaxContentWidth += $extra_width; + $display = $this->GetCSSProperty('display'); + + if (($display == 'inline' || $display == 'table-cell')) { + $this->Parent->CurLine_MinContentWidth += $this->MinContentWidth; + $this->Parent->CurLine_MaxContentWidth += $this->MaxContentWidth; + if ($this->Node->Name == 'BR') { + $this->Parent->ContentWidthNewLine(); + } + } + else { + if ($this->MinContentWidth > $this->Parent->MinContentWidth) { + $this->Parent->MinContentWidth = $this->MinContentWidth; +// $this->Parent->CalcMinMaxContentWidth(); + } + if ($this->MaxContentWidth > $this->Parent->MaxContentWidth) { + $this->Parent->MaxContentWidth = $this->MaxContentWidth; +// $this->Parent->CalcMinMaxContentWidth(); + } + } +// $this->ContentWidthNewLine(); + } + + +} + +class kPDFLine extends kPDFElement { + public $LastLine = true; + + function __construct($node, $helper) + { + $line_node = new kXMLNode('_LINE_', array('STYLE' => 'display: block')); +// $line_node->SetParent($node); + $node->AddChild($line_node); + parent::__construct($line_node, $helper); + } + + function Init() + { + parent::Init(); + $this->SetCSSProperty('display', 'block'); + $this->SetCSSProperty('margin-left', '0'); + $this->SetCSSProperty('margin-right', '0'); + $this->SetCSSProperty('margin-top', '0'); + $this->SetCSSProperty('margin-bottom', '0'); + $this->SetCSSProperty('padding-left', '0'); + $this->SetCSSProperty('padding-right', '0'); + $this->SetCSSProperty('padding-top', '0'); + $this->SetCSSProperty('padding-bottom', '0'); + } + +} Index: branches/RC/core/units/general/xml_helper.php =================================================================== diff -u -N -r10251 -r10294 --- branches/RC/core/units/general/xml_helper.php (.../xml_helper.php) (revision 10251) +++ branches/RC/core/units/general/xml_helper.php (.../xml_helper.php) (revision 10294) @@ -9,15 +9,18 @@ * @var kXMLNode */ var $CurrentElement = null; + + var $Mode; /** * Parses XML data specified and returns root node * * @param string $xml * @return kXMLNode */ - function &Parse($xml = null) + function &Parse($xml = null, $mode=XML_NO_TEXT_NODES) { + $this->Mode = $mode; $this->Clear(); // in case if Parse method is called more then one time $xml_parser = xml_parser_create(); xml_set_element_handler( $xml_parser, Array(&$this, 'startElement'), Array(&$this, 'endElement') ); @@ -34,7 +37,26 @@ return $root_copy; } - + + function ConvertHTMLEntities($s){ + //build first an assoc. array with the entities we want to match + $table1 = get_html_translation_table(HTML_ENTITIES, ENT_QUOTES); + + $patterns = array(); + $replacements = array(); + //now build another assoc. array with the entities we want to replace (numeric entities) + foreach ($table1 as $k=>$v){ + $patterns[] = "/$v/"; + // $c = htmlentities($k,ENT_QUOTES,"UTF-8"); + $replacements[] = "&#".ord($k).";"; + } + + //now perform a replacement using preg_replace + //each matched value in array 1 will be replaced with the corresponding value in array 2 + $s = preg_replace($patterns,$replacements,$s); + return $s; + } + function startElement(&$Parser, &$Elem, $Attrs) { $parent =& $this->CurrentElement; // 1. $parent is now reference to $this->CurrentElement @@ -49,11 +71,21 @@ function characterData($Parser, $Line) { + if ($this->Mode == XML_WITH_TEXT_NODES) { + $text_node = new kXMLNode('_TEXT_'); + $text_node->AppendData($Line); + $this->CurrentElement->AddChild( $text_node ); + } $this->CurrentElement->AppendData($Line); } function endElement($Parser, $Elem) { + if ($this->Mode == XML_WITH_TEXT_NODES) { + /*if (count($this->CurrentElement->Children) == 1 && $this->CurrentElement->firstChild->Name == '_TEXT_') { + $this->CurrentElement->Children = array(); + }*/ + } if ($this->CurrentElement->Parent != null) { $this->CurrentElement =& $this->CurrentElement->Parent; } @@ -86,11 +118,16 @@ * @var int */ var $Position = 0; + + var $CRC = null; function kXMLNode($name, $attrs = array()) { - $this->Name = $name; - $this->Attributes = $attrs; + $this->Name = strtoupper($name); + foreach ($attrs as $attr => $value) { + $this->Attributes[strtoupper($attr)] = $value; + } + $this->CRC = crc32($this->Name.join(array_keys($this->Attributes)).join(array_values($this->Attributes))); } function SetParent(&$elem) @@ -184,6 +221,27 @@ return $child->Data; } } + + /** + * Returns next node to this, false in case of end list + * + * @return kXMLNode + */ + function &PrevSibling() + { + if (!is_null($this->Parent) && $this->Position > 0) { + $pos = $this->Position - 1; + do { + $ret =& $this->Parent->GetChildByPosition($pos--); + } while ($ret->Name == '_TEXT_' && $pos >= 0); + if ($ret->Name == '_TEXT_') $ret = false; + return $ret; + } + else { + $false = false; + return $false; + } + } /** * Returns next node to this, false in case of end list @@ -193,7 +251,11 @@ function &NextSibling() { if (!is_null($this->Parent)) { - $ret =& $this->Parent->GetChildByPosition($this->Position + 1); + $pos = $this->Position + 1; + do { + $ret =& $this->Parent->GetChildByPosition($pos++); + } while ($ret->Name == '_TEXT_' && $pos < count($this->Parent->Children)); + if ($ret->Name == '_TEXT_') $ret = false; return $ret; } else { Index: branches/RC/core/kernel/constants.php =================================================================== diff -u -N -r9639 -r10294 --- branches/RC/core/kernel/constants.php (.../constants.php) (revision 9639) +++ branches/RC/core/kernel/constants.php (.../constants.php) (revision 10294) @@ -63,4 +63,13 @@ define('REGEX_EMAIL_USER', '[-a-zA-Z0-9!\#$%&*+\/=?^_`{|}~.]+'); define('REGEX_EMAIL_DOMAIN', '[a-zA-Z0-9]{1}[-.a-zA-Z0-9_]*\.[a-zA-Z]{2,6}'); define('ALLOW_DEFAULT_SETTINGS', '_USE_DEFAULT_USER_DATA_'); //Allow persistent vars to take data from default user's persistent data + + define('XML_NO_TEXT_NODES', 1); // Normal mode for XMLHelper + define('XML_WITH_TEXT_NODES', 2); // Will create text nodes for every char-data (used in kPDFHelper) + + // ChangeLog actions + define('clCREATE', 1); + define('clUPDATE', 2); + define('clDELETE', 3); + ?> \ No newline at end of file Index: branches/RC/core/units/logs/session_logs/session_log_eh.php =================================================================== diff -u -N --- branches/RC/core/units/logs/session_logs/session_log_eh.php (revision 0) +++ branches/RC/core/units/logs/session_logs/session_log_eh.php (revision 10294) @@ -0,0 +1,58 @@ +Application->recallObject($event->Prefix, null, Array ('skip_autoload' => 1)); + /* @var $object kDBItem */ + + $fields_hash = Array ( + 'SessionStart' => adodb_mktime(), + 'IP' => $_SERVER['REMOTE_ADDR'], + 'PortalUserId' => $this->Application->RecallVar('user_id'), + 'SessionId' => $this->Application->GetSID(), + 'Status' => 0, + ); + + $object->SetDBFieldsFromHash($fields_hash); + + $object->UpdateFormattersSubFields(); + + if ($object->Create()) { + $this->Application->StoreVar('_SessionLogId_', $object->GetID()); + } + } + + /** + * Closes log for current session + * + * @param kEvent $event + */ + function OnEndSession(&$event) + { + $object =& $this->Application->recallObject($event->Prefix, null, Array ('skip_autoload' => 1)); + /* @var $object kDBItem */ + + $object->Load($this->Application->RecallVar('_SessionLogId_')); + if (!$object->isLoaded()) { + return ; + } + + $fields_hash = Array ( + 'SessionEnd' => adodb_mktime(), + 'Status' => 1, + ); + + $object->SetDBFieldsFromHash($fields_hash); + + $object->UpdateFormattersSubFields(); + $object->Update(); + } + +} \ No newline at end of file Index: branches/RC/core/admin_templates/logs/change_logs/change_log_edit.tpl =================================================================== diff -u -N --- branches/RC/core/admin_templates/logs/change_logs/change_log_edit.tpl (revision 0) +++ branches/RC/core/admin_templates/logs/change_logs/change_log_edit.tpl (revision 10294) @@ -0,0 +1,88 @@ + + + + + + +
+ + + + + + +
+ + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + +
+
+ + + Index: branches/RC/core/kernel/utility/formatters/left_formatter.php =================================================================== diff -u -N -r8929 -r10294 --- branches/RC/core/kernel/utility/formatters/left_formatter.php (.../left_formatter.php) (revision 8929) +++ branches/RC/core/kernel/utility/formatters/left_formatter.php (.../left_formatter.php) (revision 10294) @@ -56,7 +56,8 @@ $options['options'][$found] = $value; } - if ($found === false) { + $skip_errors = array_key_exists('skip_errors', $options) && $options['skip_errors']; + if ($found === false && !$skip_errors) { // option not found at all -> return not formatted value & set error $object->SetError($field_name, 'invalid_option', 'la_error_InvalidOption'); return $value; Index: branches/RC/core/kernel/db/db_event_handler.php =================================================================== diff -u -N -r10024 -r10294 --- branches/RC/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 10024) +++ branches/RC/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 10294) @@ -1201,6 +1201,8 @@ $skip_master = false; $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); + $changes_var_name = $this->Prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix); + if (!$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $live_ids = $temp->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array()); @@ -1219,7 +1221,12 @@ // NOTE: only works if main item has subitems !!! $this->StoreSelectedIDs($event, $live_ids); } + + $this->SaveLoggedChanges($changes_var_name); } + else { + $this->Application->RemoveVar($changes_var_name); + } $this->clearSelectedIDs($event); $event->redirect_params = Array('opener' => 'u'); @@ -1230,6 +1237,40 @@ } } + function SaveLoggedChanges($changes_var_name) + { + $ses_log_id = $this->Application->RecallVar('_SessionLogId_'); + if (!$ses_log_id) { + return ; + } + + $changes = $this->Application->RecallVar($changes_var_name); + $changes = $changes ? unserialize($changes) : Array (); + if (!$changes) { + return ; + } + + $add_fields = Array ( + 'PortalUserId' => $this->Application->RecallVar('user_id'), + 'SessionLogId' => $ses_log_id, + ); + + $changelog_table = $this->Application->getUnitOption('change-log', 'TableName'); + $sessionlog_table = $this->Application->getUnitOption('session-log', 'TableName'); + + foreach ($changes as $rec) { + $this->Conn->doInsert(array_merge($rec, $add_fields), $changelog_table); + } + + $sql = 'UPDATE '.$sessionlog_table.' + SET AffectedItems = AffectedItems + '.count($changes).' + WHERE SessionLogId = '.$ses_log_id; + $this->Conn->Query($sql); + + $this->Application->RemoveVar($changes_var_name); + } + + /** * Cancels edit * Removes all temp tables and clears selected ids Index: branches/RC/core/units/configuration/configuration_config.php =================================================================== diff -u -N -r8929 -r10294 --- branches/RC/core/units/configuration/configuration_config.php (.../configuration_config.php) (revision 8929) +++ branches/RC/core/units/configuration/configuration_config.php (.../configuration_config.php) (revision 10294) @@ -16,6 +16,7 @@ ), 'IDField' => 'VariableId', + 'TitleField' => 'VariableName', 'TitlePresets' => Array( 'default' => Array('tag_params' => Array('conf' => Array('per_page' => -1))), Index: branches/RC/core/kernel/utility/temp_handler.php =================================================================== diff -u -N -r9643 -r10294 --- branches/RC/core/kernel/utility/temp_handler.php (.../temp_handler.php) (revision 9643) +++ branches/RC/core/kernel/utility/temp_handler.php (.../temp_handler.php) (revision 10294) @@ -602,6 +602,7 @@ $query = 'DELETE FROM '.$this->GetTempName($master['TableName']).' WHERE '.$master['IdField'].' = 0'; $this->Conn->Query($query); + $this->UpdateChangeLogForeignKeys($master, $live_ids[$an_id], $an_id); } } @@ -636,8 +637,31 @@ return $this->savedIDs[ $master['Prefix'] ]; } + + function UpdateChangeLogForeignKeys($master, $live_id, $temp_id) + { + $main_prefix = $this->Application->GetTopmostPrefix($master['Prefix']); + $ses_var_name = $main_prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix); + $changes = $this->Application->RecallVar($ses_var_name); + $changes = $changes ? unserialize($changes) : array(); + + foreach ($changes as $key => $rec) { + if ($rec['Prefix'] == $master['Prefix']) { + if ($rec['ItemId'] == $temp_id) { + $changes[$key]['ItemId'] = $live_id; + } + } + if ($rec['MasterPrefix'] == $master['Prefix']) { + if ($rec['MasterId'] == $temp_id) { + $changes[$key]['MasterId'] = $live_id; + } + } + } + $this->Application->StoreVar($ses_var_name, serialize($changes)); + } function UpdateForeignKeys($master, $live_id, $temp_id) { + $this->UpdateChangeLogForeignKeys($master, $live_id, $temp_id); foreach ($master['SubTables'] as $sub_table) { $foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey']; if (!$foreign_key_field) return; Index: branches/RC/core/units/general/helpers/controls/minput_helper.php =================================================================== diff -u -N -r10022 -r10294 --- branches/RC/core/units/general/helpers/controls/minput_helper.php (.../minput_helper.php) (revision 10022) +++ branches/RC/core/units/general/helpers/controls/minput_helper.php (.../minput_helper.php) (revision 10294) @@ -109,16 +109,77 @@ { $object =& $event->getObject(); /* @var $object kDBItem */ + + $sub_item =& $this->Application->recallObject($sub_prefix, null, Array('skip_autoload' => true)); + /* @var $sub_item kDBItem */ $foreign_key = $this->Application->getUnitOption($sub_prefix, 'ForeignKey'); $sql = 'SELECT * FROM '.$this->getTable($sub_prefix, $object->IsTempTable()).' WHERE '.$foreign_key.' = '.$object->GetID(); + $selected_items = $this->Conn->Query($sql); + + $field_names = array_keys( $sub_item->GetFieldValues() ); + + foreach ($selected_items as $key => $fields_hash) { + $sub_item->Clear(); + $sub_item->SetDBFieldsFromHash($fields_hash); + + // to fill *_date and *_time fields from main date fields + $sub_item->UpdateFormattersSubFields(); + + foreach ($field_names as $field) { + $field_options = $sub_item->GetFieldOptions($field); + $formatter = array_key_exists('formatter', $field_options) ? $field_options['formatter'] : false; + + if ($formatter == 'kDateFormatter') { + $selected_items[$key][$field] = $sub_item->GetField($field); + } + else { + $selected_items[$key][$field] = $sub_item->GetDBField($field); + } + } + } $object->SetDBField($store_field, $this->prepareMInputXML($selected_items, $use_fields)); } + + /** + * Saves data from minput control to subitem table (used from subitem hook) + * + * @param kEvent $sub_event + * @param string $store_field + */ + function SaveValues(&$sub_event, $store_field) + { + $main_object =& $sub_event->MasterEvent->getObject(); + $affected_field = $main_object->GetDBField($store_field); + + $object =& $this->Application->recallObject($sub_event->getPrefixSpecial(), null, Array('skip_autoload' => true)); + /*@var $object kDBItem*/ + + $sub_table = $object->TableName; + $foreign_key = $this->Application->getUnitOption($sub_event->Prefix, 'ForeignKey'); + + $sql = 'DELETE FROM '.$sub_table.' + WHERE '.$foreign_key.' = '.$main_object->GetID(); + + $this->Conn->Query($sql); + + if ($affected_field) { + $records = $this->parseMInputXML($affected_field); + $main_id = $main_object->GetID(); + + foreach ($records as $fields_hash) { + $object->Clear(); + $fields_hash[$foreign_key] = $main_id; + $object->SetDBFieldsFromHash($fields_hash); + $object->Create(); + } + } + } } ?> \ No newline at end of file Index: branches/RC/admin/install/upgrades/inportal_upgrade_v4.3.0.sql =================================================================== diff -u -N -r10209 -r10294 --- branches/RC/admin/install/upgrades/inportal_upgrade_v4.3.0.sql (.../inportal_upgrade_v4.3.0.sql) (revision 10209) +++ branches/RC/admin/install/upgrades/inportal_upgrade_v4.3.0.sql (.../inportal_upgrade_v4.3.0.sql) (revision 10294) @@ -21,5 +21,8 @@ INSERT INTO Counters VALUES (DEFAULT, 'members_online', 'SELECT COUNT(*) FROM <%PREFIX%>UserSession WHERE PortalUserId > 0', NULL , NULL , '3600', '0', '|UserSession|'); INSERT INTO Counters VALUES (DEFAULT, 'guests_online', 'SELECT COUNT(*) FROM <%PREFIX%>UserSession WHERE PortalUserId <= 0', NULL , NULL , '3600', '0', '|UserSession|'); +CREATE TABLE ChangeLogs (ChangeLogId bigint(20) NOT NULL auto_increment, PortalUserId int(11) NOT NULL default '0', SessionLogId int(11) NOT NULL default '0', `Action` tinyint(4) NOT NULL default '0', OccuredOn int(11) NOT NULL default '0', Prefix varchar(255) NOT NULL default '', ItemId bigint(20) NOT NULL default '0', Changes text NOT NULL, MasterPrefix varchar(255) NOT NULL default '', MasterId bigint(20) NOT NULL default '0', PRIMARY KEY (ChangeLogId), KEY PortalUserId (PortalUserId), KEY SessionLogId (SessionLogId), KEY `Action` (`Action`), KEY OccuredOn (OccuredOn), KEY Prefix (Prefix), KEY MasterPrefix (MasterPrefix)); +CREATE TABLE SessionLogs (SessionLogId bigint(20) NOT NULL auto_increment, PortalUserId int(11) NOT NULL default '0', SessionId int(10) NOT NULL default '0', `Status` tinyint(4) NOT NULL default '1', SessionStart int(11) NOT NULL default '0', SessionEnd int(11) default NULL, IP varchar(15) NOT NULL default '', AffectedItems int(11) NOT NULL default '0', PRIMARY KEY (SessionLogId), KEY SessionId (SessionId), KEY `Status` (`Status`)); + UPDATE Modules SET Version = '4.2.3' WHERE Name = 'Core'; UPDATE Modules SET Version = '4.3.0' WHERE Name = 'In-Portal'; \ No newline at end of file Index: branches/RC/core/kernel/session/session.php =================================================================== diff -u -N -r10111 -r10294 --- branches/RC/core/kernel/session/session.php (.../session.php) (revision 10111) +++ branches/RC/core/kernel/session/session.php (.../session.php) (revision 10294) @@ -236,7 +236,18 @@ { $expired_sids = $this->GetExpiredSIDs(); if ($expired_sids) { - $where_clause=' WHERE '.$this->IDField.' IN ("'.implode('","',$expired_sids).'")'; + $sessionlog_table = $this->Application->getUnitOption('session-log', 'TableName'); + $session_log_sql = + ' UPDATE '.$sessionlog_table.' + SET Status = 2, SessionEnd = + ( SELECT '.$this->TimestampField.' - '.$this->SessionTimeout.' + FROM '.$this->TableName.' + WHERE '.$this->IDField.' = '.$sessionlog_table.'.SessionId + ) + WHERE Status = 0 AND SessionId IN ('.join(',', $expired_sids).')'; + $this->Conn->Query($session_log_sql); + + $where_clause = ' WHERE '.$this->IDField.' IN ("'.implode('","',$expired_sids).'")'; $sql = 'DELETE FROM '.$this->SessionDataTable.$where_clause; $this->Conn->Query($sql); @@ -245,7 +256,7 @@ // delete debugger ouputs left of expired sessions foreach ($expired_sids as $expired_sid) { - $debug_file = KERNEL_PATH.'/../cache/debug_@'.$expired_sid.'@.txt'; + $debug_file = (defined('WRITEABLE') ? WRITEABLE : FULL_PATH.'/kernel').'/cache/debug_@'.$expired_sid.'@.txt'; if (file_exists($debug_file)) { @unlink($debug_file); } Index: branches/RC/core/units/pdf/pdf_config.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/pdf_config.php (revision 0) +++ branches/RC/core/units/pdf/pdf_config.php (revision 10294) @@ -0,0 +1,10 @@ + 'pdf', + 'EventHandlerClass' => Array('class' => 'kEventHandler', 'file' => '', 'build_event' => 'OnBuild'), + + 'RegisterClasses' => Array( + Array('pseudo'=>'PDFHelper','class'=>'kPDFHelper','file'=>'pdf_helper.php','build_event'=>'','require_classes'=>'kHelper'), + ), + ); \ No newline at end of file Index: branches/RC/core/units/pdf/pdf_renderer.php =================================================================== diff -u -N --- branches/RC/core/units/pdf/pdf_renderer.php (revision 0) +++ branches/RC/core/units/pdf/pdf_renderer.php (revision 10294) @@ -0,0 +1,27 @@ +