Index: admin_templates/categories/edit_content.tpl =================================================================== --- admin_templates/categories/edit_content.tpl (revision 14476) +++ admin_templates/categories/edit_content.tpl (working copy) @@ -16,7 +16,7 @@ 'select', '', function() { - submit_event('content',''); + submit_event('content', 'OnSaveContentBlock'); } ) ); @@ -24,23 +24,13 @@ 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(); @@ -71,4 +61,35 @@ + + \ No newline at end of file Index: admin_templates/img/toolbar/tool_history.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\toolbar\tool_history.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/toolbar/tool_history_f2.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\toolbar\tool_history_f2.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/toolbar/tool_history_f3.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\toolbar\tool_history_f3.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/toolbar/tool_preview.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\toolbar\tool_preview.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/toolbar/tool_preview_f2.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\toolbar\tool_preview_f2.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/toolbar/tool_preview_f3.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\toolbar\tool_preview_f3.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control ___________________________________________________________________ Added: tsvn:autoprops + *.php = svn:eol-style=LF;svn:keywords=Id *.tpl = svn:eol-style=LF *.sql = svn:eol-style=LF *.lang = svn:eol-style=LF *.sh = svn:eol-style=LF;svn:executable *.txt = svn:eol-style=LF *.html = svn:eol-style=LF *.htm = svn:eol-style=LF *.css = svn:eol-style=LF *.js = svn:eol-style=LF *.xml = svn:eol-style=LF .htaccess = svn:eol-style=LF .smsignore = svn:eol-style=LF COPYRIGHT = svn:eol-style=LF CREDITS = svn:eol-style=LF INSTALL = svn:eol-style=LF LICENSE = svn:eol-style=LF LICENSES = svn:eol-style=LF README = svn:eol-style=LF Added: bugtraq:url + http://tracker.in-portal.org/view.php?id=%BUGID% Added: bugtraq:logregex + (?:[Bb]ugs?|[Ii]ssues?|[Rr]eports?|[Ff]ixe?s?|[Rr]esolves?)+\s+(?:#?(?:\d+)[,\.\s]*)+ (\d+) Index: admin_templates/img/top_frame/revision_control/button_vp_left.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control\button_vp_left.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/top_frame/revision_control/button_vp_right.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control\button_vp_right.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/top_frame/revision_control/button_vp_right2.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control\button_vp_right2.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/top_frame/revision_control/close_black.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control\close_black.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/top_frame/revision_control/close_white.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control\close_white.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/top_frame/revision_control/history_bottom.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control\history_bottom.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/top_frame/revision_control/history_item_background.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control\history_item_background.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/top_frame/revision_control/history_item_background_hover.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control\history_item_background_hover.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/img/top_frame/revision_control/message_background_red.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: admin_templates\img\top_frame\revision_control\message_background_red.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: admin_templates/incs/cms.css =================================================================== --- admin_templates/incs/cms.css (revision 14476) +++ admin_templates/incs/cms.css (working copy) @@ -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: admin_templates/js/script.js =================================================================== --- admin_templates/js/script.js (revision 14476) +++ admin_templates/js/script.js (working copy) @@ -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: admin_templates/js/template_manager.js =================================================================== --- admin_templates/js/template_manager.js (revision 14476) +++ admin_templates/js/template_manager.js (working copy) @@ -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: admin_templates/js/toolbar.js =================================================================== --- admin_templates/js/toolbar.js (revision 14476) +++ admin_templates/js/toolbar.js (working copy) @@ -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: install/english.lang =================================================================== --- install/english.lang (revision 14487) +++ install/english.lang (working copy) @@ -35,6 +35,7 @@ TG9jYXRl TW92ZSBEb3du TW92ZSBVcA== + UHVibGlzaGluZyBUb29scw== UmVidWlsZA== UmVjb21waWxl UmVmcmVzaA== @@ -51,6 +52,7 @@ VW5zZWxlY3Q= VXA= VXNl + Ynk= Q2FuY2Vs U2VjdGlvbg== TnVtYmVyIG9mIGRheXMgZm9yIGEgY2F0LiB0byBiZSBORVc= @@ -318,7 +320,9 @@ RG93bmxvYWQgQ1NW RG93bmxvYWQgRXhwb3J0IEZpbGU= RG93bmxvYWQgTGFuZ3VhZ2UgRXhwb3J0 + RHJhZnQ= RHJhZnQgQXZhaWxhYmxl + ZHJhZnQgc2F2ZWQgYXQgJXM= Q29udGVudCBFZGl0b3I= WW91IGhhdmUgbm90IHNhdmVkIGNoYW5nZXMgdG8gdGhlIGl0ZW0geW91IGFyZSBlZGl0aW5nITxiciAvPkNsaWNrIE9LIHRvIGxvb3NlIGNoYW5nZXMgYW5kIGdvIHRvIHRoZSBzZWxlY3RlZCBzZWN0aW9uPGJyIC8+b3IgQ2FuY2VsIHRvIHN0YXkgaW4gdGhlIGN1cnJlbnQgc2VjdGlvbi4= RGVmYXVsdCB0ZXh0 @@ -805,6 +809,7 @@ Q3VzdG9tICJUbyIgUmVjaXBpZW50KC1zKQ== Q3VzdG9tIFNlbmRlcg== ZGF5KHMp + RGVjbGluZWQ= RGVmYXVsdCBXZWJzaXRlIGFkZHJlc3M= RGVueQ== RGVzY3JpcHRpb24= @@ -842,6 +847,7 @@ UGhvbmU= UG9wdXAgV2luZG93 UHJvY2Vzc2Vk + UHVibGlzaGVk UmF0aW5n UmVjaXBpZW50IEUtbWFpbA== UmVjaXBpZW50IE5hbWU= @@ -885,6 +891,9 @@ KEdNVCArMDk6MDAp UGFkZGluZ3M= UGFnZQ== + QXR0ZW50aW9uOiAlcyBpcyBjdXJyZW50bHkgZWRpdGluZyB0aGlzIHNlY3Rpb24h + QXR0ZW50aW9uOiAlcyBhcmUgY3VycmVudGx5IGVkaXRpbmcgdGhpcyBzZWN0aW9uISA= + QXR0ZW50aW9uOiAlcyBhcmUgY3VycmVudGx5IGVkaXRpbmcgdGhpcyBzZWN0aW9uIQ== UGFzc3dvcmRzIGRvIG5vdCBtYXRjaA== UGFzc3dvcmQgaXMgdG9vIHNob3J0LCBwbGVhc2UgZW50ZXIgYXQgbGVhc3QgJXMgY2hhcmFjdGVycw== UGVuZGluZw== @@ -1046,8 +1055,10 @@ Tm90IGFsbCByZXF1aXJlZCBmaWVsZHMgYXJlIGZpbGxlZC4gUGxlYXNlIGZpbGwgdGhlbSBmaXJzdC4= Q29tbWVudHMgcGVyIFBhZ2U= Q29tbWVudHMgcGVyIFBhZ2UgKHNob3J0LWxpc3Qp + UmV2aXNpb24gIyVz SG9tZQ== U2FtcGxlIFRleHQ= + c2F2ZWQgYXQgJXM= U2F2ZSBVc2VybmFtZSBvbiBUaGlzIENvbXB1dGVy U2VhcmNo QmFzaWMgUGVybWlzc2lvbnM= @@ -1303,7 +1314,9 @@ RWRpdGluZyBBZ2VudA== RWRpdGluZyBCYW4gUnVsZQ== RWRpdGluZyBDaGFuZ2VzIExvZw== + Q29udGVudCBFZGl0b3IgLSBBdXRvLXNhdmVkIGF0ICVz RWRpdGluZyBDb3VudHJ5L1N0YXRl + RWRpdGluZyBEcmFmdCAoJTIkcyk= RWRpdGluZyBFbWFpbCBFdmVudA== RWRpdGluZyBGaWxl RWRpdGluZyBNZW1iZXJzaGlw @@ -1412,6 +1425,7 @@ Vmlld2luZyBmb3JtIHN1Ym1pc3Npb24= Vmlld2luZyBNYWlsaW5nIExpc3Q= Vmlld2luZyBSZXBseQ== + Vmlld2luZyBSZXZpc2lvbiAjJXMgKCVzKQ== VmlzaXRz V2Vic2l0ZQ== Q3Vyci4gU2VjdGlvbg== @@ -1435,6 +1449,7 @@ RGVueQ== RGV0YWlscw== RGlzYWJsZQ== + RGlzY2FyZA== RWRpdA== RWRpdCBDdXJyZW50IFNlY3Rpb24= RnJvbnQtRW5kIE9ubHk= @@ -1442,6 +1457,7 @@ RXhwb3J0 RXhwb3J0IExhbmd1YWdl SGlkZSBNZW51 + SGlzdG9yeQ== SG9tZQ== SW1wb3J0 SW1wb3J0IExhbmd1YWdl @@ -1473,9 +1489,11 @@ TmV4dA== UGFzdGU= UHJldmlvdXM= + UHJldmlldw== U2V0IFByaW1hcnkgR3JvdXA= UHJpbnQ= UHJvY2VzcyBRdWV1ZQ== + UHVibGlzaA== UmVidWlsZCBTZWN0aW9uIENhY2hl UmVjYWxjdWxhdGUgUHJpb3JpdGllcw== UmVmcmVzaA== Index: install/install_data.sql =================================================================== --- install/install_data.sql (revision 14487) +++ install/install_data.sql (working copy) @@ -489,6 +489,11 @@ INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.DELETE', 'la_PermName_Category.Delete_desc', 'In-Portal'); INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.ADD.PENDING', 'la_PermName_Category.AddPending_desc', 'In-Portal'); INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.MODIFY', 'la_PermName_Category.Modify_desc', 'In-Portal'); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.ADD', 'la_PermName_Category.Revision.Add_desc', 'In-Portal'); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.ADD.PENDING', 'la_PermName_Category.Revision.Add.Pending_desc', 'In-Portal'); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.MODERATE', 'la_PermName_Category.Revision.Moderate_desc', 'In-Portal'); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.HISTORY.VIEW', 'la_PermName_Category.Revision.History.View_desc', 'In-Portal'); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.HISTORY.RESTORE', 'la_PermName_Category.Revision.History.Restore_desc', 'In-Portal'); INSERT INTO PermissionConfig VALUES (DEFAULT, 'ADMIN', 'la_PermName_Admin_desc', 'Admin'); INSERT INTO PermissionConfig VALUES (DEFAULT, 'LOGIN', 'la_PermName_Login_desc', 'Front'); INSERT INTO PermissionConfig VALUES (DEFAULT, 'DEBUG.ITEM', 'la_PermName_Debug.Item_desc', 'Admin'); Index: install/install_schema.sql =================================================================== --- install/install_schema.sql (revision 14476) +++ install/install_schema.sql (working copy) @@ -1,9 +1,9 @@ CREATE TABLE PermissionConfig ( - PermissionConfigId int(11) NOT NULL auto_increment, - PermissionName varchar(30) NOT NULL default '', - Description varchar(255) NOT NULL default '', - ModuleId varchar(20) NOT NULL default '0', - PRIMARY KEY (PermissionConfigId), + PermissionConfigId int(11) NOT NULL AUTO_INCREMENT, + PermissionName varchar(255) NOT NULL DEFAULT '', + Description varchar(255) NOT NULL DEFAULT '', + ModuleId varchar(20) NOT NULL DEFAULT '0', + PRIMARY KEY (PermissionConfigId), KEY PermissionName (PermissionName) ); @@ -460,6 +460,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', PRIMARY KEY (CategoryId), UNIQUE KEY ResourceId (ResourceId), KEY ParentId (ParentId), @@ -487,7 +488,8 @@ KEY EnablePageCache (EnablePageCache), KEY OverridePageCacheKey (OverridePageCacheKey), KEY PageExpiration (PageExpiration), - KEY Protected (Protected) + KEY Protected (Protected), + KEY LiveRevisionNumber (LiveRevisionNumber) ); CREATE TABLE CategoryCustomData ( @@ -988,15 +990,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: install/remove_schema.sql =================================================================== --- install/remove_schema.sql (revision 14476) +++ install/remove_schema.sql (working copy) @@ -61,6 +61,7 @@ DROP TABLE StopWords; DROP TABLE MailingLists; DROP TABLE PageContent; +DROP TABLE PageRevisions; DROP TABLE FormFields; DROP TABLE FormSubmissions; DROP TABLE SubmissionLog; Index: install/upgrades.php =================================================================== --- install/upgrades.php (revision 14476) +++ install/upgrades.php (working copy) @@ -1768,4 +1768,38 @@ $this->Conn->Query($sql); } } + + /** + * Update to 5.1.3; Transforms PageContent table + * + * @param string $mode when called mode {before, after) + */ + function Upgrade_5_1_3($mode) + { + if ($mode == 'after') { + $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: install/upgrades.sql =================================================================== --- install/upgrades.sql (revision 14487) +++ install/upgrades.sql (working copy) @@ -2039,4 +2039,38 @@ UPDATE Phrase SET l<%PRIMARY_LANGUAGE%>_Translation = 'User name length (min - max)' -WHERE PhraseKey = 'LA_TEXT_MIN_USERNAME' AND l<%PRIMARY_LANGUAGE%>_Translation = 'Minimum user name length'; \ No newline at end of file +WHERE PhraseKey = 'LA_TEXT_MIN_USERNAME' AND l<%PRIMARY_LANGUAGE%>_Translation = 'Minimum user name length'; + +# ===== v 5.1.3 ===== +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'); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.ADD.PENDING', 'la_PermName_Category.Revision.Add.Pending_desc', 'In-Portal'); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.MODERATE', 'la_PermName_Category.Revision.Moderate_desc', 'In-Portal'); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.HISTORY.VIEW', 'la_PermName_Category.Revision.History.View_desc', 'In-Portal'); +INSERT INTO PermissionConfig VALUES (DEFAULT, 'CATEGORY.REVISION.HISTORY.RESTORE', 'la_PermName_Category.Revision.History.Restore_desc', 'In-Portal'); \ No newline at end of file Index: kernel/application.php =================================================================== --- kernel/application.php (revision 14509) +++ kernel/application.php (working copy) @@ -1042,15 +1042,15 @@ safeDefine('DBG_SKIP_REPORTING', 1); // safeDefine, because debugger also defines it } } - elseif ($this->GetVar('admin')) { - // viewing front-end through admin's frame + elseif ( $this->GetVar('admin') ) { $admin_session =& $this->Application->recallObject('Session.admin'); - $user = (int)$admin_session->RecallVar('user_id'); // in case, when no valid admin session found - $perm_helper =& $this->recallObject('PermissionsHelper'); - /* @var $perm_helper kPermissionsHelper */ + /* @var $admin_session Session */ - if ($perm_helper->CheckUserPermission($user, 'CATEGORY.MODIFY', 0, $this->getBaseCategory())) { - // user can edit cms blocks + // store Admin Console User's ID to Front-End's session for cross-session permission checks + $this->Application->StoreVar('admin_user_id', (int)$admin_session->RecallVar('user_id')); + + 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); } @@ -3013,10 +3013,28 @@ function CheckPermission($name, $type = 1, $cat_id = null) { $perm_helper =& $this->recallObject('PermissionsHelper'); + /* @var $perm_helper kPermissionsHelper */ + 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 * * @param string $field Index: kernel/constants.php =================================================================== --- kernel/constants.php (revision 14476) +++ kernel/constants.php (working copy) @@ -175,4 +175,7 @@ define('RECIPIENT_TYPE_BCC', 3); define('PAGE_TYPE_VIRTUAL', 1); - define('PAGE_TYPE_TEMPLATE', 2); \ No newline at end of file + define('PAGE_TYPE_TEMPLATE', 2); + + define('CONTENT_LASTAUTOSAVE_UPDATE_INTERVAL', 5 * 60); // 5 minutes + define('CONTENT_LASTAUTOSAVE_REFRESH_INTERVAL', 20); // 20 seconds \ No newline at end of file Index: kernel/db/db_event_handler.php =================================================================== --- kernel/db/db_event_handler.php (revision 14476) +++ kernel/db/db_event_handler.php (working copy) @@ -675,7 +675,9 @@ $object->Counted = false; // when requery="1" should re-count records too! $object->ClearOrderFields(); // prevents duplicate order fields, when using requery="1" - $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: kernel/languages/phrases_cache.php =================================================================== --- kernel/languages/phrases_cache.php (revision 14476) +++ kernel/languages/phrases_cache.php (working copy) @@ -145,6 +145,7 @@ else { if ($this->Application->isAdmin) { $this->LanguageId = $this->Application->Session->GetField('Language'); + $this->AdminLanguageId = $this->LanguageId; // same languages, when used from Admin Console } else { $this->LanguageId = $this->Application->GetVar('m_lang'); Index: units/categories/categories_config.php =================================================================== --- units/categories/categories_config.php (revision 14476) +++ units/categories/categories_config.php (working copy) @@ -270,7 +270,7 @@ '-virtual' => '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), ), 'VirtualFields' => Array ( Index: units/categories/categories_tag_processor.php =================================================================== --- units/categories/categories_tag_processor.php (revision 14476) +++ units/categories/categories_tag_processor.php (working copy) @@ -1076,7 +1076,7 @@ * Returns page object based on requested params * * @param Array $params - * @return PagesItem + * @return CategoriesItem */ function &_getPage($params) { @@ -1133,23 +1133,20 @@ $page =& $this->_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 = ''; @@ -1345,7 +1342,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"; @@ -1363,6 +1360,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', ); @@ -1382,8 +1380,31 @@ $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'); @@ -1391,6 +1412,9 @@ $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"; @@ -1433,6 +1457,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 ''; } @@ -1515,8 +1544,78 @@ 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 . '
'; - } else { // button without border around the page @@ -1530,6 +1629,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 @@ -1539,6 +1661,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: units/content/content_config.php =================================================================== --- units/content/content_config.php (revision 14476) +++ units/content/content_config.php (working copy) @@ -30,14 +30,15 @@ ), 'IDField' => '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, - 'TitleField' => 'ContentNum', // field, used in bluebar when editing existing item - + 'TitleField' => 'ContentNum', // field, used in bluebar when editing existing item + 'TitlePresets' => Array ( 'default' => Array ( 'new_status_labels' => Array ('content' => '!la_title_Adding_Content!'), @@ -48,10 +49,14 @@ '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'), ) ), @@ -59,6 +64,7 @@ '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', 'formatter' => 'kMultiLanguage', 'using_fck' => 1, 'default' => ''), ), Index: units/content/content_eh.php =================================================================== --- units/content/content_eh.php (revision 14476) +++ units/content/content_eh.php (working copy) @@ -34,4 +34,171 @@ return $perm_helper->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: units/helpers/helpers_config.php =================================================================== --- units/helpers/helpers_config.php (revision 14476) +++ units/helpers/helpers_config.php (working copy) @@ -68,5 +68,6 @@ Array ('pseudo' => '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'), ), ); \ No newline at end of file Index: units/helpers/minifiers/minify_helper.php =================================================================== --- units/helpers/minifiers/minify_helper.php (revision 14476) +++ units/helpers/minifiers/minify_helper.php (working copy) @@ -124,7 +124,13 @@ } // replace templates base - $templates_base = $this->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: units/helpers/page_helper.php =================================================================== --- units/helpers/page_helper.php (revision 0) +++ units/helpers/page_helper.php (revision 0) @@ -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.Login 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; + } +} \ No newline at end of file Index: units/helpers/permissions_helper.php =================================================================== --- units/helpers/permissions_helper.php (revision 14476) +++ units/helpers/permissions_helper.php (working copy) @@ -389,6 +389,7 @@ $perm_prefix = getArrayValue($params, 'perm_prefix'); $perm_event = getArrayValue($params, 'perm_event'); $permission_groups = getArrayValue($params, 'permissions'); + $check_admin = isset($params['admin']) && $params['admin']; if ($permission_groups && !$perm_event) { // check permissions by permission names in current category @@ -404,12 +405,22 @@ $is_system = isset($params['system']) && $params['system'] ? 1 : 0; foreach ($permission_groups as $permission_group) { + $has_permission = true; $permissions = explode(',', $permission_group); - $has_permission = true; - 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; + + 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) { @@ -532,8 +543,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 $name == 'SYSTEM_ACCESS.READONLY' ? 0 : 1; @@ -554,18 +585,27 @@ // 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 = $this->Application->RecallVar('UserGroups'); + } + else { + // checking not current user + $groups = $this->Application->RecallVar('UserGroups:' . $user_id); - if ($user_id == $this->Application->RecallVar('user_id')) { - $groups = explode(',', $this->Application->RecallVar('UserGroups')); + if ( $groups === false ) { + $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); + } } - 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') ); - } + $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: units/page_revisions/page_revision_eh.php =================================================================== --- units/page_revisions/page_revision_eh.php (revision 0) +++ units/page_revisions/page_revision_eh.php (revision 0) @@ -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; + } +} \ No newline at end of file Index: units/page_revisions/page_revision_tp.php =================================================================== --- units/page_revisions/page_revision_tp.php (revision 0) +++ units/page_revisions/page_revision_tp.php (revision 0) @@ -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') ); + } +} \ No newline at end of file Index: units/page_revisions/page_revisions_config.php =================================================================== --- units/page_revisions/page_revisions_config.php (revision 0) +++ units/page_revisions/page_revisions_config.php (revision 0) @@ -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.Login', + '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' => 'Login', '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: units/structure/structure_config.php =================================================================== --- units/structure/structure_config.php (revision 14476) +++ units/structure/structure_config.php (working copy) @@ -104,7 +104,7 @@ '-virtual' => '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), ), 'VirtualFields' => Array(