Index: branches/5.2.x/core/admin_templates/img/toolbar/tool_history_f3.gif =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/units/structure/structure_config.php =================================================================== diff -u -N -r14726 -r14856 --- branches/5.2.x/core/units/structure/structure_config.php (.../structure_config.php) (revision 14726) +++ branches/5.2.x/core/units/structure/structure_config.php (.../structure_config.php) (revision 14856) @@ -1,6 +1,6 @@ 'SELECT %1$s.* %2$s FROM %1$s', ), - 'SubItems' => Array('content'), + 'SubItems' => Array('content', 'page-revision'), 'ListSortings' => Array( '' => Array( @@ -205,6 +205,7 @@ ), 'PageCacheKey' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), 'PageExpiration' => Array ('type' => 'int', 'default' => NULL), + 'LiveRevisionNumber' => Array ('type' => 'int', 'not_null' => 1, 'default' => 1), 'DirectLinkEnabled' => Array ( 'type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, Index: branches/5.2.x/core/admin_templates/img/top_frame/revision_control/button_vp_left.png =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/install/upgrades.php =================================================================== diff -u -N -r14853 -r14856 --- branches/5.2.x/core/install/upgrades.php (.../upgrades.php) (revision 14853) +++ branches/5.2.x/core/install/upgrades.php (.../upgrades.php) (revision 14856) @@ -1,6 +1,6 @@ transformSortings(); $this->transformFieldPhrases(); // because of "la_col_ItemPrefix" phrase + $this->createPageRevisions(); } } @@ -1896,4 +1897,31 @@ $this->Conn->Query($sql); } } + + protected function createPageRevisions() + { + $sql = 'SELECT DISTINCT PageId + FROM ' . TABLE_PREFIX . 'PageContent'; + $page_ids = $this->Conn->GetCol($sql); + + foreach ($page_ids as $page_id) { + $fields_hash = Array ( + 'PageId' => $page_id, + 'RevisionNumber' => 1, + 'IsDraft' => 0, + 'FromRevisionNumber' => 0, + 'CreatedById' => USER_ROOT, + 'CreatedOn' => adodb_mktime(), + 'Status' => STATUS_ACTIVE, + ); + + $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'PageRevisions'); + + $fields_hash = Array ( + 'RevisionId' => $this->Conn->getInsertID(), + ); + + $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'PageContent', 'PageId = ' . $page_id); + } + } } \ No newline at end of file Index: branches/5.2.x/core/admin_templates/categories/edit_content.tpl =================================================================== diff -u -N -r14585 -r14856 --- branches/5.2.x/core/admin_templates/categories/edit_content.tpl (.../edit_content.tpl) (revision 14585) +++ branches/5.2.x/core/admin_templates/categories/edit_content.tpl (.../edit_content.tpl) (revision 14856) @@ -16,31 +16,21 @@ 'select', '', function() { - submit_event('content',''); + submit_event('content', 'OnSaveContentBlock'); } ) ); a_toolbar.AddButton( new ToolBarButton( 'cancel', - '', + '', function() { - cancel_edit('content','OnCancelEdit','',''); + window_close(); } ) ); - a_toolbar.AddButton( - new ToolBarButton( - 'reset_edit', - '', - function() { - reset_form('content', 'OnReset', ''); - } - ) - ); - a_toolbar.Render(); @@ -81,4 +71,35 @@ + + \ No newline at end of file Index: branches/5.2.x/core/admin_templates/img/top_frame/revision_control/close_black.gif =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/install/install_schema.sql =================================================================== diff -u -N -r14853 -r14856 --- branches/5.2.x/core/install/install_schema.sql (.../install_schema.sql) (revision 14853) +++ branches/5.2.x/core/install/install_schema.sql (.../install_schema.sql) (revision 14856) @@ -1,6 +1,6 @@ CREATE TABLE PermissionConfig ( PermissionConfigId int(11) NOT NULL auto_increment, - PermissionName varchar(30) NOT NULL default '', + PermissionName varchar(255) NOT NULL default '', Description varchar(255) NOT NULL default '', ModuleId varchar(20) NOT NULL default '0', IsSystem tinyint(1) NOT NULL DEFAULT '0', @@ -474,6 +474,7 @@ OverridePageCacheKey tinyint(4) NOT NULL DEFAULT '0', PageCacheKey varchar(255) NOT NULL DEFAULT '', PageExpiration int(11) DEFAULT NULL, + LiveRevisionNumber int(11) NOT NULL DEFAULT '1', DirectLinkEnabled tinyint(4) NOT NULL DEFAULT '1', DirectLinkAuthKey varchar(20) NOT NULL, PRIMARY KEY (CategoryId), @@ -503,7 +504,8 @@ KEY EnablePageCache (EnablePageCache), KEY OverridePageCacheKey (OverridePageCacheKey), KEY PageExpiration (PageExpiration), - KEY Protected (Protected) + KEY Protected (Protected), + KEY LiveRevisionNumber (LiveRevisionNumber) ); CREATE TABLE CategoryCustomData ( @@ -1025,15 +1027,34 @@ PageContentId int(11) NOT NULL AUTO_INCREMENT, ContentNum int(11) NOT NULL DEFAULT '0', PageId int(11) NOT NULL DEFAULT '0', + RevisionId int(11) NOT NULL, l1_Content text, l2_Content text, l3_Content text, l4_Content text, l5_Content text, PRIMARY KEY (PageContentId), - KEY ContentNum (ContentNum,PageId) + KEY ContentNum (ContentNum,PageId), + KEY RevisionId (RevisionId) ); +CREATE TABLE PageRevisions ( + RevisionId int(11) NOT NULL AUTO_INCREMENT, + PageId int(11) NOT NULL, + RevisionNumber int(11) NOT NULL, + IsDraft tinyint(4) NOT NULL, + FromRevisionId int(11) NOT NULL, + CreatedById int(11) DEFAULT NULL, + CreatedOn int(11) DEFAULT NULL, + AutoSavedOn int(11) DEFAULT NULL, + `Status` tinyint(4) NOT NULL DEFAULT '2', + PRIMARY KEY (RevisionId), + KEY PageId (PageId), + KEY RevisionNumber (RevisionNumber), + KEY IsDraft (IsDraft), + KEY `Status` (`Status`) +); + CREATE TABLE FormFields ( FormFieldId int(11) NOT NULL AUTO_INCREMENT, FormId int(11) NOT NULL DEFAULT '0', Index: branches/5.2.x/core/admin_templates/img/toolbar/tool_preview_f2.gif =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/admin_templates/img/toolbar/tool_preview.gif =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/admin_templates/img/toolbar/tool_preview_f3.gif =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/admin_templates/js/toolbar.js =================================================================== diff -u -N -r14244 -r14856 --- branches/5.2.x/core/admin_templates/js/toolbar.js (.../toolbar.js) (revision 14244) +++ branches/5.2.x/core/admin_templates/js/toolbar.js (.../toolbar.js) (revision 14856) @@ -18,15 +18,13 @@ } - if (typeof(onclick) == 'function') { + if ( $.isFunction(onclick) ) { this.onClick = onclick; } else { this.onClick = function() { - if (eval('typeof('+this.Title+')') == 'function') - eval(this.Title + '()'); + this.runFunction(this.Title); } - } this.imgObject = null; @@ -59,17 +57,13 @@ ToolBarButton.prototype.IconsPath = function() { - if (typeof(img_path) == 'undefined') { - //alert('error: toolbar image path not set'); - } - var $module_path = this.Module; if (this.Module != 'core') { $module_path = 'modules/' + $module_path; } - return img_path.replace('#MODULE#', $module_path) + 'toolbar/'; + return this.ToolBar.IconPath.replace('#MODULE#', $module_path) + 'toolbar/'; } ToolBarButton.prototype.GetHTML = function() { @@ -150,6 +144,12 @@ }; } +ToolBarButton.prototype.runFunction = function($name) { + if ( window[$name] !== undefined && $.isFunction( window[$name] ) ) { + window[$name](); + } +} + ToolBarButton.prototype.SetOnClick = function() { // we have SetOnMouseOut for this ??? /*this.Container.onmouseout = function() { @@ -162,9 +162,7 @@ if (this.inClick || this.btn.ReadOnly) return; this.inClick = true; - if (eval('typeof('+this.btn.Title+')') == 'function') { - eval(this.btn.Title + '()'); - } + this.runFunction(this.btn.Title); this.inClick = false; } @@ -285,13 +283,24 @@ /* ----------- */ -function ToolBar(icon_prefix, $module) +function ToolBar(icon_prefix, $module, $image_path) { this.Module = $module ? $module : 'core'; this.IconPrefix = icon_prefix ? icon_prefix : 'tool_'; this.IconSize = {w:32,h:32}; this.Buttons = {}; this.UseLabels = typeof($use_toolbarlabels) != 'undefined' ? $use_toolbarlabels : false; + + if ( $image_path !== undefined ) { + this.IconPath = $image_path; + } + else if ( typeof(img_path) != 'undefined' ) { + this.IconPath = img_path; + } + else { + this.IconPath = ''; +// alert('error: toolbar image path not set'); + } } ToolBar.prototype.AddButton = function(a_button) Index: branches/5.2.x/core/install/english.lang =================================================================== diff -u -N -r14853 -r14856 --- branches/5.2.x/core/install/english.lang (.../english.lang) (revision 14853) +++ branches/5.2.x/core/install/english.lang (.../english.lang) (revision 14856) @@ -37,6 +37,7 @@ TG9jYXRl TW92ZSBEb3du TW92ZSBVcA== + UHVibGlzaGluZyBUb29scw== UmVidWlsZA== UmVjb21waWxl UmVmcmVzaA== @@ -54,6 +55,7 @@ VW5zZWxlY3Q= VXA= VXNl + Ynk= Q2FuY2Vs U2VjdGlvbg== TnVtYmVyIG9mIGRheXMgZm9yIGEgY2F0LiB0byBiZSBORVc= @@ -228,7 +230,9 @@ RG93bmxvYWQgQ1NW RG93bmxvYWQgRXhwb3J0IEZpbGU= RG93bmxvYWQgTGFuZ3VhZ2UgRXhwb3J0 + RHJhZnQ= RHJhZnQgQXZhaWxhYmxl + ZHJhZnQgc2F2ZWQgYXQgJXM= Q29udGVudCBFZGl0b3I= WW91IGhhdmUgbm90IHNhdmVkIGNoYW5nZXMgdG8gdGhlIGl0ZW0geW91IGFyZSBlZGl0aW5nITxiciAvPkNsaWNrIE9LIHRvIGxvb3NlIGNoYW5nZXMgYW5kIGdvIHRvIHRoZSBzZWxlY3RlZCBzZWN0aW9uPGJyIC8+b3IgQ2FuY2VsIHRvIHN0YXkgaW4gdGhlIGN1cnJlbnQgc2VjdGlvbi4= RGVmYXVsdCB0ZXh0 @@ -762,6 +766,7 @@ Q3VzdG9tICJUbyIgUmVjaXBpZW50KC1zKQ== Q3VzdG9tIFNlbmRlcg== ZGF5KHMp + RGVjbGluZWQ= RGVmYXVsdCBXZWJzaXRlIGFkZHJlc3M= RGVueQ== RGVzY3JpcHRpb24= @@ -801,6 +806,7 @@ UGhvbmU= UG9wdXAgV2luZG93 UHJvY2Vzc2Vk + UHVibGlzaGVk UXVlcnkgU3RyaW5nIChTSUQp UmF0aW5n UmVjaXBpZW50IEUtbWFpbA== @@ -847,6 +853,9 @@ KEdNVCArMDk6MDAp UGFkZGluZ3M= UGFnZQ== + QXR0ZW50aW9uOiAlcyBpcyBjdXJyZW50bHkgZWRpdGluZyB0aGlzIHNlY3Rpb24h + QXR0ZW50aW9uOiAlcyBhcmUgY3VycmVudGx5IGVkaXRpbmcgdGhpcyBzZWN0aW9uISA= + QXR0ZW50aW9uOiAlcyBhcmUgY3VycmVudGx5IGVkaXRpbmcgdGhpcyBzZWN0aW9uIQ== UGFzc3dvcmRzIGRvIG5vdCBtYXRjaA== UGFzc3dvcmQgaXMgdG9vIHNob3J0LCBwbGVhc2UgZW50ZXIgYXQgbGVhc3QgJXMgY2hhcmFjdGVycw== UGVuZGluZw== @@ -1007,8 +1016,10 @@ Tm90IGFsbCByZXF1aXJlZCBmaWVsZHMgYXJlIGZpbGxlZC4gUGxlYXNlIGZpbGwgdGhlbSBmaXJzdC4= Q29tbWVudHMgcGVyIFBhZ2U= Q29tbWVudHMgcGVyIFBhZ2UgKHNob3J0LWxpc3Qp + UmV2aXNpb24gIyVz SG9tZQ== U2FtcGxlIFRleHQ= + c2F2ZWQgYXQgJXM= U2F2ZSBVc2VybmFtZSBvbiBUaGlzIENvbXB1dGVy U2VhcmNo QmFzaWMgUGVybWlzc2lvbnM= @@ -1264,7 +1275,9 @@ RWRpdGluZyBBZ2VudA== RWRpdGluZyBCYW4gUnVsZQ== RWRpdGluZyBDaGFuZ2VzIExvZw== + Q29udGVudCBFZGl0b3IgLSBBdXRvLXNhdmVkIGF0ICVz RWRpdGluZyBDb3VudHJ5L1N0YXRl + RWRpdGluZyBEcmFmdCAoJTIkcyk= RWRpdGluZyBFbWFpbCBFdmVudA== RWRpdGluZyBGaWxl RWRpdGluZyBNZW1iZXJzaGlw @@ -1378,6 +1391,7 @@ Vmlld2luZyBmb3JtIHN1Ym1pc3Npb24= Vmlld2luZyBNYWlsaW5nIExpc3Q= Vmlld2luZyBSZXBseQ== + Vmlld2luZyBSZXZpc2lvbiAjJXMgKCVzKQ== VmlzaXRz V2Vic2l0ZQ== dG8= @@ -1404,13 +1418,15 @@ RGVueQ== RGV0YWlscw== RGlzYWJsZQ== + RGlzY2FyZA== RWRpdA== RWRpdCBDdXJyZW50IFNlY3Rpb24= RnJvbnQtRW5kIE9ubHk= RW5hYmxl RXhwb3J0 RXhwb3J0IExhbmd1YWdl SGlkZSBNZW51 + SGlzdG9yeQ== SG9tZQ== SW1wb3J0 SW1wb3J0IExhbmd1YWdl @@ -1444,9 +1460,11 @@ TmV4dA== UGFzdGU= UHJldmlvdXM= + UHJldmlldw== U2V0IFByaW1hcnkgR3JvdXA= UHJpbnQ= UHJvY2VzcyBRdWV1ZQ== + UHVibGlzaA== UmVidWlsZCBTZWN0aW9uIENhY2hl UmVjYWxjdWxhdGUgUHJpb3JpdGllcw== UmVmcmVzaA== Index: branches/5.2.x/core/kernel/languages/phrases_cache.php =================================================================== diff -u -N -r14699 -r14856 --- branches/5.2.x/core/kernel/languages/phrases_cache.php (.../phrases_cache.php) (revision 14699) +++ branches/5.2.x/core/kernel/languages/phrases_cache.php (.../phrases_cache.php) (revision 14856) @@ -1,6 +1,6 @@ Application->isAdmin) { $language_id = $this->Application->Session->GetField('Language'); + $this->AdminLanguageId = $language_id; // same languages, when used from Admin Console } else { $language_id = $this->Application->GetVar('m_lang'); Index: branches/5.2.x/core/install/install_data.sql =================================================================== diff -u -N -r14853 -r14856 --- branches/5.2.x/core/install/install_data.sql (.../install_data.sql) (revision 14853) +++ branches/5.2.x/core/install/install_data.sql (.../install_data.sql) (revision 14856) @@ -497,6 +497,11 @@ INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.DELETE', 'la_PermName_Category.Delete_desc', 'In-Portal', 1); INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.ADD.PENDING', 'la_PermName_Category.AddPending_desc', 'In-Portal', 1); INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.MODIFY', 'la_PermName_Category.Modify_desc', 'In-Portal', 1); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.ADD', 'la_PermName_Category.Revision.Add_desc', 'In-Portal', 1); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.ADD.PENDING', 'la_PermName_Category.Revision.Add.Pending_desc', 'In-Portal', 1); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.MODERATE', 'la_PermName_Category.Revision.Moderate_desc', 'In-Portal', 1); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.HISTORY.VIEW', 'la_PermName_Category.Revision.History.View_desc', 'In-Portal', 1); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.HISTORY.RESTORE', 'la_PermName_Category.Revision.History.Restore_desc', 'In-Portal', 1); INSERT INTO PermissionConfig VALUES (DEFAULT, 'ADMIN', 'la_PermName_Admin_desc', 'Admin', 1); INSERT INTO PermissionConfig VALUES (DEFAULT, 'LOGIN', 'la_PermName_Login_desc', 'Front', 1); INSERT INTO PermissionConfig VALUES (DEFAULT, 'DEBUG.ITEM', 'la_PermName_Debug.Item_desc', 'Admin', 1); @@ -552,6 +557,9 @@ INSERT INTO Permissions VALUES(DEFAULT, 'CATEGORY.ADD.PENDING', 13, 1, 0, 1); INSERT INTO Permissions VALUES(DEFAULT, 'CATEGORY.DELETE', 11, 1, 0, 1); INSERT INTO Permissions VALUES(DEFAULT, 'CATEGORY.MODIFY', 11, 1, 0, 1); +INSERT INTO Permissions VALUES(DEFAULT, 'CATEGORY.REVISION.ADD', 11, 1, 0, 1); +INSERT INTO Permissions VALUES(DEFAULT, 'CATEGORY.REVISION.HISTORY.VIEW', 11, 1, 0, 1); +INSERT INTO Permissions VALUES(DEFAULT, 'CATEGORY.REVISION.HISTORY.RESTORE', 11, 1, 0, 1); INSERT INTO Permissions VALUES (DEFAULT, 'in-portal:service.view', 11, 1, 1, 0); INSERT INTO Permissions VALUES (DEFAULT, 'in-portal:service.edit', 11, 1, 1, 0); Index: branches/5.2.x/core/admin_templates/img/top_frame/revision_control/button_vp_right2.png =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/admin_templates/img/toolbar/tool_history.gif =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/units/content/content_eh.php =================================================================== diff -u -N -r14628 -r14856 --- branches/5.2.x/core/units/content/content_eh.php (.../content_eh.php) (revision 14628) +++ branches/5.2.x/core/units/content/content_eh.php (.../content_eh.php) (revision 14856) @@ -1,6 +1,6 @@ finalizePermissionCheck($event, $perm_status); } + + /** + * Saves changes to a content block (+ creates draft if missing) + * + * @param kEvent $event + */ + function OnSaveContentBlock(&$event) + { + if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { + $event->status = erFAIL; + return ; + } + + if ( !$this->saveContentBlock($event, false) ) { + $event->status = erFAIL; + } + + $event->SetRedirectParam('opener', 'u'); + } + + /** + * Performs auto-save of current content block (will create draft too) + * + * @param kEvent $event + */ + function OnAutoSave(&$event) + { + $event->status = erSTOP; + + if ( $this->Application->GetVar('ajax') != 'yes' ) { + return ; + } + + echo $this->saveContentBlock($event, true); + } + + /** + * Saves content block + * + * @param kEvent $event + * @param bool $is_draft + * @return string + */ + function saveContentBlock(&$event, $is_draft) + { + $object =& $event->getObject( Array('skip_autoload' => true) ); + /* @var $object kDBItem */ + + $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + if ( !$items_info ) { + return ; + } + + list ($object, $revision) = $this->getContentBlockAndRevision($event); + + list (, $field_values) = each($items_info); + $object->SetFieldsFromHash($field_values); + $updated = $object->Update(); + + if ( $updated ) { + $revision->SetDBField('AutoSavedOn_date', adodb_mktime()); + $revision->SetDBField('AutoSavedOn_time', adodb_mktime()); + $revision->Update(); + } + + if ( $is_draft ) { + if ( $updated ) { + $page_helper =& $this->Application->recallObject('PageHelper'); + /* @var $page_helper PageHelper */ + + return $revision->GetField('AutoSavedOn') . ' (' . $page_helper->getAgoTime( $revision->GetDBField('AutoSavedOn') ) . ')'; + } + } + else { + return $updated; + } + + return ''; + } + + /** + * Returns last autosave time + * + * @param kEvent $event + */ + function OnGetAutoSaveTime(&$event) + { + $event->status = erSTOP; + + if ( $this->Application->GetVar('ajax') != 'yes' ) { + return ; + } + + list ($object, $revision) = $this->getContentBlockAndRevision($event); + + $page_helper =& $this->Application->recallObject('PageHelper'); + /* @var $page_helper PageHelper */ + + $time = $revision->GetField('AutoSavedOn'); + + if ( $time ) { + echo $time . ' (' . $page_helper->getAgoTime( $revision->GetDBField('AutoSavedOn') ) . ')'; + } + } + + /** + * Loads content block from given revision + * + * @param kDBItem $object + * @param kDBItem $revision + */ + function loadFromRevision(&$object, &$revision) + { + $load_keys = Array ( + 'PageId' => $object->GetDBField('PageId'), + 'ContentNum' => $object->GetDBField('ContentNum'), + 'RevisionId' => $revision->GetID(), + ); + + $object->Load($load_keys); + } + + function getContentBlockAndRevision(&$event) + { + $object =& $event->getObject( Array('skip_autoload' => true) ); + /* @var $object kDBItem */ + + $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + if ( !$items_info ) { + return ; + } + + list ($id, $field_values) = each($items_info); + $object->Load($id); + + $revision =& $this->Application->recallObject('page-revision', null, Array ('skip_autoload' => true)); + /* @var $revision kDBItem */ + + $revision->Load( $object->GetDBField('RevisionId') ); + + if ( !$revision->GetDBField('IsDraft') ) { + // editing live revision of a page's content block -> get draft for current user and page + $load_keys = Array ( + 'PageId' => $revision->GetDBField('PageId'), + 'IsDraft' => 1, + 'CreatedById' => $this->Application->RecallVar('user_id'), + ); + + $revision->Load($load_keys); + + if ( $revision->isLoaded() ) { + // draft found -> use draft's content block version + $this->loadFromRevision($object, $revision); + } + else { + // draft not found -> create new + $revision->SetDBFieldsFromHash($load_keys); + $revision->SetDBField('FromRevisionId', $object->GetDBField('RevisionId')); + + if ( $revision->Create() ) { + $this->loadFromRevision($object, $revision); + } + } + } + + return Array (&$object, &$revision); + } } \ No newline at end of file Index: branches/5.2.x/core/admin_templates/incs/cms.css =================================================================== diff -u -N -r14244 -r14856 --- branches/5.2.x/core/admin_templates/incs/cms.css (.../cms.css) (revision 14244) +++ branches/5.2.x/core/admin_templates/incs/cms.css (.../cms.css) (revision 14856) @@ -175,4 +175,191 @@ display: none; opacity: 1; filter: alpha(opacity=100); -} \ No newline at end of file +} + + +/* === Misc Styles for "Content Revision Control" button === */ +.cms-clear { clear: both; } +.cms-right { float: right; } +.cms-left { float: left; } + + +/* === Current Revision Information === */ +#cms-current-revision-info { + color: #4b4b4b; + font-size: 11px; + font-family: Arial, Helvetica, sans-serif; + font-weight: bold; + white-space: nowrap; + float: left; + padding: 0px 30px; +} + +#cms-current-revision-info span { + display: block; + /*color: #008c1b;*/ + font-size: 16px; + display: block; + padding: 10px 0px 3px 0px; +} + +#cms-current-revision-info span.cms-revision-published { color: #15b300; } +#cms-current-revision-info span.cms-revision-pending { color: #ff9600; } +#cms-current-revision-info span.cms-revision-declined { color: #f90000; } + + +/* === Revision Editing Toolbar === */ +#cms-revision-toolbar { + background-color: #F0F1EB; + border-collapse: collapse; + border-color: #aaaaaa; + border-style: solid; + border-width: 0 1px 1px; + font-size: 8pt; +} + +#cms-revision-toolbar-layer { + position: absolute; + width: 630px; + top: 0px; + z-index: 100; +} + +.toolbar-button, .toolbar-button-disabled, .toolbar-button-over { + color: #006F99; + float: left; + font-size: 8pt; + padding: 5px; + text-align: center; + vertical-align: middle; +} + +a#cms-toggle-revision-toolbar { + display: block; + background: url(@templates_base@/img/top_frame/revision_control/button_vp_right.png) top right no-repeat; + padding-right: 24px; + float: right; + margin-top: -1px; + text-decoration: none; +} + +a#cms-toggle-revision-toolbar span { + display: block; + background: url(@templates_base@/img/top_frame/revision_control/button_vp_left.png) top left no-repeat; + line-height: 30px; + color: #000000; + font-family: Arial, Helvetica, sans-serif; + font-size: 13px; + font-weight: bold; + padding: 0px 10px 0px 15px; +} + +a#cms-toggle-revision-toolbar:hover span { + color: #666666; +} + +a#cms-toggle-revision-toolbar.opened { + background: url(@templates_base@/img/top_frame/revision_control/button_vp_right2.png) top right no-repeat; +} + +#cms-close-toolbar { + float: right; + display: block; + width: 10px; + height: 10px; + background: url(@templates_base@/img/top_frame/revision_control/close_black.gif) top left; + overflow: hidden; + margin: 10px 10px 0 0; +} + + +/* === Revision Editing Notice === */ +#cms-editing-notice { + width: 230px; + position: absolute; + z-index: 101; + margin-top: 50px; + margin-left: 10px; + + display: none; +} + +#cms-editing-notice .top { + background: url(@templates_base@/img/top_frame/revision_control/message_background_red.png) left top no-repeat; + padding: 30px 20px 10px 20px; + color: #FFFFFF; + font-weight: bold; + font-family: Arial, Helvetica, sans-serif; +} + +#cms-editing-notice .bottom { + background: url(@templates_base@/img/top_frame/revision_control/message_background_red.png) left bottom no-repeat; + height: 6px; +} + +#cms-close-editing-notice { + float:right; + display:block; + width:10px; + height:10px; + background:url(@templates_base@/img/top_frame/revision_control/close_white.gif) top left; + overflow:hidden; + margin:-5px -10px 0 0; +} + + +/* === Revision Dropdown === */ +#cms-revision-dropdown { + width: 240px; + position: absolute; + margin-left: 260px; + margin-top: 30px; + z-index: 102; + + display: none; +} + +#cms-revision-dropdown .top { + border-top: 1px solid #aaaaaa; + border-left: 1px solid #aaaaaa; + border-right: 1px solid #aaaaaa; +} + +#cms-revision-dropdown .item, #cms-revision-dropdown .item:hover { + color: #666666; + background: url(@templates_base@/img/top_frame/revision_control/history_item_background.gif) top left repeat-x #FFFFFF; + padding: 10px 13px; + border-top: 1px solid #e7e7e7; + font-size: 11px; + cursor: pointer; +} + +#cms-revision-dropdown .item:hover { + background: url(@templates_base@/img/top_frame/revision_control/history_item_background_hover.gif) top left repeat-x #F8F8F8; +} + +#cms-revision-dropdown .item .red { + color: #FF0000; + padding-top: 6px; +} + +#cms-revision-dropdown .bottom { + background: url(@templates_base@/img/top_frame/revision_control/history_bottom.png) top left no-repeat; + height: 6px; +} + +.item .cms-revision-published, .item .cms-revision-pending, .item .cms-revision-declined { + display: block; + font-size: 13px; + font-weight: bold; + padding-bottom: 8px; +} + +.item .cms-revision-published, .item .cms-revision-published a { color: #15b300 !important; } +.item .cms-revision-published a:hover { color: #1ada00 !important; } + +.item .cms-revision-pending, .item .cms-revision-pending a { color: #ff9600 !important; } +.item .cms-revision-pending a:hover { color: #ffba58 !important; } + +.item .cms-revision-declined, .item .cms-revision-declined a { color: #f90000 !important; } +.item .cms-revision-declined a:hover { color: #ff6666 !important; } \ No newline at end of file Index: branches/5.2.x/core/units/page_revisions/page_revision_eh.php =================================================================== diff -u -N --- branches/5.2.x/core/units/page_revisions/page_revision_eh.php (revision 0) +++ branches/5.2.x/core/units/page_revisions/page_revision_eh.php (revision 14856) @@ -0,0 +1,359 @@ +Name == 'OnItemBuild' ) { + return true; + } + + if ( $event->Name == 'OnGetInfo' || $event->Name == 'OnDiscard' ) { + return $this->Application->isAdminUser; + } + + $perm_helper =& $this->Application->recallObject('PermissionsHelper'); + /* @var $perm_helper kPermissionsHelper */ + + if ( $event->Name == 'OnSave' ) { + $perm_status = $this->Application->CheckPermission('CATEGORY.REVISION.ADD', 0) || $this->Application->CheckPermission('CATEGORY.REVISION.ADD.PENDING', 0); + + return $perm_helper->finalizePermissionCheck($event, $perm_status); + } + + if ( $event->Name == 'OnPublish' || $event->Name == 'OnDecline' ) { + $perm_status = $this->Application->CheckPermission('CATEGORY.REVISION.MODERATE', 0); + + return $perm_helper->finalizePermissionCheck($event, $perm_status); + } + + return parent::CheckPermission($event); + } + + /** + * Lists all current page revisions + * + * @param kEvent $event + */ + function SetCustomQuery(&$event) + { + parent::SetCustomQuery($event); + + $object =& $event->getObject(); + /* @var $object kDBList */ + + $page_id = $event->getEventParam('page_id'); + + if ( $this->Application->isAdmin ) { + $user_id = $this->Application->RecallVar('user_id'); + } + else { + $user_id = $this->Application->RecallVar('admin_user_id'); + } + + $object->addFilter('draft_filter', 'IF(%1$s.IsDraft = 1, %1$s.CreatedById = ' . $user_id . ', TRUE)'); + + if ( $page_id !== false ) { + $object->addFilter('parent_filter', '%1$s.PageId = ' . $page_id); + } + } + + /** + * Returns current page revision + * + * @param kEvent $event + */ + function getPassedID(&$event) + { + if ( $event->Special == 'current' ) { + $page =& $this->Application->recallObject('st.-virtual'); + /* @var $page kDBItem */ + + $page_helper =& $this->Application->recallObject('PageHelper'); + /* @var $page_helper PageHelper */ + + $page_id = $page->GetID(); + $revision_clause = $page_helper->getRevsionWhereClause($page_id, $page->GetDBField('LiveRevisionNumber')); + + $sql = 'SELECT RevisionId + FROM ' . TABLE_PREFIX . 'PageRevisions + WHERE (PageId = ' . $page_id . ') AND (' . $revision_clause . ') + ORDER BY IsDraft DESC, RevisionNumber DESC'; + $id = $this->Conn->GetOne($sql); + + if ( $id ) { + return $id; + } + + // no revisions -> create live revision + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $object->SetDBField('PageId', $page_id); + $object->SetDBField('RevisionNumber', 1); + $object->SetDBField('Status', STATUS_ACTIVE); + $object->Create(); + + return $object->GetID(); + } + + return parent::getPassedID($event); + } + + /** + * Remembers, who created revision + * + * @param kEvent $event + */ + function OnBeforeItemCreate(&$event) + { + parent::OnBeforeItemCreate($event); + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + if ( $this->Application->isAdmin ) { + $object->SetDBField('CreatedById', $this->Application->RecallVar('user_id')); + } + else { + $object->SetDBField('CreatedById', $this->Application->RecallVar('admin_user_id')); + } + } + + /** + * Updates revision creation time + * + * @param kEvent $event + */ + function OnBeforeItemUpdate(&$event) + { + parent::OnBeforeItemUpdate($event); + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + if ( $object->GetDBField('IsDraft') == 0 && $object->GetOriginalField('IsDraft') == 1 ) { + $object->SetDBField('CreatedOn_date', adodb_mktime()); + $object->SetDBField('CreatedOn_time', adodb_mktime()); + } + } + + /** + * Creates new content blocks based on source revision + * + * @param kEvent $event + */ + function OnAfterItemCreate(&$event) + { + parent::OnAfterItemCreate($event); + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + if ( !$object->GetDBField('FromRevisionId') ) { + return ; + } + + $content =& $this->Application->recallObject('content.-item', null, Array ('skip_autoload' => true)); + /* @var $content kDBItem */ + + $sql = $content->GetSelectSQL() . ' + WHERE pr.RevisionId = ' . $object->GetDBField('FromRevisionId'); + $content_blocks = $this->Conn->Query($sql); + + foreach ($content_blocks as $content_block) { + $content->LoadFromHash($content_block); + $content->SetDBField('RevisionId', $object->GetID()); + $content->Create(); + } + } + + /** + * Mark revision as current, once it's approved + * + * @param kEvent $event + */ + function OnAfterItemUpdate(&$event) + { + parent::OnAfterItemUpdate($event); + + $object =& $event->getObject(); + /* @var $object kDBItem */ + + $status = $object->GetDBField('Status'); + + if ( $status != $object->GetOriginalField('Status') && $status == STATUS_ACTIVE ) { + $page =& $this->Application->recallObject('c.revision', null, Array ('skip_autoload' => true)); + /* @var $page kDBItem */ + + $page->Load( $object->GetDBField('PageId') ); + $page->SetDBField('LiveRevisionNumber', $object->GetDBField('RevisionNumber')); + $page->Update(); + } + } + + /** + * Returns user, who are edting current page right now + * + * @param kEvent $event + */ + function OnGetInfo(&$event) + { + $event->status = erSTOP; + + if ( $this->Application->GetVar('ajax') != 'yes' ) { + return ; + } + + $page_helper =& $this->Application->recallObject('PageHelper'); + /* @var $page_helper PageHelper */ + + $page_id = $this->Application->GetVar('m_cat_id'); + echo json_encode( $page_helper->getPageInfo($page_id) ); + } + + /** + * Saves user draft into live revision + * + * @param kEvent $event + */ + function OnSave(&$event) + { + $revision_id = $this->getCurrentDraftRevision($event); + + if ( $revision_id ) { + $object =& $event->getObject( Array('skip_autoload' => true) ); + /* @var $object kDBItem */ + + $object->Load($revision_id); + $object->SetDBField('IsDraft', 0); + $object->SetDBField('RevisionNumber', $this->getNextAvailableRevision($event)); + + if ( $this->Application->CheckPermission('CATEGORY.REVISION.ADD', 0) ) { + $object->SetDBField('Status', STATUS_ACTIVE); + } + elseif ( $this->Application->CheckPermission('CATEGORY.REVISION.ADD.PENDING', 0) ) { + $object->SetDBField('Status', STATUS_PENDING); + } + + $object->Update(); + } + + $event->SetRedirectParam('opener', 'u'); + } + + /** + * Discards user draft + * + * @param kEvent $event + */ + function OnDiscard(&$event) + { + $revision_id = $this->getCurrentDraftRevision($event); + + if ( $revision_id ) { + $temp_handler =& $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); + /* @var $temp_handler kTempTablesHandler */ + + $temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($revision_id)); + } + + $event->SetRedirectParam('opener', 'u'); + } + + /** + * Makes revision live + * + * @param kEvent $event + */ + function OnPublish(&$event) + { + $revision =& $this->Application->recallObject('page-revision.current'); + /* @var $revision kDBItem */ + + if ( !$revision->isLoaded() || $revision->GetDBField('Status') == STATUS_ACTIVE || $revision->GetDBField('IsDraft') ) { + return ; + } + + $revision->SetDBField('Status', STATUS_ACTIVE); + $revision->Update(); + + $event->SetRedirectParam('opener', 'u'); + } + + /** + * Denies changes made in revision + * + * @param kEvent $event + */ + function OnDecline(&$event) + { + $revision =& $this->Application->recallObject('page-revision.current'); + /* @var $revision kDBItem */ + + if ( !$revision->isLoaded() || $revision->GetDBField('Status') == STATUS_DISABLED || $revision->GetDBField('IsLive') || $revision->GetDBField('IsDraft') ) { + return ; + } + + $revision->SetDBField('Status', STATUS_DISABLED); + $revision->Update(); + + $event->SetRedirectParam('opener', 'u'); + } + + /** + * Returns revision id of user's draft + * + * @param kEvent $event + * @return int + */ + function getCurrentDraftRevision(&$event) + { + $where_clause = Array ( + 'IsDraft = 1', + 'PageId = ' . $this->Application->GetVar('m_cat_id'), + 'CreatedById = ' . $this->Application->RecallVar('user_id'), + ); + + $sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . ' + FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' + WHERE (' . implode(') AND (', $where_clause) . ')'; + + return $this->Conn->GetOne($sql); + } + + /** + * Returns next available revision number for current page + * + * @param kEvent $event + * @return int + */ + function getNextAvailableRevision(&$event) + { + $sql = 'SELECT MAX(RevisionNumber) + FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' + WHERE PageId = ' . $this->Application->GetVar('m_cat_id'); + $max_revision = (int)$this->Conn->GetOne($sql); + + return $max_revision + 1; + } +} Index: branches/5.2.x/core/install/remove_schema.sql =================================================================== diff -u -N -r14789 -r14856 --- branches/5.2.x/core/install/remove_schema.sql (.../remove_schema.sql) (revision 14789) +++ branches/5.2.x/core/install/remove_schema.sql (.../remove_schema.sql) (revision 14856) @@ -62,6 +62,7 @@ DROP TABLE StopWords; DROP TABLE MailingLists; DROP TABLE PageContent; +DROP TABLE PageRevisions; DROP TABLE FormFields; DROP TABLE FormSubmissions; DROP TABLE SubmissionLog; Index: branches/5.2.x/core/admin_templates/img/top_frame/revision_control/history_bottom.png =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/install/upgrades.sql =================================================================== diff -u -N -r14853 -r14856 --- branches/5.2.x/core/install/upgrades.sql (.../upgrades.sql) (revision 14853) +++ branches/5.2.x/core/install/upgrades.sql (.../upgrades.sql) (revision 14856) @@ -2340,4 +2340,41 @@ UPDATE Phrase SET l<%PRIMARY_LANGUAGE%>_ColumnTranslation = l<%PRIMARY_LANGUAGE%>_Translation -WHERE PhraseKey IN ('LA_FLD_CATEGORY', 'LA_FLD_ORDER'); \ No newline at end of file +WHERE PhraseKey IN ('LA_FLD_CATEGORY', 'LA_FLD_ORDER'); + +CREATE TABLE PageRevisions ( + RevisionId int(11) NOT NULL AUTO_INCREMENT, + PageId int(11) NOT NULL, + RevisionNumber int(11) NOT NULL, + IsDraft tinyint(4) NOT NULL, + FromRevisionId int(11) NOT NULL, + CreatedById int(11) DEFAULT NULL, + CreatedOn int(11) DEFAULT NULL, + AutoSavedOn int(11) DEFAULT NULL, + `Status` tinyint(4) NOT NULL DEFAULT '2', + PRIMARY KEY (RevisionId), + KEY PageId (PageId), + KEY RevisionNumber (RevisionNumber), + KEY IsDraft (IsDraft), + KEY `Status` (`Status`) +); + +ALTER TABLE Category + ADD LiveRevisionNumber INT NOT NULL DEFAULT '1' AFTER PageExpiration, + ADD INDEX (LiveRevisionNumber); + +ALTER TABLE PageContent + ADD RevisionId INT NOT NULL AFTER PageId, + ADD INDEX (RevisionId); + +ALTER TABLE PermissionConfig CHANGE PermissionName PermissionName VARCHAR(255) NOT NULL DEFAULT ''; + +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.ADD', 'la_PermName_Category.Revision.Add_desc', 'In-Portal', 1); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.ADD.PENDING', 'la_PermName_Category.Revision.Add.Pending_desc', 'In-Portal', 1); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.MODERATE', 'la_PermName_Category.Revision.Moderate_desc', 'In-Portal', 1); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.HISTORY.VIEW', 'la_PermName_Category.Revision.History.View_desc', 'In-Portal', 1); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.HISTORY.RESTORE', 'la_PermName_Category.Revision.History.Restore_desc', 'In-Portal', 1); + +INSERT INTO Permissions VALUES(DEFAULT, 'CATEGORY.REVISION.ADD', 11, 1, 0, 1); +INSERT INTO Permissions VALUES(DEFAULT, 'CATEGORY.REVISION.HISTORY.VIEW', 11, 1, 0, 1); +INSERT INTO Permissions VALUES(DEFAULT, 'CATEGORY.REVISION.HISTORY.RESTORE', 11, 1, 0, 1); \ No newline at end of file Index: branches/5.2.x/core/admin_templates/img/top_frame/revision_control/message_background_red.png =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/units/helpers/helpers_config.php =================================================================== diff -u -N -r14758 -r14856 --- branches/5.2.x/core/units/helpers/helpers_config.php (.../helpers_config.php) (revision 14758) +++ branches/5.2.x/core/units/helpers/helpers_config.php (.../helpers_config.php) (revision 14856) @@ -1,6 +1,6 @@ 'SiteHelper', 'class' => 'SiteHelper', 'file' => 'site_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), Array ('pseudo' => 'DeploymentHelper', 'class' => 'DeploymentHelper', 'file' => 'deployment_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), + Array ('pseudo' => 'PageHelper', 'class' => 'PageHelper', 'file' => 'page_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), Array ('pseudo' => 'BackupHelper', 'class' => 'BackupHelper', 'file' => 'backup_helper.php', 'build_event' => ''), Array ('pseudo' => 'AjaxFormHelper', 'class' => 'AjaxFormHelper', 'file' => 'ajax_form_helper.php', 'build_event' => '', 'require_classes' => 'kHelper'), ), Index: branches/5.2.x/core/units/page_revisions/page_revisions_config.php =================================================================== diff -u -N --- branches/5.2.x/core/units/page_revisions/page_revisions_config.php (revision 0) +++ branches/5.2.x/core/units/page_revisions/page_revisions_config.php (revision 14856) @@ -0,0 +1,93 @@ + 'page-revision', + 'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'), + 'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'), + 'EventHandlerClass' => Array ('class' => 'PageRevisionEventHandler', 'file' => 'page_revision_eh.php', 'build_event' => 'OnBuild'), + 'TagProcessorClass' => Array ('class' => 'PageRevisionTagProcessor', 'file' => 'page_revision_tp.php', 'build_event' => 'OnBuild'), + 'AutoLoad' => true, + 'QueryString' => Array ( + 1 => 'id', + 2 => 'Page', + 3 => 'PerPage', + 4 => 'event', + 5 => 'mode', // needed? + ), + + 'IDField' => 'RevisionId', + 'ParentTableKey' => 'CategoryId', // linked field in master table + 'ForeignKey' => 'PageId', // linked field in subtable + 'ParentPrefix' => 'c', + 'AutoDelete' => true, + 'AutoClone' => true, + + 'TitleField' => 'RevisionNumber', + + 'TableName' => TABLE_PREFIX . 'PageRevisions', + + 'ListSQLs' => Array ( + '' => ' SELECT %1$s.* %2$s + FROM %1$s + JOIN ' . TABLE_PREFIX . '%3$sCategory c ON c.CategoryId = %1$s.PageId + LEFT JOIN ' . TABLE_PREFIX . 'PortalUser created_by ON created_by.PortalUserId = %1$s.CreatedById' + ), + + 'SubItems' => Array ('content'), + + 'ListSortings' => Array ( + '' => Array ( + 'Sorting' => Array ('IsDraft' => 'desc', 'RevisionNumber' => 'desc'), + ) + ), + + 'CalculatedFields' => Array ( + '' => Array ( + 'CreatedBy' => 'created_by.Username', + 'IsLive' => 'IF(%1$s.RevisionNumber = c.LiveRevisionNumber, 1, 0)', + ), + ), + + 'Fields' => Array ( + 'RevisionId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'PageId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'RevisionNumber' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'IsDraft' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'not_null' => 1, 'default' => 0 + ), + 'FromRevisionId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'CreatedById' => Array ( + 'type' => 'int', + 'formatter' => 'kLEFTFormatter', 'options' => Array (USER_ROOT => 'root', USER_GUEST => 'Guest'), 'left_sql' => 'SELECT %s FROM ' . TABLE_PREFIX . 'PortalUser WHERE `%s` = \'%s\'', 'left_key_field' => 'PortalUserId', 'left_title_field' => 'Username', 'error_msgs' => Array ('invalid_option' => '!la_error_UserNotFound!'), 'sample_value' => 'Guest', 'required' => 1, + 'default' => NULL + ), + 'CreatedOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => '#NOW#'), + 'AutoSavedOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => NULL), + 'Status' => Array ('type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array (2 => 'la_Pending', 1 => 'la_opt_Published', 0 => 'la_opt_Declined'), 'use_phrases' => 1, 'not_null' => 1, 'default' => 2) + ), + + 'VirtualFields' => Array ( + 'CreatedBy' => Array ('type' => 'string', 'default' => ''), + 'IsLive' => Array ( + 'type' => 'int', + 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, + 'default' => 0, + ) + ), + ); Index: branches/5.2.x/core/units/categories/categories_config.php =================================================================== diff -u -N -r14726 -r14856 --- branches/5.2.x/core/units/categories/categories_config.php (.../categories_config.php) (revision 14726) +++ branches/5.2.x/core/units/categories/categories_config.php (.../categories_config.php) (revision 14856) @@ -1,6 +1,6 @@ 'SELECT %1$s.* %2$s FROM %1$s', ), - 'SubItems' => Array ('c-rel', 'c-search','c-img', 'c-cdata', 'c-perm', 'content'), + 'SubItems' => Array ('c-rel', 'c-search','c-img', 'c-cdata', 'c-perm', 'content', 'page-revision'), 'ListSortings' => Array ( '' => Array ( @@ -401,6 +401,7 @@ ), 'PageCacheKey' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), 'PageExpiration' => Array ('type' => 'int', 'default' => NULL), + 'LiveRevisionNumber' => Array ('type' => 'int', 'not_null' => 1, 'default' => 1), 'DirectLinkEnabled' => Array ( 'type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, Index: branches/5.2.x/core/units/helpers/permissions_helper.php =================================================================== diff -u -N -r14731 -r14856 --- branches/5.2.x/core/units/helpers/permissions_helper.php (.../permissions_helper.php) (revision 14731) +++ branches/5.2.x/core/units/helpers/permissions_helper.php (.../permissions_helper.php) (revision 14856) @@ -1,6 +1,6 @@ CheckPermission($permission, $is_system, $perm_category) && $owner_checked; + $permissions = explode(',', $permission_group); + + if ( $check_admin ) { + foreach ($permissions as $permission) { + $owner_checked = (strpos($permission, '.OWNER.') !== false) ? $is_owner : true; + $has_permission = $has_permission && $this->CheckAdminPermission($permission, $is_system, $perm_category) && $owner_checked; + } } + else { + foreach ($permissions as $permission) { + $owner_checked = (strpos($permission, '.OWNER.') !== false) ? $is_owner : true; + $has_permission = $has_permission && $this->CheckPermission($permission, $is_system, $perm_category) && $owner_checked; + } + } + $group_has_permission = $group_has_permission || $has_permission; if ($group_has_permission) { @@ -543,8 +554,28 @@ return $this->CheckUserPermission($user_id, $name, $type, $cat_id); } + /** + * Check current admin permissions (when called from Front-End) based on it's group permissions in specified category (for non-system permissions) or just checks if system permission is set + * + * @param string $name permission name + * @param int $cat_id category id, current used if not specified + * @param int $type permission type {1 - system, 0 - per category} + * @return int + */ + function CheckAdminPermission($name, $type = 1, $cat_id = null) + { + if ( $this->Application->isAdmin ) { + return $this->CheckPermission($name, $type, $cat_id); + } + + $user_id = $this->Application->RecallVar('admin_user_id'); + return $this->CheckUserPermission($user_id, $name, $type, $cat_id); + } + function CheckUserPermission($user_id, $name, $type = 1, $cat_id = null) { + $user_id = (int)$user_id; + if ( $user_id == USER_ROOT ) { // "root" is allowed anywhere return substr($name, -5) == '.deny' || $name == 'SYSTEM_ACCESS.READONLY' ? 0 : 1; @@ -565,18 +596,28 @@ // perm cache is build only based on records in db, that's why if permission is not explicitly denied, then // that (perm cache creator) code thinks that it is allowed & adds corresponding record and code below will // return incorrect results - if ( $user_id == $this->Application->RecallVar('user_id') ) { - $groups = explode(',', $this->Application->RecallVar('UserGroups')); + $groups = $this->Application->RecallVar('UserGroups'); } - else { // checking not current user - $sql = 'SELECT GroupId - FROM ' . TABLE_PREFIX . 'UserGroup - WHERE (PortalUserId = ' . $user_id . ') AND ( (MembershipExpires IS NULL) OR ( MembershipExpires >= UNIX_TIMESTAMP() ) )'; - $groups = $this->Conn->GetCol($sql); - array_push($groups, $this->Application->ConfigValue('User_LoggedInGroup')); + else { + // checking not current user + $groups = $this->Application->RecallVar('UserGroups:' . $user_id); + + if ( $groups === false ) { +// die('me'); + $sql = 'SELECT GroupId + FROM '.TABLE_PREFIX.'UserGroup + WHERE (PortalUserId = '.$user_id.') AND ( (MembershipExpires IS NULL) OR ( MembershipExpires >= UNIX_TIMESTAMP() ) )'; + $groups = $this->Conn->GetCol($sql); + + array_push($groups, $this->Application->ConfigValue('User_LoggedInGroup') ); + $groups = implode(',', $groups); + + $this->Application->StoreVar('UserGroups:' . $user_id, $groups); + } } + $groups = explode(',', $groups); $cache_key = $name . '|' . $type . '|' . $cat_id . '|' . implode(',', $groups); $perm_value = $this->Application->getCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key); Index: branches/5.2.x/core/units/helpers/page_helper.php =================================================================== diff -u -N --- branches/5.2.x/core/units/helpers/page_helper.php (revision 0) +++ branches/5.2.x/core/units/helpers/page_helper.php (revision 14856) @@ -0,0 +1,315 @@ +getHistoryPermissionAndUser($page_id); + + $where_clause = Array ( + 'pr.PageId = ' . $page_id, + 'pr.CreatedById <> ' . $user_id, + 'pr.IsDraft = 1', + ); + + $sql = 'SELECT CASE pr.CreatedById WHEN ' . USER_ROOT . ' THEN "root" WHEN ' . USER_GUEST . ' THEN "Guest" ELSE u.Username END + FROM ' . $this->Application->getUnitOption('page-revision', 'TableName') . ' pr + LEFT JOIN ' . TABLE_PREFIX . 'PortalUser u ON u.PortalUserId = pr.CreatedById + WHERE (' . implode(') AND (', $where_clause) . ')'; + $users = $this->Conn->GetCol($sql); + + $page_revisions = Array (); + + if ( $history_permission ) { + $tag_params = Array ('per_page' => -1, 'skip_parent_filter' => 1, 'requery' => 1, 'page_id' => $page_id); + + $revisions =& $this->Application->recallObject('page-revision.list', 'page-revision_List', $tag_params); + /* @var $revisions kDBList */ + + $revisions->Query(); + $revisions->GoFirst(); + + $status_options = $revisions->GetFieldOptions('Status'); + $draft_label = $this->Application->Phrase('la_Draft', false, true); + $title_label = $this->Application->Phrase('la_RevisionNumber', false, true); + $by_label = $this->Application->Phrase('la_By', false, true); + + while ( !$revisions->EOL() ) { + $status = $revisions->GetDBField('Status'); + $status_label = $this->Application->Phrase($status_options['options'][$status], false, true); + + $page_revisions[ 'r' . $revisions->GetDBField('RevisionNumber') ] = Array ( + 'title' => $revisions->GetDBField('IsDraft') ? $draft_label : sprintf($title_label, $revisions->GetDBField('RevisionNumber')), + 'status' => $status, + 'status_label' => mb_strtolower($status_label), + 'datetime' => $revisions->GetField('CreatedOn'), + 'author' => $by_label . ': ' . $revisions->GetField('CreatedById'), + 'draft' => (int)$revisions->GetDBField('IsDraft'), + ); + + $revisions->GoNext(); + } + } + + $current_revision =& $this->Application->recallObject('page-revision.current'); + /* @var $current_revision kDBItem */ + + $revision_status = $current_revision->GetDBField('Status'); + $status_options = $current_revision->GetFieldOptions('Status'); + $status_label = $this->Application->Phrase($status_options['options'][$revision_status], false, true); + + $revision_phase = $current_revision->GetDBField('IsDraft') ? 'la_title_EditingDraft' : 'la_title_ViewingRevision'; + $revision_title = sprintf($this->Application->Phrase($revision_phase, false, true), $current_revision->GetDBField('RevisionNumber'), mb_strtolower($status_label)); + $current_revision_info = Array ('title' => $revision_title, 'status' => $revision_status, 'saved' => ''); + + $autosave_time = $current_revision->GetDBField('AutoSavedOn'); + + if ( $autosave_time ) { + $phrase = $this->Application->Phrase($current_revision->GetDBField('IsDraft') ? 'la_DraftSavedAt' : 'la_SavedAt', false, true); + $current_revision_info['saved'] = sprintf($phrase, $current_revision->GetField('AutoSavedOn_time') . ' (' . $this->getAgoTime($autosave_time) . ')'); + } + + $currently_editing = $this->getPluralPhrase( + count($users), + Array ( + 'phrase1' => 'la_PageCurrentlyEditing1', + 'phrase2' => 'la_PageCurrentlyEditing2', + 'phrase5' => 'la_PageCurrentlyEditing5', + ), + false, true + ); + + $currently_editing = sprintf($currently_editing, implode(', ', $users)); + + return Array ('current_revision' => $current_revision_info, 'editors' => $users, 'editors_warning' => $currently_editing, 'revisions' => $page_revisions); + } + + /** + * Returns time passed between 2 given dates in "X minutes Y seconds ago" format + * + * @param int $from_date + * @param int $to_date + * @return string + */ + function getAgoTime($from_date, $to_date = null, $max_levels = 1) + { + $blocks = Array ( + Array ('name' => 'year', 'amount' => 60*60*24*365), + Array ('name' => 'month' ,'amount' => 60*60*24*31), + Array ('name' => 'week', 'amount' => 60*60*24*7), + Array ('name' => 'day', 'amount' => 60*60*24), + Array ('name' => 'hour', 'amount' => 60*60), + Array ('name' => 'minute', 'amount' => 60), + Array ('name' => 'second', 'amount' => 1), + ); + + if ( !isset($to_date) ) { + $to_date = adodb_mktime(); + } + + $diff = abs($to_date - $from_date); + + if ( $diff == 0 ) { + return 'now'; + } + + $current_level = 1; + $result = Array (); + + foreach ($blocks as $block) { + if ($current_level > $max_levels) { + break; + } + + if ( $diff / $block['amount'] >= 1 ) { + $amount = floor($diff / $block['amount']); + $plural = $amount > 1 ? 's' : ''; + + $result[] = $amount . ' ' . $block['name'] . $plural; + $diff -= $amount * $block['amount']; + $current_level++; + } + } + + return implode(' ', $result) . ' ago'; + } + + /** + * Returns where clause for loading correct revision for a given page + * + * @param int $page_id + * @param int $live_revision_number + * @param string $table_name + * @return string + */ + function getRevsionWhereClause($page_id, $live_revision_number, $table_name = '') + { + $revision = (int)$this->Application->GetVar('revision'); + list ($user_id, $has_permission) = $this->getHistoryPermissionAndUser($page_id); + + if ( $has_permission && $revision ) { + $revision_clause = $table_name . 'RevisionNumber = ' . $revision . ' AND ' . $table_name . 'IsDraft = 0'; + } + else { + $editing_mode = $this->Application->GetVar('editing_mode'); // not in a EDITING_MODE constant, while in admin console + $revision_clause = $table_name . 'RevisionNumber = ' . $live_revision_number . ' AND ' . $table_name . 'IsDraft = 0'; + + if ( $this->Application->GetVar('preview') || $editing_mode == EDITING_MODE_CONTENT ) { + $revision_clause = '(' . $table_name . 'CreatedById = ' . $user_id . ' AND ' . $table_name . 'IsDraft = 1) OR (' . $revision_clause . ')'; + } + } + + return $revision_clause; + } + + /** + * Returns current admin user id (even, when called from front-end) and it's revision history view permission + * + * @param int $page_id + * @return Array + */ + function getHistoryPermissionAndUser($page_id) + { + $user_id = (int)$this->Application->RecallVar($this->Application->isAdmin ? 'user_id' : 'admin_user_id'); + $history_permission = $this->Application->CheckAdminPermission('CATEGORY.REVISION.HISTORY.VIEW', 0, $page_id); + + return Array ($user_id, $history_permission); + } + + /** + * Creates new content block in every revision that misses it. Plus creates first page revision + * + * @param int $page_id + * @param int $num + */ + function createNewContentBlock($page_id, $num) + { + $sql = 'SELECT pc.PageContentId, pr.RevisionId + FROM ' . TABLE_PREFIX . 'PageRevisions pr + LEFT JOIN ' . TABLE_PREFIX . 'PageContent pc ON pc.RevisionId = pr.RevisionId AND pc.ContentNum = ' . $num . ' + WHERE pr.PageId = ' . $page_id; + $revisions = $this->Conn->GetCol($sql, 'RevisionId'); + + if ( !$revisions ) { + // no revisions for a page -> create a live revision + $revision =& $this->Application->recallObject('page-revision.live', null, Array ('skip_autoload' => true)); + /* @var $revision kDBItem */ + + $revision->SetDBField('PageId', $page_id); + $revision->SetDBField('RevisionNumber', 1); + $revision->SetDBField('Status', STATUS_ACTIVE); + $revision->Create(); + + $revisions[ $revision->GetID() ] = NULL; + } + + $content_block =& $this->Application->recallObject('content.new', null, Array ('skip_autoload' => true)); + /* @var $content_block kDBItem */ + + $content_block->SetDBField('PageId', $page_id); + $content_block->SetDBField('ContentNum', $num); + + foreach ($revisions as $revision_id => $content_block_id) { + if ( is_numeric($content_block_id) ) { + continue; + } + + $content_block->SetDBField('RevisionId', $revision_id); + $content_block->Create(); + } + } + + /** + * Loads content block by it's number + * + * @param kDBItem $content_block + * @param CategoriesItem $page + * @param int $num + * + * @return bool + */ + function loadContentBlock(&$content_block, &$page, $num) + { + $page_id = $page->GetID(); + + if ( !EDITING_MODE && !$this->Application->GetVar('preview') ) { + $revision_clause = 'pr.RevisionNumber = ' . $page->GetDBField('LiveRevisionNumber') . ' AND pr.IsDraft = 0'; + } + else { + $revision_clause = $this->getRevsionWhereClause($page_id, $page->GetDBField('LiveRevisionNumber'), 'pr.'); + } + + + $sql = $content_block->GetSelectSQL() . ' + WHERE (' . $content_block->TableName . '.PageId = ' . $page_id . ') AND (' . $content_block->TableName . '.ContentNum = ' . $num . ') AND (' . $revision_clause . ') + ORDER BY pr.IsDraft DESC, pr.RevisionNumber DESC'; + $content_data = $this->Conn->GetRow($sql); + + $content_block->LoadFromHash($content_data); + + return $content_block->isLoaded(); + } + + /** + * Returns phrase based on given number + * + * @param int $number + * @param Array $forms + * @return string + */ + function getPluralPhrase($number, $forms, $allow_editing = true, $use_admin = false) + { + // normalize given forms + if ( !array_key_exists('phrase5', $forms) ) { + $forms['phrase5'] = $forms['phrase2']; + } + + $phrase_type = $this->getPluralPhraseType($number); + + return $this->Application->Phrase( $forms['phrase' . $phrase_type], $allow_editing, $use_admin ); + } + + /** + * Returns phrase type based on given number + * + * @param int $number + * @return int + */ + function getPluralPhraseType($number) + { + $last_digit = substr($number, -1); + $last_but_one_digit = strlen($number) > 1 ? substr($number, -2, 1) : false; + $phrase_type = '5'; + + if ($last_but_one_digit != 1) { + if ($last_digit == 1) { + $phrase_type = '1'; + } + elseif ($last_digit >= 2 && $last_digit <= 4) { + $phrase_type = '2'; + } + } + + return $phrase_type; + } +} Index: branches/5.2.x/core/admin_templates/img/top_frame/revision_control/close_white.gif =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/units/helpers/minifiers/minify_helper.php =================================================================== diff -u -N -r14837 -r14856 --- branches/5.2.x/core/units/helpers/minifiers/minify_helper.php (.../minify_helper.php) (revision 14837) +++ branches/5.2.x/core/units/helpers/minifiers/minify_helper.php (.../minify_helper.php) (revision 14856) @@ -1,6 +1,6 @@ Application->ProcessParsedTag('m', 'TemplatesBase', Array ()); + if ( isset($params['templates_base']) ) { + $templates_base = $params['templates_base']; + } + else { + $templates_base = $this->Application->ProcessParsedTag('m', 'TemplatesBase', Array ()); + } + $templates_base = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', BASE_PATH . '/', $templates_base); $string = str_replace('@templates_base@', rtrim($templates_base, '/'), $string); Index: branches/5.2.x/core/units/page_revisions/page_revision_tp.php =================================================================== diff -u -N --- branches/5.2.x/core/units/page_revisions/page_revision_tp.php (revision 0) +++ branches/5.2.x/core/units/page_revisions/page_revision_tp.php (revision 14856) @@ -0,0 +1,29 @@ +getObject($params); + /* @var $object kDBItem */ + + $page_helper =& $this->Application->recallObject('PageHelper'); + /* @var $page_helper PageHelper */ + + return $page_helper->getAgoTime( $object->GetDBField('AutoSavedOn') ); + } +} Index: branches/5.2.x/core/admin_templates/img/top_frame/revision_control/button_vp_right.png =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/kernel/constants.php =================================================================== diff -u -N -r14853 -r14856 --- branches/5.2.x/core/kernel/constants.php (.../constants.php) (revision 14853) +++ branches/5.2.x/core/kernel/constants.php (.../constants.php) (revision 14856) @@ -1,6 +1,6 @@ GetVar('admin') ) { - // viewing front-end through admin's frame $admin_session =& $this->recallObject('Session.admin'); /* @var $admin_session Session */ - $user = (int)$admin_session->RecallVar('user_id'); // in case, when no valid admin session found + // store Admin Console User's ID to Front-End's session for cross-session permission checks + $this->StoreVar('admin_user_id', (int)$admin_session->RecallVar('user_id')); - $perm_helper =& $this->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ - - if ( $perm_helper->CheckUserPermission($user, 'CATEGORY.MODIFY', 0, $this->getBaseCategory()) ) { - // user can edit cms blocks + if ( $this->CheckAdminPermission('CATEGORY.MODIFY', 0, $this->getBaseCategory()) ) { + // user can edit cms blocks (when viewing front-end through admin's frame) $editing_mode = $this->GetVar('editing_mode'); define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE); } @@ -2411,6 +2408,22 @@ return $perm_helper->CheckPermission($name, $type, $cat_id); } + /** + * Check current admin permissions based on it's group permissions in specified category + * + * @param string $name permission name + * @param int $cat_id category id, current used if not specified + * @param int $type permission type {1 - system, 0 - per category} + * @return int + */ + function CheckAdminPermission($name, $type = 1, $cat_id = null) + { + $perm_helper =& $this->recallObject('PermissionsHelper'); + /* @var $perm_helper kPermissionsHelper */ + + return $perm_helper->CheckAdminPermission($name, $type, $cat_id); + } + /** * Set's any field of current visit * Index: branches/5.2.x/core/units/content/content_config.php =================================================================== diff -u -N -r14585 -r14856 --- branches/5.2.x/core/units/content/content_config.php (.../content_config.php) (revision 14585) +++ branches/5.2.x/core/units/content/content_config.php (.../content_config.php) (revision 14856) @@ -1,6 +1,6 @@ 'PageContentId', - 'ParentTableKey' => 'CategoryId', // linked field in master table - 'ForeignKey' => 'PageId', // linked field in subtable + 'ParentTableKey' => Array ('c' => 'CategoryId', 'st' => 'CategoryId', 'page-revision' => 'RevisionId'), // linked field in master table + 'ForeignKey' => Array ('c' => 'PageId', 'st' => 'PageId', 'page-revision' => 'RevisionId'), // linked field in subtable 'ParentPrefix' => 'c', 'AutoDelete' => true, 'AutoClone' => true, @@ -48,17 +48,22 @@ 'TableName' => TABLE_PREFIX . 'PageContent', - 'ListSQLs' => Array ('' => 'SELECT * FROM %s'), + 'ListSQLs' => Array ( + '' => ' SELECT %1$s.* %2$s + FROM %1$s + JOIN ' . TABLE_PREFIX . '%3$sPageRevisions pr ON pr.PageId = %1$s.PageId AND pr.RevisionId = %1$s.RevisionId' + ), 'ListSortings' => Array ( '' => Array ( - 'Sorting' => Array ('ContentNum' => 'asc'), + 'Sorting' => Array ('ContentNum' => 'asc', 'RevisionNumber' => 'desc'), ) ), 'Fields' => Array ( 'PageContentId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), 'ContentNum' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), 'PageId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), + 'RevisionId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), 'Content' => Array ( 'type' => 'string', 'min_len' => 0, 'max_len' => 65536, 'formatter' => 'kMultiLanguage', 'using_fck' => 1, Index: branches/5.2.x/core/units/categories/categories_tag_processor.php =================================================================== diff -u -N -r14748 -r14856 --- branches/5.2.x/core/units/categories/categories_tag_processor.php (.../categories_tag_processor.php) (revision 14748) +++ branches/5.2.x/core/units/categories/categories_tag_processor.php (.../categories_tag_processor.php) (revision 14856) @@ -1,6 +1,6 @@ _getPage($params); /* @var $page kDBItem */ - if (!$page->isLoaded()) { + if ( !$page->isLoaded() ) { // page is not created yet => all blocks are empty return ''; } - $page_id = $page->GetID(); + $page_helper =& $this->Application->recallObject('PageHelper'); + /* @var $page_helper PageHelper */ $content =& $this->Application->recallObject('content.-block', null, Array ('skip_autoload' => true)); /* @var $content kDBItem */ - $data = Array ('PageId' => $page_id, 'ContentNum' => $num); - $content->Load($data); - - if (!$content->isLoaded()) { - // bug: missing content blocks are created even if user have no SMS-management rights - $content->SetFieldsFromHash($data); - $content->Create(); + if ( !$page_helper->loadContentBlock($content, $page, $num) && EDITING_MODE ) { + $page_helper->createNewContentBlock($page->GetID(), $num); + $page_helper->loadContentBlock($content, $page, $num); } $edit_code_before = $edit_code_after = ''; @@ -1364,7 +1361,7 @@ $js_url . '/../incs/cms.css', ); - $css_compressed = $minify_helper->CompressScriptTag(Array ('files' => implode('|', $to_compress))); + $css_compressed = $minify_helper->CompressScriptTag(Array ('files' => implode('|', $to_compress), 'templates_base' => $js_url . '/../')); $ret = '' . "\n"; @@ -1381,6 +1378,7 @@ $js_url . '/is.js', $js_url . '/application.js', $js_url . '/script.js', + $js_url . '/toolbar.js', $js_url . '/jquery/thickbox/thickbox.js', $js_url . '/template_manager.js', ); @@ -1400,15 +1398,40 @@ $url_params = Array ('theme-file_event' => 'OnSaveLayout', 'source' => $template, 'pass' => 'all,theme-file', '__NO_REWRITE__' => 1, 'no_amp' => 1); $save_layout_url = $this->Application->HREF('index', '', $url_params); - $this_url = $this->Application->HREF('', '', Array ('editing_mode' => '#EDITING_MODE#', '__NO_REWRITE__' => 1, 'no_amp' => 1)); - $ret .= "var aTemplateManager = new TemplateManager('" . $edit_template_url . "', '" . $this_url . "', '" . $save_layout_url . "', " . (int)EDITING_MODE . ");\n"; + $page =& $this->_getPage($params); + + $url_params = Array( + 'pass' => 'm,c', + 'c_id' => $page->GetID(), + 'c_event' => 'OnGetPageInfo', + '__URLENCODE__' => 1, + '__NO_REWRITE__'=> 1, + 'index_file' => 'index.php', + ); + + $page_helper =& $this->Application->recallObject('PageHelper'); + /* @var $page_helper PageHelper */ + + $class_params = Array ( + 'pageId' => $page->GetID(), + 'pageInfo' => $page_helper->getPageInfo( $page->GetID() ), + 'editUrl' => $edit_template_url, + 'browseUrl' => $this->Application->HREF('', '', Array ('editing_mode' => '#EDITING_MODE#', '__NO_REWRITE__' => 1, 'no_amp' => 1)), + 'saveLayoutUrl' => $save_layout_url, + 'editingMode' => (int)EDITING_MODE, + ); + + $ret .= "var aTemplateManager = new TemplateManager(" . json_encode($class_params) . ");\n"; $ret .= "var main_title = '" . addslashes( $this->Application->ConfigValue('Site_Name') ) . "';" . "\n"; $use_popups = (int)$this->Application->ConfigValue('UsePopups'); $ret .= "var \$use_popups = " . ($use_popups > 0 ? 'true' : 'false') . ";\n"; $ret .= "var \$modal_windows = " . ($use_popups == 2 ? 'true' : 'false') . ";\n"; if ( EDITING_MODE != EDITING_MODE_BROWSE ) { + $ret .= 'var $visible_toolbar_buttons = true' . ";\n"; + $ret .= 'var $use_toolbarlabels = ' . ($this->Application->ConfigValue('UseToolbarLabels') ? 'true' : 'false') . ";\n";; + $ret .= "var base_url = '" . $this->Application->BaseURL() . "';" . "\n"; $ret .= 'TB.closeHtml = \'close
\';' . "\n"; @@ -1451,6 +1474,11 @@ */ function EditPage($params) { + if ( $this->Application->GetVar('preview') ) { + // prevents draft preview function to replace last template in session and break page/content block editing process + $this->Application->SetVar('skip_last_template', 1); + } + if (!EDITING_MODE) { return ''; } @@ -1533,6 +1561,77 @@ if ($display_mode == 'start') { // button with border around the page + if ( EDITING_MODE == EDITING_MODE_CONTENT ) { + $tabs = "\n" . str_repeat("\t", 9); + $base_url = $this->Application->BaseURL(); + $toolbar_hidden = $this->Application->GetVar('toolbar_hidden'); + + $edit_code .= ' +
+
+
+ + +
+
+
+ +
+
+
+
+
+ +
+
+ + +
+ +
+
+ + +
+
+ + ' . $publishing_tools . ' +
' . "\n"; + } + $edit_code .= '
' . $edit_btn . '
'; } @@ -1548,6 +1647,29 @@ } if ($display_mode != 'end') { + if ( EDITING_MODE == EDITING_MODE_CONTENT ) { + $url_params = Array( + 'pass' => 'm', + 'm_opener' => 'd', + 'm_cat_id' => $page->GetID(), + '__URLENCODE__' => 1, + '__NO_REWRITE__'=> 1, + 'front' => 1, + 'index_file' => 'index.php', + ); + + $revision = $this->Application->GetVar('revision'); + + if ( $revision ) { + $url_params['revision'] = $revision; + } + + $page_admin_url = $this->Application->HREF('', ADMIN_DIRECTORY, $url_params); + $edit_code .= '
+ +
'; + } + $edit_code .= '
'; // when "EditingScripts" tag is not used, make sure, that scripts are also included @@ -1557,6 +1679,13 @@ return $edit_code; } + function toolbarButton($name, $title, $tabs) + { + $phrase = $this->Application->Phrase($title, false, true); + + return $tabs . 'a_toolbar.AddButton( new ToolBarButton("' . $name . '", "' . htmlspecialchars($phrase) . '") );'; + } + function _getThemeFileId() { $template = $this->Application->GetVar('t'); Index: branches/5.2.x/core/admin_templates/img/top_frame/revision_control/history_item_background_hover.gif =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/admin_templates/js/script.js =================================================================== diff -u -N -r14705 -r14856 --- branches/5.2.x/core/admin_templates/js/script.js (.../script.js) (revision 14705) +++ branches/5.2.x/core/admin_templates/js/script.js (.../script.js) (revision 14856) @@ -130,6 +130,29 @@ set_hidden_field('remove_specials[' + prefix_special + ']', null); } +function submit_event_ajax(prefix_special, event, t, $callback) { + if ( !Application.processHooks(prefix_special + ':' + event) ) { + return false; + } + + if (event) { + set_hidden_field('events[' + prefix_special + ']', event); + } + + if (t) { + set_hidden_field('t', t); + } + + var $form = $('#kernel_form'), + $from_params = $form.serialize(); + + $.post($form.attr('action'), $from_params, $callback); + + // reset remove special mark (otherwise all future events will have special removed too) + set_hidden_field('events[' + prefix_special + ']', ''); + set_hidden_field('remove_specials[' + prefix_special + ']', null); +} + function submit_action($url, $action) { $form = document.getElementById($form_name); Index: branches/5.2.x/core/admin_templates/img/top_frame/revision_control/history_item_background.gif =================================================================== diff -u -N Binary files differ Index: branches/5.2.x/core/kernel/db/db_event_handler.php =================================================================== diff -u -N -r14855 -r14856 --- branches/5.2.x/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 14855) +++ branches/5.2.x/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 14856) @@ -1,6 +1,6 @@ reset(); - $object->linkToParent( $this->getMainSpecial($event) ); + if ( $event->getEventParam('skip_parent_filter') === false ) { + $object->linkToParent( $this->getMainSpecial($event) ); + } $this->AddFilters($event); $this->SetCustomQuery($event); // new!, use this for dynamic queries based on specials for ex. Index: branches/5.2.x/core/admin_templates/js/template_manager.js =================================================================== diff -u -N -r14244 -r14856 --- branches/5.2.x/core/admin_templates/js/template_manager.js (.../template_manager.js) (revision 14244) +++ branches/5.2.x/core/admin_templates/js/template_manager.js (.../template_manager.js) (revision 14856) @@ -1,13 +1,18 @@ -function TemplateManager ($edit_url, $browse_url, $save_layout_url, $edting_mode) { - this._editUrl = $edit_url; - this.browseUrl = $browse_url; - this._saveLayoutUrl = $save_layout_url; - this.editingMode = $edting_mode; // from {1 - browse, 2 - content, 3 - design} +function TemplateManager ( $settings ) { + this.pageId = 0; + this.editUrl = ''; + this.browseUrl = ''; + this.saveLayoutUrl = ''; + this.editingMode = 0; // from {1 - browse, 2 - content, 3 - design} + this.pageInfo = {editors: [], revisions: {}}; // information about page in "Content Mode" + this._blocks = {}; this._blockOrder = Array (); this.inDrag = false; // don't process mouse over/out events while in drag mode + $.extend(this, $settings); + var $template_manager = this; $(document).ready( @@ -51,6 +56,20 @@ // make all spans with phrases clickable $template_manager.setupEditTranslationButtons(document); + + // hide "Revision History" div on every body click (bubbled), but not a "toolbar button", that opens it + $('body').click( + function ($e) { + var $target = $($e.target), + $id = $target.attr('id'); + + if ( $id && ($id == 'tool_history' || $id == 'div_history') ) { + return ; + } + + $('#cms-revision-dropdown:visible').hide(); + } + ); } if ($template_manager.editingMode == 3) { @@ -96,10 +115,196 @@ $(this).css('opacity', 0.5); } ); + + // related to content revision control toolbar + $('#cms-toggle-revision-toolbar').click( + function ($e) { + var $me = $(this); + + if ( $me.hasClass('opened') ) { + var $height = $('#cms-revision-toolbar').height(); + + $('#cms-revision-toolbar-layer').animate({top: (-1) * $height}, 'fast'); + $('#cms-editing-notice, #cms-revision-dropdown').hide(); + setCookie('toolbar_hidden', 1); + } + else { + $('#cms-revision-toolbar-layer').animate({top: 0}, 'fast'); + setCookie('toolbar_hidden', 0); + } + + $me.toggleClass('opened'); + + return false; + } + ); + + $('#cms-close-toolbar').click( + function () { + var $height = $('#cms-revision-toolbar').height(); + + $('#cms-toggle-revision-toolbar').removeClass('opened'); + $('#cms-revision-toolbar-layer').css('top', (-1) * $height); + + $('#cms-editing-notice, #cms-revision-dropdown').hide(); + setCookie('toolbar_hidden', 1); + + return false; + } + ); + + $('#cms-close-editing-notice').click( + function () { + $('#cms-editing-notice').hide(); + + return false; + } + ); + + $('.toolbar-button', '#cms-revision-toolbar').click( + function ($e) { + var $button_name = $(this).attr('id').replace(/^(tool|div)_/, ''); + + $template_manager.revisionToolbarClick($button_name); + } + ); + + setInterval( + function () { + $.getJSON( + $('#kf_revisions_' + $template_manager.pageId).attr('action') + '&events[page-revision]=OnGetInfo', + function ($data) { + $template_manager.pageInfo = $data; + $template_manager.processPageInfo(); + } + ); + }, 20 * 1000 // 20 seconds + ); + + if ( !$.isEmptyObject($template_manager.pageInfo) ) { + $template_manager.processPageInfo(); + } } ); } +TemplateManager.prototype.processPageInfo = function () { + var $class_mapping = { + 1: 'cms-revision-published', + 2: 'cms-revision-pending', + 0: 'cms-revision-declined' + }; + + var $title = $('.revision-title', '#cms-current-revision-info'); + + $title.html( this.pageInfo.current_revision.title ); + $('.draft-saved', '#cms-current-revision-info').html( this.pageInfo.current_revision.saved ); + + for (var $status in $class_mapping) { + $title.toggleClass( $class_mapping[$status], $status === this.pageInfo.current_revision.status ); + } + + if ( $('#cms-toggle-revision-toolbar').hasClass('opened') ) { + var $notice = $('#cms-editing-notice'); + + if ( this.pageInfo.editors.length ) { + if ( $('span:first', $notice).attr('prev_editors') != this.pageInfo.editors.join(',') ) { + // show notice, only when editors change occurs + $('span:first', $notice).html(this.pageInfo.editors_warning).attr('prev_editors', this.pageInfo.editors.join(',')); + + if ( $notice.is(':hidden') ) { + $notice.fadeIn(); + } + } + } + else if ( $notice.is(':visible') ) { + $notice.fadeOut(); + } + } + + var $revision_container = $('.top', '#cms-revision-dropdown'), + $revision_mask = '
\ + {TITLE} ({STATUS_LABEL})\ +
{DATETIME}
\ +
{AUTHOR}
\ +
\ +
'; + + $revision_container.empty(); + + if ( $.isArray(this.pageInfo.revisions) ) { + // no revisions yet + } + else { + for (var $revision in this.pageInfo.revisions) { + var $html = $revision_mask, + $revision_info = this.pageInfo.revisions[$revision]; + + for (var $field in $revision_info) { + $html = $html.replace( new RegExp('{' + $field.toUpperCase() + '}', 'g'), $revision_info[$field] ); + } + + $html = $html.replace(/{CLASS}/g, $class_mapping[$revision_info.status] ); + + if ( $revision_info['draft'] ) { + $html = $html.replace(/{LINK}/g, this.browseUrl.replace('#EDITING_MODE#', 2) ); + } + else { + $html = $html.replace(/{LINK}/g, this.browseUrl.replace('#EDITING_MODE#', 2) + '&revision=' + $revision.substr(1) ); + } + + $revision_container.append($html); + } + + $('.item', '#cms-revision-dropdown .top').each( + function () { + var $row = $(this); + + $('a:first', $row).click( + function ($e) { + $e.stopPropagation(); + } + ); + + $row.click( + function ($e) { + window.location.href = $('a:first', this).attr('href'); + } + ); + } + ); + } +} + +TemplateManager.prototype.revisionToolbarClick = function ($button_name) { +// console.log('button ', $button_name, ' clicked'); + + var $button_event_map = { + 'select': 'OnSave', + 'delete': 'OnDiscard', + 'approve': 'OnPublish', + 'decline': 'OnDecline' + }; + + if ( $button_event_map[$button_name] !== undefined ) { + $form_name = 'kf_revisions_' + this.pageId; + submit_event('page-revision', $button_event_map[$button_name]); + + return ; + } + + switch ( $button_name ) { + case 'preview': + var $url = this.browseUrl.replace('#EDITING_MODE#', 0).replace(/&(admin|editing_mode)=[\d]/g, ''); + window.open( $url + '&preview=1' ); + break; + + case 'history': + $('#cms-revision-dropdown').toggle(); + break; + } +} + TemplateManager.prototype.setupEditTranslationButtons = function ($container) { $("span[name='cms-translate-phrase']", $container).each( function() { @@ -198,7 +403,7 @@ var $me = this; var $settings = { - url: this._saveLayoutUrl + '&' + $sort_order + '&width=200&height=70&modal=true', + url: this.saveLayoutUrl + '&' + $sort_order + '&width=200&height=70&modal=true', caption: 'Layout Saving Result', onDataReceived: function ($data) { var $message = ''; @@ -229,7 +434,7 @@ TemplateManager.prototype.onBtnClick = function ($e, $element) { var $id = $element.id.replace(/_btn$/, ''); var $block_info = this._blocks[$id]; - var $url = this._editUrl.replace('#BLOCK#', $block_info.block_name + ':' + $block_info.function_name).replace('#EVENT#', 'OnLoadBlock'); + var $url = this.editUrl.replace('#BLOCK#', $block_info.block_name + ':' + $block_info.function_name).replace('#EVENT#', 'OnLoadBlock'); direct_edit('theme-file', $url); Index: branches/5.2.x/core/admin_templates/img/toolbar/tool_history_f2.gif =================================================================== diff -u -N Binary files differ