|
@@ -144,10 +174,48 @@
|
id); ?>
|
-
-
- state, $i, 'articles.', $canChange, 'cb', $item->publish_up, $item->publish_down); ?>
- featured, $i, $canChange); ?>
+
+
+
+ featured, $i, $canChange); ?>
+ state_condition) :
+
+ case Workflow::TRASHED:
+ $icon = 'trash';
+ break;
+
+ case Workflow::UNPUBLISHED:
+ $icon = 'unpublish';
+ break;
+
+ endswitch;
+ ?>
+
+
+
+
+
+
+
+
+ escape($item->state_title); ?>
+
+
+ 'transition-select_' . (int) $item->id,
+ 'list.attr' => [
+ 'class' => 'custom-select custom-select-sm form-control form-control-sm',
+ 'onchange' => "listItemTask('cb" . (int) $i . "', 'articles.runTransition')"]
+ ];
+ echo HTMLHelper::_('select.genericlist', $transitions, 'transition_' . (int) $item->id, $attribs);
+ ?>
+
+
|
@@ -163,8 +231,8 @@
escape($item->title); ?>
- escape($item->alias)); ?>
-
+ escape($item->alias)); ?>
+
escape($item->category_title); ?>
diff --git a/administrator/components/com_menus/presets/joomla.xml b/administrator/components/com_menus/presets/joomla.xml
index e80ceea85b4a8..147cd35e222d5 100644
--- a/administrator/components/com_menus/presets/joomla.xml
+++ b/administrator/components/com_menus/presets/joomla.xml
@@ -33,8 +33,14 @@
type="component"
element="com_content"
link="index.php?option=com_content&view=featured"
- class="class:featured"
- />
+ class="class:featured" >
+
+
@@ -189,7 +195,19 @@
link="index.php?option=com_users&view=levels">
+
+
| |
-
diff --git a/administrator/modules/mod_menu/Menu/CssMenu.php b/administrator/modules/mod_menu/Menu/CssMenu.php
index 979640f4e7e2e..9795ae78da751 100644
--- a/administrator/modules/mod_menu/Menu/CssMenu.php
+++ b/administrator/modules/mod_menu/Menu/CssMenu.php
@@ -319,6 +319,25 @@ protected function preprocess($items)
list($assetName) = isset($query['context']) ? explode('.', $query['context'], 2) : array('com_fields');
}
+ elseif ($item->element === 'com_workflow')
+ {
+ parse_str($item->link, $query);
+
+ // Only display Fields menus when enabled in the component
+ $workflow = null;
+
+ if (isset($query['extension']))
+ {
+ $workflow = ComponentHelper::getParams($query['extension'])->get('workflows_enable', 1);
+ }
+
+ if (!$workflow)
+ {
+ continue;
+ }
+
+ list($assetName) = isset($query['extension']) ? explode('.', $query['extension'], 2) : array('com_workflow');
+ }
elseif ($item->element === 'com_config' && !$user->authorise('core.admin'))
{
continue;
diff --git a/build/media/system/js/core.js b/build/media/system/js/core.js
new file mode 100644
index 0000000000000..ca293537011c9
--- /dev/null
+++ b/build/media/system/js/core.js
@@ -0,0 +1,1312 @@
+/**
+ * @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+// Only define the Joomla namespace if not defined.
+Joomla = window.Joomla || {};
+
+// Only define editors if not defined
+Joomla.editors = Joomla.editors || {};
+
+// An object to hold each editor instance on page, only define if not defined.
+Joomla.editors.instances = Joomla.editors.instances || {
+ /**
+ * *****************************************************************
+ * All Editors MUST register, per instance, the following callbacks:
+ * *****************************************************************
+ *
+ * getValue Type Function Should return the complete data from the editor
+ * Example: function () { return this.element.value; }
+ * setValue Type Function Should replace the complete data of the editor
+ * Example: function (text) { return this.element.value = text; }
+ * replaceSelection Type Function Should replace the selected text of the editor
+ * If nothing selected, will insert the data at the cursor
+ * Example: function (text) { return insertAtCursor(this.element, text); }
+ *
+ * USAGE (assuming that jform_articletext is the textarea id)
+ * {
+ * To get the current editor value:
+ * Joomla.editors.instances['jform_articletext'].getValue();
+ * To set the current editor value:
+ * Joomla.editors.instances['jform_articletext'].setValue('Joomla! rocks');
+ * To replace(selection) or insert a value at the current editor cursor:
+ * replaceSelection: Joomla.editors.instances['jform_articletext'].replaceSelection('Joomla! rocks')
+ * }
+ *
+ * *********************************************************
+ * ANY INTERACTION WITH THE EDITORS SHOULD USE THE ABOVE API
+ * *********************************************************
+ *
+ * jInsertEditorText() @deprecated 4.0
+ */
+};
+
+Joomla.Modal = {
+ /**
+ * *****************************************************************
+ * Modals should implement
+ * *****************************************************************
+ *
+ * getCurrent Type Function Should return the modal element
+ * setCurrent Type Function Should set the modal element
+ * current Type {node} The modal element
+ *
+ * USAGE (assuming that exampleId is the modal id)
+ * To get the current modal element:
+ * Joomla.Modal.current; // Returns node element, eg: document.getElementById('exampleId')
+ * To set the current modal element:
+ * Joomla.Modal.setCurrent(document.getElementById('exampleId'));
+ *
+ * *************************************************************
+ * Joomla's UI modal uses `element.close();` to close the modal
+ * and `element.open();` to open the modal
+ * If you are using another modal make sure the same
+ * functionality is bound to the modal element
+ * @see media/legacy/bootstrap.init.js
+ * *************************************************************
+ */
+ current: '',
+ setCurrent: function(element) {
+ this.current = element;
+ },
+ getCurrent: function() {
+ return this.current;
+ },
+};
+
+(function( Joomla, document ) {
+ "use strict";
+
+ /**
+ * Generic submit form
+ *
+ * @param {String} task The given task
+ * @param {node} form The form element
+ * @param {bool} validate The form element
+ *
+ * @returns {void}
+ */
+ Joomla.submitform = function(task, form, validate) {
+
+ if (!form) {
+ form = document.getElementById('adminForm');
+ }
+
+ if (task) {
+ form.task.value = task;
+ }
+
+ // Toggle HTML5 validation
+ form.noValidate = !validate;
+
+ if (!validate) {
+ form.setAttribute('novalidate', '');
+ } else if (form.hasAttribute('novalidate')) {
+ form.removeAttribute('novalidate');
+ }
+
+ // Submit the form.
+ // Create the input type="submit"
+ var button = document.createElement('input');
+ button.style.display = 'none';
+ button.type = 'submit';
+
+ // Append it and click it
+ form.appendChild(button).click();
+
+ // If "submit" was prevented, make sure we don't get a build up of buttons
+ form.removeChild(button);
+ };
+
+ /**
+ * Default function. Can be overriden by the component to add custom logic
+ *
+ * @param {String} task The given task
+ * @param {String} formSelector The form selector eg '#adminForm'
+ * @param {bool} validate The form element
+ *
+ * @returns {void}
+ */
+ Joomla.submitbutton = function( task, formSelector, validate ) {
+ var form = document.querySelector( formSelector || 'form.form-validate' );
+
+ if (form) {
+
+ if (validate === undefined || validate === null) {
+ var pressbutton = task.split('.'),
+ cancelTask = form.getAttribute('data-cancel-task');
+
+ if (!cancelTask) {
+ cancelTask = pressbutton[0] + '.cancel';
+ }
+
+ validate = task !== cancelTask;
+ }
+
+ if (!validate || document.formvalidator.isValid( form )) {
+ Joomla.submitform( task, form );
+ }
+
+ } else {
+ Joomla.submitform( task );
+ }
+ };
+
+ /**
+ * Custom behavior for JavaScript I18N in Joomla! 1.6
+ *
+ * @type {{}}
+ *
+ * Allows you to call Joomla.JText._() to get a translated JavaScript string pushed in with JText::script() in Joomla.
+ */
+ Joomla.JText = {
+ strings: {},
+
+ /**
+ * Translates a string into the current language.
+ *
+ * @param {String} key The string to translate
+ * @param {String} def Default string
+ *
+ * @returns {String}
+ */
+ '_': function( key, def ) {
+
+ // Check for new strings in the optionsStorage, and load them
+ var newStrings = Joomla.getOptions('joomla.jtext');
+ if ( newStrings ) {
+ this.load(newStrings);
+
+ // Clean up the optionsStorage from useless data
+ Joomla.loadOptions({'joomla.jtext': null});
+ }
+
+ def = def === undefined ? '' : def;
+ key = key.toUpperCase();
+
+ return this.strings[ key ] !== undefined ? this.strings[ key ] : def;
+ },
+
+ /**
+ * Load new strings in to Joomla.JText
+ *
+ * @param {Object} object Object with new strings
+ * @returns {Joomla.JText}
+ */
+ load: function( object ) {
+ for ( var key in object ) {
+ if (!object.hasOwnProperty(key)) continue;
+ this.strings[ key.toUpperCase() ] = object[ key ];
+ }
+
+ return this;
+ }
+ };
+
+ /**
+ * Joomla options storage
+ *
+ * @type {{}}
+ *
+ * @since 3.7.0
+ */
+ Joomla.optionsStorage = Joomla.optionsStorage || null;
+
+ /**
+ * Get script(s) options
+ *
+ * @param {String} key Name in Storage
+ * @param {mixed} def Default value if nothing found
+ *
+ * @return {mixed}
+ *
+ * @since 3.7.0
+ */
+ Joomla.getOptions = function( key, def ) {
+ // Load options if they not exists
+ if (!Joomla.optionsStorage) {
+ Joomla.loadOptions();
+ }
+
+ return Joomla.optionsStorage[key] !== undefined ? Joomla.optionsStorage[key] : def;
+ };
+
+ /**
+ * Load new options from given options object or from Element
+ *
+ * @param {Object|undefined} options The options object to load. Eg {"com_foobar" : {"option1": 1, "option2": 2}}
+ *
+ * @since 3.7.0
+ */
+ Joomla.loadOptions = function( options ) {
+ // Load form the script container
+ if (!options) {
+ var elements = document.querySelectorAll('.joomla-script-options.new'),
+ str, element, option, counter = 0;
+
+ for (var i = 0, l = elements.length; i < l; i++) {
+ element = elements[i];
+ str = element.text || element.textContent;
+ option = JSON.parse(str);
+
+ if (option) {
+ Joomla.loadOptions(option);
+ counter++;
+ }
+
+ element.className = element.className.replace(' new', ' loaded');
+ }
+
+ if (counter) {
+ return;
+ }
+ }
+
+ // Initial loading
+ if (!Joomla.optionsStorage) {
+ Joomla.optionsStorage = options || {};
+ }
+ // Merge with existing
+ else if ( options ) {
+ for (var p in options) {
+ if (options.hasOwnProperty(p)) {
+ /**
+ * If both existing and new options are objects, merge them with Joomla.extend(). But test for new
+ * option being null, as null is an object, but we want to allow clearing of options with ...
+ *
+ * Joomla.loadOptions({'joomla.jtext': null});
+ */
+ if (options[p] !== null && typeof Joomla.optionsStorage[p] === 'object' && typeof options[p] === 'object') {
+ Joomla.optionsStorage[p] = Joomla.extend(Joomla.optionsStorage[p], options[p]);
+ } else {
+ Joomla.optionsStorage[p] = options[p];
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Method to replace all request tokens on the page with a new one.
+ *
+ * @param {String} newToken The token
+ *
+ * Used in Joomla Installation
+ */
+ Joomla.replaceTokens = function( newToken ) {
+ if (!/^[0-9A-F]{32}$/i.test(newToken)) { return; }
+
+ var els = document.getElementsByTagName( 'input' ),
+ i, el, n;
+
+ for ( i = 0, n = els.length; i < n; i++ ) {
+ el = els[i];
+
+ if ( el.type == 'hidden' && el.value == '1' && el.name.length == 32 ) {
+ el.name = newToken;
+ }
+ }
+ };
+
+ /**
+ * USED IN: all list forms.
+ *
+ * Toggles the check state of a group of boxes
+ *
+ * Checkboxes must have an id attribute in the form cb0, cb1...
+ *
+ * @param {mixed} checkbox The number of box to 'check', for a checkbox element
+ * @param {string} stub An alternative field name
+ *
+ * @return {boolean}
+ */
+ Joomla.checkAll = function( checkbox, stub ) {
+ if (!checkbox.form) return false;
+
+ stub = stub ? stub : 'cb';
+
+ var c = 0,
+ i, e, n;
+
+ for ( i = 0, n = checkbox.form.elements.length; i < n; i++ ) {
+ e = checkbox.form.elements[ i ];
+
+ if ( e.type == checkbox.type && e.id.indexOf( stub ) === 0 ) {
+ e.checked = checkbox.checked;
+ c += e.checked ? 1 : 0;
+ }
+ }
+
+ if ( checkbox.form.boxchecked ) {
+ checkbox.form.boxchecked.value = c;
+ Joomla.Event.dispatch(checkbox.form.boxchecked, 'change');
+ }
+
+ return true;
+ };
+
+ /**
+ * Toggles the check state of a group of boxes
+ *
+ * Checkboxes must have an id attribute in the form cb0, cb1...
+ *
+ * @param {node} item The form
+ * @param {string} stub An alternative field name
+ *
+ * @return {boolean}
+ */
+ Joomla.uncheckAll = function( item, stub ) {
+ if (!item.form) return false;
+
+ stub = stub ? stub : 'cb';
+
+ var c = 0,
+ i, e, n;
+
+ for ( i = 0, n = item.form.elements.length; i < n; i++ ) {
+ e = item.form.elements[ i ];
+
+ if ( e.type === 'checkbox' && e.id.indexOf( stub ) === 0 ) {
+ e.checked = false;
+ }
+ }
+
+ if ( item.form.boxchecked ) {
+ item.form.boxchecked.value = c;
+ }
+
+ return true;
+ };
+
+ /**
+ * Toggles the check state of a group of boxes
+ *
+ * Checkboxes must have an id attribute in the form cb0, cb1...
+ *
+ * @param {node} el The form item
+ * @param {bool} cond An alternative value to set checkbox
+ *
+ * @return {boolean}
+ */
+ Joomla.toggleOne = function( el, cond ) {
+ if (!el.form) return false;
+
+ var item = el;
+
+ while (item = item.parentNode) {
+ if (item.tagName.toLowerCase() === 'tr') {
+ break;
+ }
+ }
+
+ var checkbox = item.querySelector('input[name="cid[]"]');
+
+ if (checkbox) {
+ checkbox.checked = cond ? cond : !checkbox.checked;
+ if (checkbox.checked) {
+ cond = checkbox.checked;
+ }
+ }
+
+ if ( el.form.boxchecked && cond) {
+ el.form.boxchecked.value = parseInt(el.form.boxchecked.value) + 1;
+ }
+
+ return true;
+ };
+
+ /**
+ * Render messages send via JSON
+ * Used by some javascripts such as validate.js
+ *
+ * @param {object} messages JavaScript object containing the messages to render. Example:
+ * var messages = {
+ * "message": ["Message one", "Message two"],
+ * "error": ["Error one", "Error two"]
+ * };
+ * @param {string} selector The selector of the container where the message will be rendered
+ * @param {bool} keepOld If we shall discard old messages
+ * @param {int} timeout The milliseconds before the message self destruct
+ * @return void
+ */
+ Joomla.renderMessages = function( messages, selector, keepOld, timeout ) {
+ var messageContainer, type, typeMessages, messagesBox, title, titleWrapper, i, messageWrapper, alertClass;
+
+ if (typeof selector === 'undefined' || selector && selector === '#system-message-container') {
+ messageContainer = document.getElementById( 'system-message-container' );
+ } else {
+ messageContainer = document.querySelector( selector );
+ }
+
+ if (typeof keepOld === 'undefined' || keepOld && keepOld === false) {
+ Joomla.removeMessages( messageContainer );
+ }
+
+ for ( type in messages ) {
+ if ( !messages.hasOwnProperty( type ) ) { continue; }
+ // Array of messages of this type
+ typeMessages = messages[ type ];
+
+ if (typeof window.customElements === 'object' && typeof window.customElements.get('joomla-alert') === 'function') {
+ messagesBox = document.createElement( 'joomla-alert' );
+
+ if (['notice','message', 'error'].indexOf(type) > -1) {
+ alertClass = (type === 'notice') ? 'info' : type;
+ alertClass = (type === 'message') ? 'success' : alertClass;
+ alertClass = (type === 'error') ? 'danger' : alertClass;
+ } else {
+ alertClass = 'info';
+ }
+
+ messagesBox.setAttribute('type', alertClass);
+ messagesBox.setAttribute('dismiss', 'true');
+
+ if (timeout && parseInt(timeout) > 0) {
+ messagesBox.setAttribute('autodismiss', timeout);
+ }
+ } else {
+ // Create the alert box
+ messagesBox = document.createElement( 'div' );
+
+ // Message class
+ if (['notice','message', 'error'].indexOf(type) > -1) {
+ alertClass = (type === 'notice') ? 'info' : type;
+ alertClass = (type === 'message') ? 'success' : alertClass;
+ alertClass = (type === 'error') ? 'danger' : alertClass;
+ } else {
+ alertClass = 'info';
+ }
+
+ messagesBox.className = 'alert ' + alertClass;
+
+ // Close button
+ var buttonWrapper = document.createElement( 'button' );
+ buttonWrapper.setAttribute('type', 'button');
+ buttonWrapper.setAttribute('data-dismiss', 'alert');
+ buttonWrapper.className = 'close';
+ buttonWrapper.innerHTML = '×';
+ messagesBox.appendChild( buttonWrapper );
+ }
+
+ // Title
+ title = Joomla.JText._( type );
+
+ // Skip titles with untranslated strings
+ if ( typeof title != 'undefined' ) {
+ titleWrapper = document.createElement( 'h4' );
+ titleWrapper.className = 'alert-heading';
+ titleWrapper.innerHTML = Joomla.JText._( type ) ? Joomla.JText._( type ) : type;
+ messagesBox.appendChild( titleWrapper );
+ }
+
+ // Add messages to the message box
+ for ( i = typeMessages.length - 1; i >= 0; i-- ) {
+ messageWrapper = document.createElement( 'div' );
+ messageWrapper.innerHTML = typeMessages[ i ];
+ messagesBox.appendChild( messageWrapper );
+ }
+
+ messageContainer.appendChild( messagesBox );
+
+ if (typeof window.customElements !== 'object' && typeof window.customElements.get('joomla-alert') !== 'function') {
+ if (timeout && parseInt(timeout) > 0) {
+ setTimeout(function () {
+ Joomla.removeMessages(messageContainer);
+ }, timeout);
+ }
+ }
+ }
+ };
+
+
+ /**
+ * Remove messages
+ *
+ * @param {element} container The element of the container of the message to be removed
+ *
+ * @return {void}
+ */
+ Joomla.removeMessages = function( container ) {
+ var messageContainer;
+
+ if (container) {
+ messageContainer = container;
+ } else {
+ messageContainer = document.getElementById( 'system-message-container' );
+ }
+
+ if (typeof window.customElements === 'object' && window.customElements.get('joomla-alert')) {
+ var messages = messageContainer.querySelectorAll('joomla-alert');
+ if (messages.length) {
+ for (var i = 0, l = messages.length; i < l; i++) {
+ messages[i].close();
+ }
+ }
+ } else {
+ // Empty container with a while for Chrome performance issues
+ while ( messageContainer.firstChild ) messageContainer.removeChild( messageContainer.firstChild );
+
+ // Fix Chrome bug not updating element height
+ messageContainer.style.display = 'none';
+ messageContainer.offsetHeight;
+ messageContainer.style.display = '';
+ }
+ };
+
+ /**
+ * Treat AJAX errors.
+ * Used by some javascripts such as sendtestmail.js and permissions.js
+ *
+ * @param {object} xhr XHR object.
+ * @param {string} textStatus Type of error that occurred.
+ * @param {string} error Textual portion of the HTTP status.
+ *
+ * @return {object} JavaScript object containing the system error message.
+ *
+ * @since 3.6.0
+ */
+ Joomla.ajaxErrorsMessages = function( xhr, textStatus, error ) {
+ var msg = {};
+
+ // For jQuery jqXHR
+ if (textStatus === 'parsererror')
+ {
+ // Html entity encode.
+ var encodedJson = xhr.responseText.trim();
+
+ var buf = [];
+ for (var i = encodedJson.length-1; i >= 0; i--) {
+ buf.unshift( [ '', encodedJson[i].charCodeAt(), ';' ].join('') );
+ }
+
+ encodedJson = buf.join('');
+
+ msg.error = [ Joomla.JText._('JLIB_JS_AJAX_ERROR_PARSE').replace('%s', encodedJson) ];
+ }
+ else if (textStatus === 'nocontent')
+ {
+ msg.error = [ Joomla.JText._('JLIB_JS_AJAX_ERROR_NO_CONTENT') ];
+ }
+ else if (textStatus === 'timeout')
+ {
+ msg.error = [ Joomla.JText._('JLIB_JS_AJAX_ERROR_TIMEOUT') ];
+ }
+ else if (textStatus === 'abort')
+ {
+ msg.error = [ Joomla.JText._('JLIB_JS_AJAX_ERROR_CONNECTION_ABORT') ];
+ }
+ // For vannila XHR
+ else if (xhr.responseJSON && xhr.responseJSON.message)
+ {
+ msg.error = [ Joomla.JText._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status) + ' ' + xhr.responseJSON.message + '' ];
+ }
+ else if (xhr.statusText)
+ {
+ msg.error = [ Joomla.JText._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status) + ' ' + xhr.statusText + '' ];
+ }
+ else
+ {
+ msg.error = [ Joomla.JText._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status) ];
+ }
+
+ return msg;
+ };
+
+ /**
+ * USED IN: administrator/components/com_cache/views/cache/tmpl/default.php
+ * administrator/components/com_installer/views/discover/tmpl/default_item.php
+ * administrator/components/com_installer/views/update/tmpl/default_item.php
+ * administrator/components/com_languages/helpers/html/languages.php
+ * libraries/joomla/html/html/grid.php
+ *
+ * @param {boolean} isitchecked Flag for checked
+ * @param {node} form The form
+ *
+ * @return {void}
+ */
+ Joomla.isChecked = function( isitchecked, form ) {
+ if ( typeof form === 'undefined' ) {
+ form = document.getElementById( 'adminForm' );
+ }
+
+ form.boxchecked.value = isitchecked ? parseInt(form.boxchecked.value) + 1 : parseInt(form.boxchecked.value) - 1;
+
+ Joomla.Event.dispatch(form.boxchecked, 'change');
+
+ // If we don't have a checkall-toggle, done.
+ if ( !form.elements[ 'checkall-toggle' ] ) return;
+
+ // Toggle main toggle checkbox depending on checkbox selection
+ var c = true,
+ i, e, n;
+
+ for ( i = 0, n = form.elements.length; i < n; i++ ) {
+ e = form.elements[ i ];
+
+ if ( e.type == 'checkbox' && e.name != 'checkall-toggle' && !e.checked ) {
+ c = false;
+ break;
+ }
+ }
+
+ form.elements[ 'checkall-toggle' ].checked = c;
+ };
+
+ /**
+ * USED IN: libraries/joomla/html/html/grid.php
+ * In other words, on any reorderable table
+ *
+ * @param {string} order The order value
+ * @param {string} dir The direction
+ * @param {string} task The task
+ * @param {node} form The form
+ *
+ * return {void}
+ */
+ Joomla.tableOrdering = function( order, dir, task, form ) {
+ if ( typeof form === 'undefined' ) {
+ form = document.getElementById( 'adminForm' );
+ }
+
+ form.filter_order.value = order;
+ form.filter_order_Dir.value = dir;
+ Joomla.submitform( task, form );
+ };
+
+ /**
+ * USED IN: administrator/components/com_users/views/mail/tmpl/default.php
+ * Let's get rid of this and kill it
+ *
+ * @param frmName
+ * @param srcListName
+ * @return
+ *
+ * @deprecated 4.0 No replacement
+ */
+ window.getSelectedValue = function ( frmName, srcListName ) {
+ var srcList = document[ frmName ][ srcListName ],
+ i = srcList.selectedIndex;
+
+ if ( i !== null && i > -1 ) {
+ return srcList.options[ i ].value;
+ } else {
+ return null;
+ }
+ };
+
+ /**
+ * USED IN: all over :)
+ *
+ * @param id
+ * @param task
+ * @return
+ *
+ * @deprecated 4.0 Use Joomla.listItemTask() instead
+ */
+ window.listItemTask = function ( id, task ) {
+ return Joomla.listItemTask( id, task );
+ };
+
+ /**
+ * USED IN: all over :)
+ *
+ * @param {string} id The id
+ * @param {string} task The task
+ *
+ * @return {boolean}
+ */
+ Joomla.listItemTask = function ( id, task ) {
+ var f = document.adminForm,
+ i = 0, cbx,
+ cb = f[ id ];
+
+ if ( !cb ) return false;
+
+ while ( true ) {
+ cbx = f[ 'cb' + i ];
+
+ if ( !cbx ) break;
+
+ cbx.checked = false;
+
+ i++;
+ }
+
+ cb.checked = true;
+ f.boxchecked.value = 1;
+ window.submitform( task );
+
+ return false;
+ };
+
+ /**
+ * Default function. Usually would be overriden by the component
+ *
+ * @deprecated 4.0 Use Joomla.submitbutton() instead.
+ */
+ window.submitbutton = function ( pressbutton ) {
+ Joomla.submitbutton( pressbutton );
+ };
+
+ /**
+ * Submit the admin form
+ *
+ * @deprecated 4.0 Use Joomla.submitform() instead.
+ */
+ window.submitform = function ( pressbutton ) {
+ Joomla.submitform(pressbutton);
+ };
+
+ // needed for Table Column ordering
+ /**
+ * USED IN: libraries/joomla/html/html/grid.php
+ * There's a better way to do this now, can we try to kill it?
+ *
+ * @deprecated 4.0 No replacement
+ */
+ window.saveorder = function ( n, task ) {
+ window.checkAll_button( n, task );
+ };
+
+ /**
+ * Checks all the boxes unless one is missing then it assumes it's checked out.
+ * Weird. Probably only used by ^saveorder
+ *
+ * @param {int} n The total number of checkboxes expected
+ * @param {string} task The task to perform
+ *
+ * @return void
+ *
+ * @deprecated 4.0 No replacement
+ */
+ window.checkAll_button = function ( n, task ) {
+ task = task ? task : 'saveorder';
+
+ var j, box;
+
+ for ( j = 0; j <= n; j++ ) {
+ box = document.adminForm[ 'cb' + j ];
+
+ if ( box ) {
+ box.checked = true;
+ } else {
+ alert( "You cannot change the order of items, as an item in the list is `Checked Out`" );
+ return;
+ }
+ }
+
+ Joomla.submitform( task );
+ };
+
+ /**
+ * Add Joomla! loading image layer.
+ *
+ * Used in: /administrator/components/com_installer/views/languages/tmpl/default.php
+ * /installation/template/js/installation.js
+ *
+ * @param {String} task The task to do [load, show, hide] (defaults to show).
+ * @param {HTMLElement} parentElement The HTML element where we are appending the layer (defaults to body).
+ *
+ * @return {HTMLElement} The HTML loading layer element.
+ *
+ * @since 3.6.0
+ */
+ Joomla.loadingLayer = function(task, parentElement) {
+ // Set default values.
+ task = task || 'show';
+ parentElement = parentElement || document.body;
+
+ // Create the loading layer (hidden by default).
+ if (task === 'load')
+ {
+ // Gets the site base path
+ var systemPaths = Joomla.getOptions('system.paths') || {},
+ basePath = systemPaths.root || '';
+
+ var loadingDiv = document.createElement('div');
+
+ loadingDiv.id = 'loading-logo';
+
+ // The loading layer CSS styles are JS hardcoded so they can be used without adding CSS.
+
+ // Loading layer style and positioning.
+ loadingDiv.style['position'] = 'fixed';
+ loadingDiv.style['top'] = '0';
+ loadingDiv.style['left'] = '0';
+ loadingDiv.style['width'] = '100%';
+ loadingDiv.style['height'] = '100%';
+ loadingDiv.style['opacity'] = '0.8';
+ loadingDiv.style['filter'] = 'alpha(opacity=80)';
+ loadingDiv.style['overflow'] = 'hidden';
+ loadingDiv.style['z-index'] = '10000';
+ loadingDiv.style['display'] = 'none';
+ loadingDiv.style['background-color'] = '#fff';
+
+ // Loading logo positioning.
+ loadingDiv.style['background-image'] = 'url("' + basePath + '/media/system/images/ajax-loader.gif")';
+ loadingDiv.style['background-position'] = 'center';
+ loadingDiv.style['background-repeat'] = 'no-repeat';
+ loadingDiv.style['background-attachment'] = 'fixed';
+
+ parentElement.appendChild(loadingDiv);
+ }
+ // Show or hide the layer.
+ else
+ {
+ if (!document.getElementById('loading-logo'))
+ {
+ Joomla.loadingLayer('load', parentElement);
+ }
+
+ document.getElementById('loading-logo').style['display'] = (task == 'show') ? 'block' : 'none';
+ }
+
+ return document.getElementById('loading-logo');
+ };
+
+ /**
+ * Method to Extend Objects
+ *
+ * @param {Object} destination
+ * @param {Object} source
+ *
+ * @return Object
+ */
+ Joomla.extend = function (destination, source) {
+ /**
+ * Technically null is an object, but trying to treat the destination as one in this context will error out.
+ * So emulate jQuery.extend(), and treat a destination null as an empty object.
+ */
+ if (destination === null) {
+ destination = {};
+ }
+ for (var p in source) {
+ if (source.hasOwnProperty(p)) {
+ destination[p] = source[p];
+ }
+ }
+
+ return destination;
+ };
+
+ /**
+ * Method to perform AJAX request
+ *
+ * @param {Object} options Request options:
+ * {
+ * url: 'index.php', // Request URL
+ * method: 'GET', // Request method GET (default), POST
+ * data: null, // Data to be sent, see https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/send
+ * perform: true, // Perform the request immediately, or return XMLHttpRequest instance and perform it later
+ * headers: null, // Object of custom headers, eg {'X-Foo': 'Bar', 'X-Bar': 'Foo'}
+ *
+ * onBefore: function(xhr){} // Callback on before the request
+ * onSuccess: function(response, xhr){}, // Callback on the request success
+ * onError: function(xhr){}, // Callback on the request error
+ * }
+ *
+ * @return XMLHttpRequest|Boolean
+ *
+ * @example
+ *
+ * Joomla.request({
+ * url: 'index.php?option=com_example&view=example',
+ * onSuccess: function(response, xhr){
+ * console.log(response);
+ * }
+ * })
+ *
+ * @see https://developer.mozilla.org/docs/Web/API/XMLHttpRequest
+ */
+ Joomla.request = function (options) {
+
+ // Prepare the options
+ options = Joomla.extend({
+ url: '',
+ method: 'GET',
+ data: null,
+ perform: true
+ }, options);
+
+ // Set up XMLHttpRequest instance
+ try{
+ var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('MSXML2.XMLHTTP.3.0');
+
+ xhr.open(options.method, options.url, true);
+
+ // Set the headers
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ xhr.setRequestHeader('X-Ajax-Engine', 'Joomla!');
+
+ if (options.method !== 'GET') {
+ var token = Joomla.getOptions('csrf.token', '');
+
+ if (token) {
+ xhr.setRequestHeader('X-CSRF-Token', token);
+ }
+
+ if (!options.headers || !options.headers['Content-Type']) {
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ }
+ }
+
+ // Custom headers
+ if (options.headers){
+ for (var p in options.headers) {
+ if (options.headers.hasOwnProperty(p)) {
+ xhr.setRequestHeader(p, options.headers[p]);
+ }
+ }
+ }
+
+ xhr.onreadystatechange = function () {
+ // Request not finished
+ if (xhr.readyState !== 4) return;
+
+ // Request finished and response is ready
+ if (xhr.status === 200) {
+ if(options.onSuccess) {
+ options.onSuccess.call(window, xhr.responseText, xhr);
+ }
+ } else if(options.onError) {
+ options.onError.call(window, xhr);
+ }
+ };
+
+ // Do request
+ if (options.perform) {
+ if (options.onBefore && options.onBefore.call(window, xhr) === false) {
+ // Request interrupted
+ return xhr;
+ }
+
+ xhr.send(options.data);
+ }
+
+ } catch (error) {
+ window.console ? console.log(error) : null;
+ return false;
+ }
+
+ return xhr;
+ };
+
+ /**
+ * Check if HTML5 localStorage enabled on the browser
+ *
+ * @since 4.0.0
+ */
+ Joomla.localStorageEnabled = function() {
+ var test = 'joomla-cms';
+ try {
+ localStorage.setItem(test, test);
+ localStorage.removeItem(test);
+ return true;
+ } catch(e) {
+ return false;
+ }
+ };
+
+ /**
+ * Loads any needed polyfill for web components and async load any web components
+ *
+ * Parts of the WebComponents method belong to The Polymer Project Authors. License http://polymer.github.io/LICENSE.txt
+ *
+ * @since 4.0.0
+ */
+ Joomla.WebComponents = function() {
+ var wc = Joomla.getOptions('webcomponents');
+
+ // Return early
+ if (!wc || !wc.length) {
+ return;
+ }
+
+ var polyfillsLoaded = false;
+ var whenLoadedFns = [];
+ var allowUpgrades = false;
+ var flushFn;
+
+ var fireEvent = function() {
+ window.WebComponents.ready = true;
+ document.dispatchEvent(new CustomEvent('WebComponentsReady', { bubbles: true }));
+ loadWC();
+ };
+
+ var batchCustomElements = function() {
+ if (window.customElements && customElements.polyfillWrapFlushCallback) {
+ customElements.polyfillWrapFlushCallback(function (flushCallback) {
+ flushFn = flushCallback;
+ if (allowUpgrades) {
+ flushFn();
+ }
+ });
+ }
+ };
+
+ var asyncReady = function() {
+ batchCustomElements();
+ ready();
+ };
+
+ var ready = function() {
+ // bootstrap elements before custom elements
+ if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
+ HTMLTemplateElement.bootstrap(window.document);
+ }
+ polyfillsLoaded = true;
+ runWhenLoadedFns().then(fireEvent);
+ };
+
+ var runWhenLoadedFns = function() {
+ allowUpgrades = false;
+ var done = function() {
+ allowUpgrades = true;
+ whenLoadedFns.length = 0;
+ flushFn && flushFn();
+ };
+ return Promise.all(whenLoadedFns.map(function(fn) {
+ return fn instanceof Function ? fn() : fn;
+ })).then(function() {
+ done();
+ }).catch(function(err) {
+ console.error(err);
+ });
+ }
+
+ window.WebComponents = window.WebComponents || {
+ ready: false,
+ _batchCustomElements: batchCustomElements,
+ waitFor: function(waitFn) {
+ if (!waitFn) {
+ return;
+ }
+ whenLoadedFns.push(waitFn);
+ if (polyfillsLoaded) {
+ runWhenLoadedFns();
+ }
+ }
+ };
+
+ /* Check if ES6 then apply the shim */
+ var checkES6 = function () {
+ try {
+ new Function("(a = 0) => a");
+ return true;
+ }
+ catch (err) {
+ return false;
+ }
+ };
+
+ /* Load web components async */
+ var loadWC = function () {
+ if (wc && wc.length) {
+ wc.forEach(function(component) {
+ if (component.match(/\.js/g)) {
+ var el = document.createElement('script');
+ if (!checkES6()) {
+ var es5;
+ // Browser is not ES6!
+ if (component.match(/\.min\.js/g)) {
+ es5 = component.replace(/\.min\.js/g, '-es5.min.js')
+ } else if (component.match(/\.js/g)) {
+ es5 = component.replace(/\.js/g, '-es5.js')
+ }
+ el.src = es5;
+ } else {
+ el.src = component;
+ }
+ }
+ if (el) {
+ document.head.appendChild(el);
+ }
+ });
+ }
+ };
+
+ // Get the core.js src attribute
+ var name = "core.min.js";
+ var script = document.querySelector('script[src*="' + name + '"]');
+
+ if (!script) {
+ name = "core.js";
+ script = document.querySelector('script[src*="' + name + '"]')
+ }
+
+ if (!script) {
+ throw new Error('core(.min).js is not registered correctly!')
+ }
+
+ // Feature detect which polyfill needs to be imported.
+ var polyfills = [];
+ if (!('attachShadow' in Element.prototype && 'getRootNode' in Element.prototype) ||
+ (window.ShadyDOM && window.ShadyDOM.force)) {
+ polyfills.push('sd');
+ }
+ if (!window.customElements || window.customElements.forcePolyfill) {
+ polyfills.push('ce');
+ }
+
+ var needsTemplate = (function() {
+ // no real because no `content` property (IE and older browsers)
+ var t = document.createElement('template');
+ if (!('content' in t)) {
+ return true;
+ }
+ // broken doc fragment (older Edge)
+ if (!(t.content.cloneNode() instanceof DocumentFragment)) {
+ return true;
+ }
+ // broken cloning (Edge up to at least version 17)
+ var t2 = document.createElement('template');
+ t2.content.appendChild(document.createElement('div'));
+ t.content.appendChild(t2);
+ var clone = t.cloneNode(true);
+ return (clone.content.childNodes.length === 0 ||
+ clone.content.firstChild.content.childNodes.length === 0);
+ })();
+
+ // NOTE: any browser that does not have template or ES6 features
+ // must load the full suite of polyfills.
+ if (!window.Promise || !Array.from || !window.URL || !window.Symbol || needsTemplate) {
+ polyfills = ['sd-ce-pf'];
+ }
+
+ if (polyfills.length) {
+ var newScript = document.createElement('script');
+ // Load it from the right place.
+ var replacement = 'media/vendor/webcomponentsjs/js/webcomponents-' + polyfills.join('-') + '.min.js';
+
+ var mediaVersion = script.src.match(/\?.*/)[0];
+ var base = Joomla.getOptions('system.paths');
+
+ if (!base) {
+ throw new Error('core(.min).js is not registered correctly!')
+ }
+
+ newScript.src = base.rootFull + replacement + (mediaVersion ? mediaVersion : '');
+
+ // if readyState is 'loading', this script is synchronous
+ if (document.readyState === 'loading') {
+ // make sure custom elements are batched whenever parser gets to the injected script
+ newScript.setAttribute('onload', 'window.WebComponents._batchCustomElements()');
+ document.write(newScript.outerHTML);
+ document.addEventListener('DOMContentLoaded', ready);
+ } else {
+ newScript.addEventListener('load', function () {
+ asyncReady();
+ });
+ newScript.addEventListener('error', function () {
+ throw new Error('Could not load polyfill bundle' + url);
+ });
+ document.head.appendChild(newScript);
+ }
+ } else {
+ polyfillsLoaded = true;
+ if (document.readyState === 'complete') {
+ fireEvent()
+ } else {
+ // this script may come between DCL and load, so listen for both, and cancel load listener if DCL fires
+ window.addEventListener('load', ready);
+ window.addEventListener('DOMContentLoaded', function() {
+ window.removeEventListener('load', ready);
+ ready();
+ })
+ }
+ }
+ };
+}( Joomla, document ));
+
+/**
+ * Joomla! Custom events
+ *
+ * @since 4.0.0
+ */
+(function( window, Joomla ) {
+ "use strict";
+
+ if (Joomla.Event) {
+ return;
+ }
+
+ Joomla.Event = {};
+
+ /**
+ * Dispatch custom event.
+ *
+ * An event name convention:
+ * The event name has at least two part, separated ":", eg `foo:bar`. Where the first part is an "event supporter",
+ * and second part is the event name which happened.
+ * Which is allow us to avoid possible collisions with another scripts and native DOM events.
+ * Joomla! CMS standard events should start from `joomla:`.
+ *
+ * Joomla! events:
+ * `joomla:updated` Dispatch it over the changed container, example after the content was updated via ajax
+ * `joomla:removed` The container was removed
+ *
+ * @param {HTMLElement|string} element DOM element, the event target. Or the event name, then the target will be a Window
+ * @param {String|Object} name The event name, or an optional parameters in case when "element" is an event name
+ * @param {Object} params An optional parameters. Allow to send a custom data through the event.
+ *
+ * @example
+ *
+ * Joomla.Event.dispatch(myElement, 'joomla:updated', {for: 'bar', foo2: 'bar2'}); // Will dispatch event to myElement
+ * or:
+ * Joomla.Event.dispatch('joomla:updated', {for: 'bar', foo2: 'bar2'}); // Will dispatch event to Window
+ *
+ * @since 4.0.0
+ */
+ Joomla.Event.dispatch = function(element, name, params) {
+ if (typeof element === 'string') {
+ params = name;
+ name = element;
+ element = window;
+ }
+ params = params || {};
+
+ var event;
+
+ if (window.CustomEvent && typeof(window.CustomEvent) === 'function') {
+ event = new CustomEvent(name, {
+ detail: params,
+ bubbles: true,
+ cancelable: true
+ });
+ }
+ // IE trap
+ else {
+ event = document.createEvent('Event');
+ event.initEvent(name, true, true);
+ event.detail = params;
+ }
+
+ element.dispatchEvent(event);
+ };
+
+ /**
+ * Once listener. Add EventListener to the Element and auto-remove it after the event was dispatched.
+ *
+ * @param {HTMLElement} element DOM element
+ * @param {String} name The event name
+ * @param {Function} callback The event callback
+ *
+ * @since 4.0.0
+ */
+ Joomla.Event.listenOnce = function (element, name, callback) {
+ var onceCallback = function(event){
+ element.removeEventListener(name, onceCallback);
+ return callback.call(element, event)
+ };
+
+ element.addEventListener(name, onceCallback);
+ };
+})( window, Joomla );
+
+/**
+ * Load any web components and any polyfills required
+ */
+document.addEventListener('DOMContentLoaded', function() {
+ Joomla.WebComponents();
+});
diff --git a/components/com_content/Model/ArchiveModel.php b/components/com_content/Model/ArchiveModel.php
index 18ae3f54e852e..6f35f427c0e34 100644
--- a/components/com_content/Model/ArchiveModel.php
+++ b/components/com_content/Model/ArchiveModel.php
@@ -10,6 +10,7 @@
defined('_JEXEC') or die;
+use Joomla\CMS\Factory;
use Joomla\Component\Content\Site\Helper\QueryHelper;
use Joomla\Component\Content\Site\Model\ArticlesModel;
use Joomla\Utilities\ArrayHelper;
@@ -48,10 +49,10 @@ protected function populateState($ordering = null, $direction = null)
$app = Factory::getApplication();
// Add archive properties
- $params = $this->state->params;
+ $params = $this->state->get('params');
// Filter on archived articles
- $this->setState('filter.published', 2);
+ $this->setState('filter.condition', 1);
// Filter on month, year
$this->setState('filter.month', $app->input->getInt('month'));
@@ -62,7 +63,7 @@ protected function populateState($ordering = null, $direction = null)
// Get list limit
$itemid = $app->input->get('Itemid', 0, 'int');
- $limit = $app->getUserStateFromRequest('com_content.archive.list' . $itemid . '.limit', 'limit', $params->get('display_num'), 'uint');
+ $limit = $app->getUserStateFromRequest('com_content.archive.list' . $itemid . '.limit', 'limit', $params->get('display_num', 20), 'uint');
$this->setState('list.limit', $limit);
// Set the archive ordering
@@ -89,9 +90,15 @@ protected function getListQuery()
$app = Factory::getApplication();
$catids = $app->input->getVar('catid', array());
$catids = array_values(array_diff($catids, array('')));
+ $states = $app->input->getVar('state', array());
+ $states = array_values(array_diff($states, array('')));
+
$articleOrderDate = $params->get('order_date');
+ $this->setState('filter.condition', false);
+
// Create a new query object.
+ $db = $this->getDbo();
$query = parent::getListQuery();
// Add routing for archive
@@ -117,6 +124,11 @@ protected function getListQuery()
$query->where('c.id IN (' . implode(', ', $catids) . ')');
}
+ if (count($states) > 0)
+ {
+ $query->where($db->quoteName('state_id') . ' IN (' . implode(', ', $states) . ')');
+ }
+
return $query;
}
@@ -189,10 +201,10 @@ public function getYears()
$nowDate = $db->quote(Factory::getDate()->toSql());
$query = $db->getQuery(true);
- $years = $query->year($db->qn('created'));
+ $years = $query->year($db->quoteName('created'));
$query->select('DISTINCT (' . $years . ')')
- ->from($db->qn('#__content'))
- ->where($db->qn('state') . '= 2')
+ ->from($db->quoteName('#__content'))
+ ->where($db->quoteName('state') . '= 3')
->where('(publish_up = ' . $nullDate . ' OR publish_up <= ' . $nowDate . ')')
->where('(publish_down = ' . $nullDate . ' OR publish_down >= ' . $nowDate . ')')
->order('1 ASC');
diff --git a/components/com_content/Model/ArticleModel.php b/components/com_content/Model/ArticleModel.php
index 33a85f09b7751..a51b194a5ea6e 100644
--- a/components/com_content/Model/ArticleModel.php
+++ b/components/com_content/Model/ArticleModel.php
@@ -61,7 +61,6 @@ protected function populateState()
if ((!$user->authorise('core.edit.state', 'com_content')) && (!$user->authorise('core.edit', 'com_content')))
{
$this->setState('filter.published', 1);
- $this->setState('filter.archived', 2);
}
$this->setState('filter.language', Multilanguage::isEnabled());
@@ -105,6 +104,13 @@ public function getItem($pk = null)
$query->from('#__content AS a')
->where('a.id = ' . (int) $pk);
+ $query ->select($db->quoteName('ws.condition'))
+ ->innerJoin($db->quoteName('#__workflow_states', 'ws'))
+ ->innerJoin($db->quoteName('#__workflow_associations', 'wa'))
+ ->where($db->quoteName('a.id') . ' = ' . $db->quoteName('wa.item_id'))
+ ->where($db->quoteName('wa.extension') . ' = ' . $db->quote('com_content'))
+ ->where($db->quoteName('wa.state_id') . ' = ' . $db->quoteName('ws.id'));
+
// Join on category table.
$query->select('c.title AS category_title, c.alias AS category_alias, c.access AS category_access')
->innerJoin('#__categories AS c on c.id = a.catid')
@@ -142,13 +148,13 @@ public function getItem($pk = null)
// Filter by published state.
$published = $this->getState('filter.published');
- $archived = $this->getState('filter.archived');
if (is_numeric($published))
{
- $query->where('(a.state = ' . (int) $published . ' OR a.state =' . (int) $archived . ')');
+ $query->where($db->quoteName('ws.condition') . ' = ' . $db->quote((int) $published));
}
+
$db->setQuery($query);
$data = $db->loadObject();
@@ -159,7 +165,7 @@ public function getItem($pk = null)
}
// Check for published state if filter set.
- if ((is_numeric($published) || is_numeric($archived)) && (($data->state != $published) && ($data->state != $archived)))
+ if (is_numeric($published) && $data->condition != $published)
{
throw new \Exception(Text::_('COM_CONTENT_ERROR_ARTICLE_NOT_FOUND'), 404);
}
diff --git a/components/com_content/Model/ArticlesModel.php b/components/com_content/Model/ArticlesModel.php
index 0578f358e9e48..bca7bfe227530 100644
--- a/components/com_content/Model/ArticlesModel.php
+++ b/components/com_content/Model/ArticlesModel.php
@@ -19,6 +19,7 @@
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;
+use Joomla\CMS\Workflow\Workflow;
use Joomla\CMS\Helper\TagsHelper;
use Joomla\CMS\Factory;
@@ -49,6 +50,7 @@ public function __construct($config = array())
'checked_out_time', 'a.checked_out_time',
'catid', 'a.catid', 'category_title',
'state', 'a.state',
+ 'state_condition', 'ws.condition',
'access', 'a.access', 'access_level',
'created', 'a.created',
'created_by', 'a.created_by',
@@ -122,7 +124,7 @@ protected function populateState($ordering = 'ordering', $direction = 'ASC')
if ((!$user->authorise('core.edit.state', 'com_content')) && (!$user->authorise('core.edit', 'com_content')))
{
// Filter on published for those who do not have edit or edit.state rights.
- $this->setState('filter.published', 1);
+ $this->setState('filter.condition', Workflow::PUBLISHED);
}
$this->setState('filter.language', Multilanguage::isEnabled());
@@ -156,7 +158,7 @@ protected function populateState($ordering = 'ordering', $direction = 'ASC')
protected function getStoreId($id = '')
{
// Compile the store id.
- $id .= ':' . serialize($this->getState('filter.published'));
+ $id .= ':' . serialize($this->getState('filter.condition'));
$id .= ':' . $this->getState('filter.access');
$id .= ':' . $this->getState('filter.featured');
$id .= ':' . serialize($this->getState('filter.article_id'));
@@ -198,11 +200,11 @@ protected function getListQuery()
$this->getState(
'list.select',
'a.id, a.title, a.alias, a.introtext, a.fulltext, ' .
- 'a.checked_out, a.checked_out_time, ' .
+ 'a.checked_out, a.checked_out_time,' .
'a.catid, a.created, a.created_by, a.created_by_alias, ' .
// Published/archived article in archive category is treats as archive article
// If category is not published then force 0
- 'CASE WHEN c.published = 2 AND a.state > 0 THEN 2 WHEN c.published != 1 THEN 0 ELSE a.state END as state,' .
+ 'CASE WHEN c.published = 2 AND ws.condition > 2 THEN 3 WHEN c.published != 1 THEN 1 ELSE ws.condition END as state,' .
// Use created if modified is 0
'CASE WHEN a.modified = ' . $db->quote($db->getNullDate()) . ' THEN a.created ELSE a.modified END as modified, ' .
'a.modified_by, uam.name as modified_by_name,' .
@@ -237,6 +239,14 @@ protected function getListQuery()
$query->join('LEFT', '#__content_frontpage AS fp ON fp.content_id = a.id');
}
+ // Join over the states.
+ $query->select('wa.state_id AS state_id')
+ ->join('LEFT', '#__workflow_associations AS wa ON wa.item_id = a.id');
+
+ // Join over the states.
+ $query->select('ws.title AS state_title, ws.condition AS state_condition')
+ ->join('LEFT', '#__workflow_states AS ws ON ws.id = wa.state_id');
+
// Join over the categories.
$query->select('c.title AS category_title, c.path AS category_route, c.access AS category_access, c.alias AS category_alias')
->select('c.published, c.published AS parents_published, c.lft')
@@ -261,7 +271,7 @@ protected function getListQuery()
}
// Filter by access level.
- if ($this->getState('filter.access', true))
+ if ($this->getState('filter.access', true))
{
$groups = implode(',', $user->getAuthorisedViewLevels());
$query->where('a.access IN (' . $groups . ')')
@@ -269,9 +279,9 @@ protected function getListQuery()
}
// Filter by published state
- $published = $this->getState('filter.published');
+ $condition = $this->getState('filter.condition');
- if (is_numeric($published) && $published == 2)
+ if (is_numeric($condition) && $condition == 2)
{
/**
* If category is archived then article has to be published or archived.
@@ -279,18 +289,24 @@ protected function getListQuery()
*/
$query->where('((c.published = 2 AND a.state > 0) OR (c.published = 1 AND a.state = 2))');
}
- elseif (is_numeric($published))
+ elseif (is_numeric($condition))
{
// Category has to be published
- $query->where('c.published = 1 AND a.state = ' . (int) $published);
+ $query->where("c.published = 1 AND ws.condition = " . $db->quote($condition));
}
- elseif (is_array($published))
+ elseif (is_array($condition))
{
- $published = ArrayHelper::toInteger($published);
- $published = implode(',', $published);
+ $condition = array_map(
+ function ($data) use ($db)
+ {
+ return $db->quote($data);
+ },
+ $condition
+ );
+ $condition = implode(',', $condition);
// Category has to be published
- $query->where('c.published = 1 AND a.state IN (' . $published . ')');
+ $query->where('c.published = 1 AND ws.condition IN (' . $condition . ')');
}
// Filter by featured state
diff --git a/components/com_content/Model/CategoriesModel.php b/components/com_content/Model/CategoriesModel.php
index bf75abfbdf634..022d7feb95217 100644
--- a/components/com_content/Model/CategoriesModel.php
+++ b/components/com_content/Model/CategoriesModel.php
@@ -62,7 +62,7 @@ protected function populateState($ordering = null, $direction = null)
$params = $app->getParams();
$this->setState('params', $params);
- $this->setState('filter.published', 1);
+ $this->setState('filter.condition', 1);
$this->setState('filter.access', true);
}
@@ -81,7 +81,7 @@ protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('filter.extension');
- $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . $this->getState('filter.condition');
$id .= ':' . $this->getState('filter.access');
$id .= ':' . $this->getState('filter.parentId');
diff --git a/components/com_content/Model/CategoryModel.php b/components/com_content/Model/CategoryModel.php
index e519b1ae66aca..efe7bd3948a11 100644
--- a/components/com_content/Model/CategoryModel.php
+++ b/components/com_content/Model/CategoryModel.php
@@ -147,11 +147,11 @@ protected function populateState($ordering = null, $direction = null)
if ((!$user->authorise('core.edit.state', $asset)) && (!$user->authorise('core.edit', $asset)))
{
// Limit to published for people who can't edit or edit.state.
- $this->setState('filter.published', 1);
+ $this->setState('filter.condition', 1);
}
else
{
- $this->setState('filter.published', array(0, 1, 2));
+ $this->setState('filter.condition', array(0, 1));
}
// Process show_noauth parameter
@@ -240,7 +240,7 @@ public function getItems()
$model = new ArticlesModel(array('ignore_request' => true));
$model->setState('params', Factory::getApplication()->getParams());
$model->setState('filter.category_id', $category->id);
- $model->setState('filter.published', $this->getState('filter.published'));
+ $model->setState('filter.condition', $this->getState('filter.condition'));
$model->setState('filter.access', $this->getState('filter.access'));
$model->setState('filter.language', $this->getState('filter.language'));
$model->setState('filter.featured', $this->getState('filter.featured'));
diff --git a/components/com_content/Model/FeaturedModel.php b/components/com_content/Model/FeaturedModel.php
index 3c3d1dbafc295..71a32bc535618 100644
--- a/components/com_content/Model/FeaturedModel.php
+++ b/components/com_content/Model/FeaturedModel.php
@@ -11,6 +11,7 @@
defined('_JEXEC') or die;
use Joomla\Component\Content\Site\Helper\QueryHelper;
+use Joomla\CMS\Workflow\Workflow;
use Joomla\Registry\Registry;
use Joomla\CMS\Factory;
@@ -76,11 +77,11 @@ protected function populateState($ordering = null, $direction = null)
if ((!$user->authorise('core.edit.state', 'com_content')) && (!$user->authorise('core.edit', 'com_content')))
{
// Filter on published for those who do not have edit or edit.state rights.
- $this->setState('filter.published', 1);
+ $this->setState('filter.condition', Workflow::PUBLISHED);
}
else
{
- $this->setState('filter.published', array(0, 1, 2));
+ $this->setState('filter.condition', array(Workflow::UNPUBLISHED, Workflow::PUBLISHED));
}
// Process show_noauth parameter
diff --git a/components/com_content/forms/article.xml b/components/com_content/forms/article.xml
index d0c8005d38ad0..23af7bba2c42f 100644
--- a/components/com_content/forms/article.xml
+++ b/components/com_content/forms/article.xml
@@ -58,18 +58,12 @@
/>
-
-
-
-
+ name="transition"
+ type="transition"
+ component="com_content"
+ addfieldprefix="Joomla\Component\Workflow\Administrator\Field"
+ label="COM_CONTENT_STATE"
+ >
+
+
+
+
diff --git a/components/com_content/tmpl/article/default.php b/components/com_content/tmpl/article/default.php
index e085661caa505..bdb6157e37651 100644
--- a/components/com_content/tmpl/article/default.php
+++ b/components/com_content/tmpl/article/default.php
@@ -59,8 +59,8 @@
escape($this->item->title); ?>
- item->state == 0) : ?>
-
+ item->condition == 2) : ?>
+
item->publish_up) > strtotime(Factory::getDate())) : ?>
diff --git a/components/com_content/tmpl/category/blog_item.php b/components/com_content/tmpl/category/blog_item.php
index 43f513857a213..5325f3df9d205 100644
--- a/components/com_content/tmpl/category/blog_item.php
+++ b/components/com_content/tmpl/category/blog_item.php
@@ -30,8 +30,8 @@
item); ?>
- item->state == 0 || strtotime($this->item->publish_up) > strtotime(Factory::getDate())
- || ((strtotime($this->item->publish_down) < strtotime(Factory::getDate())) && $this->item->publish_down != Factory::getDbo()->getNullDate())) : ?>
+ item->condition == 3 || strtotime($this->item->publish_up) > strtotime(Factory::getDate())
+ || ((strtotime($this->item->publish_down) < strtotime(JFactory::getDate())) && $this->item->publish_down != Factory::getDbo()->getNullDate())) : ?>
@@ -84,7 +84,7 @@
- item->state == 0 || strtotime($this->item->publish_up) > strtotime(Factory::getDate())
+ item->condition == 2 || strtotime($this->item->publish_up) > strtotime(Factory::getDate())
|| ((strtotime($this->item->publish_down) < strtotime(Factory::getDate())) && $this->item->publish_down != Factory::getDbo()->getNullDate())) : ?>
diff --git a/components/com_content/tmpl/category/default_articles.php b/components/com_content/tmpl/category/default_articles.php
index 7ba2f4d3cdfab..bf27bea0c2e9f 100644
--- a/components/com_content/tmpl/category/default_articles.php
+++ b/components/com_content/tmpl/category/default_articles.php
@@ -152,7 +152,7 @@
items as $i => $article) : ?>
- items[$i]->state == 0) : ?>
+ items[$i]->condition == 2) : ?>
@@ -197,8 +197,8 @@
- state == 0) : ?>
-
+ condition == 2) : ?>
+
diff --git a/components/com_content/tmpl/form/edit.php b/components/com_content/tmpl/form/edit.php
index 0c4ab206a0a8e..c4ba4c68a8e5f 100644
--- a/components/com_content/tmpl/form/edit.php
+++ b/components/com_content/tmpl/form/edit.php
@@ -108,8 +108,10 @@
get('show_publishing_options', 1) == 1) : ?>
form->renderField('created_by_alias'); ?>
+ item->id > 0) : ?>
+ form->renderField('transition'); ?>
+
item->params->get('access-change')) : ?>
- form->renderField('state'); ?>
form->renderField('featured'); ?>
get('show_publishing_options', 1) == 1) : ?>
form->renderField('publish_up'); ?>
diff --git a/installation/sql/mysql/joomla.sql b/installation/sql/mysql/joomla.sql
index 6833595de621c..3583b9bcc54ad 100644
--- a/installation/sql/mysql/joomla.sql
+++ b/installation/sql/mysql/joomla.sql
@@ -31,59 +31,68 @@ CREATE TABLE IF NOT EXISTS `#__assets` (
--
INSERT INTO `#__assets` (`id`, `parent_id`, `lft`, `rgt`, `level`, `name`, `title`, `rules`) VALUES
-(1, 0, 0, 105, 0, 'root.1', 'Root Asset', '{"core.login.site":{"6":1,"2":1},"core.login.admin":{"6":1},"core.login.offline":{"6":1},"core.admin":{"8":1},"core.manage":{"7":1},"core.create":{"6":1,"3":1},"core.delete":{"6":1},"core.edit":{"6":1,"4":1},"core.edit.state":{"6":1,"5":1},"core.edit.own":{"6":1,"3":1}}'),
+(1, 0, 0, 123, 0, 'root.1', 'Root Asset', '{\"core.login.site\":{\"6\":1,\"2\":1},\"core.login.admin\":{\"6\":1},\"core.login.offline\":{\"6\":1},\"core.admin\":{\"8\":1},\"core.manage\":{\"7\":1},\"core.create\":{\"6\":1,\"3\":1},\"core.delete\":{\"6\":1},\"core.edit\":{\"6\":1,\"4\":1},\"core.edit.state\":{\"6\":1,\"5\":1},\"core.edit.own\":{\"6\":1,\"3\":1}}'),
(2, 1, 1, 2, 1, 'com_admin', 'com_admin', '{}'),
-(3, 1, 3, 6, 1, 'com_banners', 'com_banners', '{"core.admin":{"7":1},"core.manage":{"6":1}}'),
-(4, 1, 7, 8, 1, 'com_cache', 'com_cache', '{"core.admin":{"7":1},"core.manage":{"7":1}}'),
-(5, 1, 9, 10, 1, 'com_checkin', 'com_checkin', '{"core.admin":{"7":1},"core.manage":{"7":1}}'),
+(3, 1, 3, 6, 1, 'com_banners', 'com_banners', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"6\":1}}'),
+(4, 1, 7, 8, 1, 'com_cache', 'com_cache', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"7\":1}}'),
+(5, 1, 9, 10, 1, 'com_checkin', 'com_checkin', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"7\":1}}'),
(6, 1, 11, 12, 1, 'com_config', 'com_config', '{}'),
-(7, 1, 13, 16, 1, 'com_contact', 'com_contact', '{"core.admin":{"7":1},"core.manage":{"6":1}}'),
-(8, 1, 17, 20, 1, 'com_content', 'com_content', '{"core.admin":{"7":1},"core.manage":{"6":1},"core.create":{"3":1},"core.edit":{"4":1},"core.edit.state":{"5":1}}'),
-(9, 1, 21, 22, 1, 'com_cpanel', 'com_cpanel', '{}'),
-(10, 1, 23, 24, 1, 'com_installer', 'com_installer', '{"core.manage":{"7":0},"core.delete":{"7":0},"core.edit.state":{"7":0}}'),
-(11, 1, 25, 26, 1, 'com_languages', 'com_languages', '{"core.admin":{"7":1}}'),
-(12, 1, 27, 28, 1, 'com_login', 'com_login', '{}'),
-(13, 1, 29, 30, 1, 'com_mailto', 'com_mailto', '{}'),
-(14, 1, 31, 32, 1, 'com_massmail', 'com_massmail', '{}'),
-(15, 1, 33, 34, 1, 'com_media', 'com_media', '{"core.admin":{"7":1},"core.manage":{"6":1},"core.create":{"3":1},"core.delete":{"5":1}}'),
-(16, 1, 35, 38, 1, 'com_menus', 'com_menus', '{"core.admin":{"7":1}}'),
-(17, 1, 39, 40, 1, 'com_messages', 'com_messages', '{"core.admin":{"7":1},"core.manage":{"7":1}}'),
-(18, 1, 41, 74, 1, 'com_modules', 'com_modules', '{"core.admin":{"7":1}}'),
-(19, 1, 75, 78, 1, 'com_newsfeeds', 'com_newsfeeds', '{"core.admin":{"7":1},"core.manage":{"6":1}}'),
-(20, 1, 79, 80, 1, 'com_plugins', 'com_plugins', '{"core.admin":{"7":1}}'),
-(21, 1, 81, 82, 1, 'com_redirect', 'com_redirect', '{"core.admin":{"7":1}}'),
-(22, 1, 83, 84, 1, 'com_search', 'com_search', '{"core.admin":{"7":1},"core.manage":{"6":1}}'),
-(23, 1, 85, 86, 1, 'com_templates', 'com_templates', '{"core.admin":{"7":1}}'),
-(24, 1, 87, 90, 1, 'com_users', 'com_users', '{"core.admin":{"7":1}}'),
-(26, 1, 91, 92, 1, 'com_wrapper', 'com_wrapper', '{}'),
+(7, 1, 13, 16, 1, 'com_contact', 'com_contact', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"6\":1}}'),
+(8, 1, 17, 38, 1, 'com_content', 'com_content', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"6\":1},\"core.create\":{\"3\":1},\"core.edit\":{\"4\":1},\"core.edit.state\":{\"5\":1},\"core.execute.transition\":{\"6\":1,\"5\":1}}'),
+(9, 1, 39, 40, 1, 'com_cpanel', 'com_cpanel', '{}'),
+(10, 1, 41, 42, 1, 'com_installer', 'com_installer', '{\"core.manage\":{\"7\":0},\"core.delete\":{\"7\":0},\"core.edit.state\":{\"7\":0}}'),
+(11, 1, 43, 44, 1, 'com_languages', 'com_languages', '{\"core.admin\":{\"7\":1}}'),
+(12, 1, 45, 46, 1, 'com_login', 'com_login', '{}'),
+(13, 1, 47, 48, 1, 'com_mailto', 'com_mailto', '{}'),
+(14, 1, 49, 50, 1, 'com_massmail', 'com_massmail', '{}'),
+(15, 1, 51, 52, 1, 'com_media', 'com_media', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"6\":1},\"core.create\":{\"3\":1},\"core.delete\":{\"5\":1}}'),
+(16, 1, 53, 56, 1, 'com_menus', 'com_menus', '{\"core.admin\":{\"7\":1}}'),
+(17, 1, 57, 58, 1, 'com_messages', 'com_messages', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"7\":1}}'),
+(18, 1, 59, 92, 1, 'com_modules', 'com_modules', '{\"core.admin\":{\"7\":1}}'),
+(19, 1, 93, 96, 1, 'com_newsfeeds', 'com_newsfeeds', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"6\":1}}'),
+(20, 1, 97, 98, 1, 'com_plugins', 'com_plugins', '{\"core.admin\":{\"7\":1}}'),
+(21, 1, 99, 100, 1, 'com_redirect', 'com_redirect', '{\"core.admin\":{\"7\":1}}'),
+(22, 1, 101, 102, 1, 'com_search', 'com_search', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"6\":1}}'),
+(23, 1, 103, 104, 1, 'com_templates', 'com_templates', '{\"core.admin\":{\"7\":1}}'),
+(24, 1, 105, 108, 1, 'com_users', 'com_users', '{\"core.admin\":{\"7\":1}}'),
+(26, 1, 109, 110, 1, 'com_wrapper', 'com_wrapper', '{}'),
(27, 8, 18, 19, 2, 'com_content.category.2', 'Uncategorised', '{}'),
(28, 3, 4, 5, 2, 'com_banners.category.3', 'Uncategorised', '{}'),
(29, 7, 14, 15, 2, 'com_contact.category.4', 'Uncategorised', '{}'),
-(30, 19, 76, 77, 2, 'com_newsfeeds.category.5', 'Uncategorised', '{}'),
-(32, 24, 88, 89, 2, 'com_users.category.7', 'Uncategorised', '{}'),
-(33, 1, 93, 94, 1, 'com_finder', 'com_finder', '{"core.admin":{"7":1},"core.manage":{"6":1}}'),
-(34, 1, 95, 96, 1, 'com_joomlaupdate', 'com_joomlaupdate', '{}'),
-(35, 1, 97, 98, 1, 'com_tags', 'com_tags', '{}'),
-(36, 1, 99, 100, 1, 'com_contenthistory', 'com_contenthistory', '{}'),
-(37, 1, 101, 102, 1, 'com_ajax', 'com_ajax', '{}'),
-(38, 1, 103, 104, 1, 'com_postinstall', 'com_postinstall', '{}'),
-(39, 18, 42, 43, 2, 'com_modules.module.1', 'Main Menu', '{}'),
-(40, 18, 44, 45, 2, 'com_modules.module.2', 'Login', '{}'),
-(41, 18, 46, 47, 2, 'com_modules.module.3', 'Popular Articles', '{}'),
-(42, 18, 48, 49, 2, 'com_modules.module.4', 'Recently Added Articles', '{}'),
-(43, 18, 50, 51, 2, 'com_modules.module.8', 'Toolbar', '{}'),
-(44, 18, 52, 53, 2, 'com_modules.module.9', 'Quick Icons', '{}'),
-(45, 18, 54, 55, 2, 'com_modules.module.10', 'Logged-in Users', '{}'),
-(46, 18, 56, 57, 2, 'com_modules.module.12', 'Admin Menu', '{}'),
-(47, 18, 58, 59, 2, 'com_modules.module.13', 'Admin Submenu', '{}'),
-(48, 18, 60, 61, 2, 'com_modules.module.14', 'User Status', '{}'),
-(49, 18, 62, 63, 2, 'com_modules.module.15', 'Title', '{}'),
-(50, 18, 64, 65, 2, 'com_modules.module.16', 'Login Form', '{}'),
-(51, 18, 66, 67, 2, 'com_modules.module.17', 'Breadcrumbs', '{}'),
-(52, 18, 68, 69, 2, 'com_modules.module.79', 'Multilanguage status', '{}'),
-(53, 18, 70, 71, 2, 'com_modules.module.86', 'Joomla Version', '{}'),
-(54, 16, 36, 37, 2, 'com_menus.menu.1', 'Main Menu', '{}'),
-(55, 18, 72, 73, 2, 'com_modules.module.87', 'Sample Data', '{}');
+(30, 19, 94, 95, 2, 'com_newsfeeds.category.5', 'Uncategorised', '{}'),
+(32, 24, 106, 107, 2, 'com_users.category.7', 'Uncategorised', '{}'),
+(33, 1, 111, 112, 1, 'com_finder', 'com_finder', '{\"core.admin\":{\"7\":1},\"core.manage\":{\"6\":1}}'),
+(34, 1, 113, 114, 1, 'com_joomlaupdate', 'com_joomlaupdate', '{}'),
+(35, 1, 115, 116, 1, 'com_tags', 'com_tags', '{}'),
+(36, 1, 117, 118, 1, 'com_contenthistory', 'com_contenthistory', '{}'),
+(37, 1, 119, 120, 1, 'com_ajax', 'com_ajax', '{}'),
+(38, 1, 121, 122, 1, 'com_postinstall', 'com_postinstall', '{}'),
+(39, 18, 60, 61, 2, 'com_modules.module.1', 'Main Menu', '{}'),
+(40, 18, 62, 63, 2, 'com_modules.module.2', 'Login', '{}'),
+(41, 18, 64, 65, 2, 'com_modules.module.3', 'Popular Articles', '{}'),
+(42, 18, 66, 67, 2, 'com_modules.module.4', 'Recently Added Articles', '{}'),
+(43, 18, 68, 69, 2, 'com_modules.module.8', 'Toolbar', '{}'),
+(44, 18, 70, 71, 2, 'com_modules.module.9', 'Quick Icons', '{}'),
+(45, 18, 72, 73, 2, 'com_modules.module.10', 'Logged-in Users', '{}'),
+(46, 18, 74, 75, 2, 'com_modules.module.12', 'Admin Menu', '{}'),
+(47, 18, 76, 77, 2, 'com_modules.module.13', 'Admin Submenu', '{}'),
+(48, 18, 78, 79, 2, 'com_modules.module.14', 'User Status', '{}'),
+(49, 18, 80, 81, 2, 'com_modules.module.15', 'Title', '{}'),
+(50, 18, 82, 83, 2, 'com_modules.module.16', 'Login Form', '{}'),
+(51, 18, 84, 85, 2, 'com_modules.module.17', 'Breadcrumbs', '{}'),
+(52, 18, 86, 87, 2, 'com_modules.module.79', 'Multilanguage status', '{}'),
+(53, 18, 88, 89, 2, 'com_modules.module.86', 'Joomla Version', '{}'),
+(54, 16, 54, 55, 2, 'com_menus.menu.1', 'Main Menu', '{}'),
+(55, 18, 90, 91, 2, 'com_modules.module.87', 'Sample Data', '{}'),
+(56, 8, 20, 37, 2, 'com_content.workflow.1', 'Joomla! Default', '{}'),
+(57, 56, 21, 22, 3, 'com_content.state.1', 'Unpublished', '{}'),
+(58, 56, 23, 24, 3, 'com_content.state.2', 'Published', '{}'),
+(59, 56, 25, 26, 3, 'com_content.state.3', 'Trashed', '{}'),
+(60, 56, 27, 28, 3, 'com_content.state.4', 'Archived', '{}'),
+(61, 56, 29, 30, 3, 'com_content.transition.1', 'Publish', '{}'),
+(62, 56, 31, 32, 3, 'com_content.transition.2', 'Unpublish', '{}'),
+(63, 56, 33, 34, 3, 'com_content.transition.3', 'Archive', '{}'),
+(64, 56, 35, 36, 3, 'com_content.transition.4', 'Trash', '{}');
-- --------------------------------------------------------
@@ -241,7 +250,7 @@ CREATE TABLE IF NOT EXISTS `#__categories` (
INSERT INTO `#__categories` (`id`, `asset_id`, `parent_id`, `lft`, `rgt`, `level`, `path`, `extension`, `title`, `alias`, `note`, `description`, `published`, `checked_out`, `checked_out_time`, `access`, `params`, `metadesc`, `metakey`, `metadata`, `created_user_id`, `created_time`, `modified_user_id`, `modified_time`, `hits`, `language`, `version`) VALUES
(1, 0, 0, 0, 11, 0, '', 'system', 'ROOT', 'root', '', '', 1, 0, '0000-00-00 00:00:00', 1, '{}', '', '', '{}', 42, '2011-01-01 00:00:01', 0, '0000-00-00 00:00:00', 0, '*', 1),
-(2, 27, 1, 1, 2, 1, 'uncategorised', 'com_content', 'Uncategorised', 'uncategorised', '', '', 1, 0, '0000-00-00 00:00:00', 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, '2011-01-01 00:00:01', 0, '0000-00-00 00:00:00', 0, '*', 1),
+(2, 27, 1, 1, 2, 1, 'uncategorised', 'com_content', 'Uncategorised', 'uncategorised', '', '', 1, 0, '0000-00-00 00:00:00', 1, '{"category_layout":"","image":"","workflow_id":"1"}', '', '', '{"author":"","robots":""}', 42, '2011-01-01 00:00:01', 0, '0000-00-00 00:00:00', 0, '*', 1),
(3, 28, 1, 3, 4, 1, 'uncategorised', 'com_banners', 'Uncategorised', 'uncategorised', '', '', 1, 0, '0000-00-00 00:00:00', 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, '2011-01-01 00:00:01', 0, '0000-00-00 00:00:00', 0, '*', 1),
(4, 29, 1, 5, 6, 1, 'uncategorised', 'com_contact', 'Uncategorised', 'uncategorised', '', '', 1, 0, '0000-00-00 00:00:00', 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, '2011-01-01 00:00:01', 0, '0000-00-00 00:00:00', 0, '*', 1),
(5, 30, 1, 7, 8, 1, 'uncategorised', 'com_newsfeeds', 'Uncategorised', 'uncategorised', '', '', 1, 0, '0000-00-00 00:00:00', 1, '{"category_layout":"","image":""}', '', '', '{"author":"","robots":""}', 42, '2011-01-01 00:00:01', 0, '0000-00-00 00:00:00', 0, '*', 1),
@@ -321,7 +330,7 @@ CREATE TABLE IF NOT EXISTS `#__content` (
`alias` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`introtext` mediumtext NOT NULL,
`fulltext` mediumtext NOT NULL,
- `state` tinyint(3) NOT NULL DEFAULT 0,
+ `state` int(10) NOT NULL DEFAULT 0,
`catid` int(10) unsigned NOT NULL DEFAULT 0,
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`created_by` int(10) unsigned NOT NULL DEFAULT 0,
@@ -536,7 +545,8 @@ INSERT INTO `#__extensions` (`extension_id`, `package_id`, `name`, `type`, `elem
(32, 0, 'com_postinstall', 'component', 'com_postinstall', '', 1, 1, 1, 1, '', '', 0, '0000-00-00 00:00:00', 0, 0, 'Joomla\\Component\\Postinstall'),
(33, 0, 'com_fields', 'component', 'com_fields', '', 1, 1, 1, 0, '', '', 0, '0000-00-00 00:00:00', 0, 0, 'Joomla\\Component\\Fields'),
(34, 0, 'com_associations', 'component', 'com_associations', '', 1, 1, 1, 0, '', '', 0, '0000-00-00 00:00:00', 0, 0, 'Joomla\\Component\\Associations'),
-(35, 0, 'com_csp', 'component', 'com_csp', '', 1, 1, 1, 0, '', '', 0, '0000-00-00 00:00:00', 0, 0, 'Joomla\\Component\\Csp'),
+(35, 0, 'com_workflow', 'component', 'com_workflow', '', 1, 1, 0, 1, '', '{}', 0, '0000-00-00 00:00:00', 0, 0, 'Joomla\\Component\\Workflow'),
+(36, 0, 'com_csp', 'component', 'com_csp', '', 1, 1, 1, 0, '', '', 0, '0000-00-00 00:00:00', 0, 0, 'Joomla\\Component\\Csp'),
(103, 0, 'Joomla! Platform', 'library', 'joomla', '', 0, 1, 1, 1, '', '', 0, '0000-00-00 00:00:00', 0, 0, ''),
(106, 0, 'PHPass', 'library', 'phpass', '', 0, 1, 1, 1, '', '', 0, '0000-00-00 00:00:00', 0, 0, ''),
(200, 0, 'mod_articles_archive', 'module', 'mod_articles_archive', '', 0, 1, 1, 0, '', '', 0, '0000-00-00 00:00:00', 0, 0, 'Joomla\\Module\\ArticlesArchive'),
@@ -671,7 +681,6 @@ INSERT INTO `#__extensions` (`extension_id`, `package_id`, `name`, `type`, `elem
(601, 802, 'English (en-GB)', 'language', 'en-GB', '', 1, 1, 1, 1, '', '', 0, '0000-00-00 00:00:00', 0, 0, ''),
(700, 0, 'files_joomla', 'file', 'joomla', '', 0, 1, 1, 1, '', '', 0, '0000-00-00 00:00:00', 0, 0, ''),
(802, 0, 'English (en-GB) Language Pack', 'package', 'pkg_en-GB', '', 0, 1, 1, 1, '', '', 0, '0000-00-00 00:00:00', 0, 0, '');
-
-- --------------------------------------------------------
--
@@ -1923,3 +1932,117 @@ INSERT INTO `#__viewlevels` (`id`, `title`, `ordering`, `rules`) VALUES
(3, 'Special', 3, '[6,3,8]'),
(5, 'Guest', 1, '[9]'),
(6, 'Super Users', 4, '[8]');
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `#__workflows`
+--
+
+CREATE TABLE IF NOT EXISTS `#__workflows` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `asset_id` int(10) DEFAULT 0,
+ `published` tinyint(1) NOT NULL DEFAULT 0,
+ `title` varchar(255) NOT NULL,
+ `description` text NOT NULL DEFAULT '',
+ `extension` varchar(255) NOT NULL,
+ `default` tinyint(1) NOT NULL DEFAULT 0,
+ `ordering` int(11) NOT NULL DEFAULT 0,
+ `created` datetime NOT NULL DEFAULT NOW(),
+ `created_by` int(10) NOT NULL DEFAULT 0,
+ `modified` datetime NOT NULL DEFAULT NOW(),
+ `modified_by` int(10) NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`),
+ KEY `asset_id` (`asset_id`),
+ KEY `title` (`title`(191)),
+ KEY `extension` (`extension`(191)),
+ KEY `default` (`default`),
+ KEY `created` (`created`),
+ KEY `created_by` (`created_by`),
+ KEY `modified` (`modified`),
+ KEY `modified_by` (`modified_by`)
+) ENGINE=InnoDB COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Dumping data for table `#__workflows`
+--
+
+INSERT INTO `#__workflows` (`id`, `asset_id`, `published`, `title`, `description`, `extension`, `default`, `ordering`, `created`, `created_by`, `modified`, `modified_by`) VALUES
+(1, 56, 1, 'Joomla! Default', '', 'com_content', 1, 1, NOW(), 0, '0000-00-00 00:00:00', 0);
+
+--
+-- Table structure for table `#__workflow_associations`
+--
+
+CREATE TABLE IF NOT EXISTS `#__workflow_associations` (
+ `item_id` int(10) NOT NULL DEFAULT 0 COMMENT 'Extension table id value',
+ `state_id` int(10) NOT NULL COMMENT 'Foreign Key to #__workflow_states.id',
+ `extension` varchar(100) NOT NULL,
+ PRIMARY KEY (`item_id`, `state_id`, `extension`),
+ KEY `idx_item_id` (`item_id`),
+ KEY `idx_state_id` (`state_id`),
+ KEY `idx_extension` (`extension`(100))
+) ENGINE=InnoDB COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Table structure for table `#__workflow_states`
+--
+
+CREATE TABLE IF NOT EXISTS `#__workflow_states` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `asset_id` int(10) DEFAULT 0,
+ `ordering` int(11) NOT NULL DEFAULT 0,
+ `workflow_id` int(10) NOT NULL,
+ `published` tinyint(1) NOT NULL DEFAULT 0,
+ `title` varchar(255) NOT NULL,
+ `description` text NOT NULL DEFAULT '',
+ `condition` enum('0','1','-2') NOT NULL,
+ `default` tinyint(1) NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`),
+ KEY `workflow_id` (`workflow_id`),
+ KEY `title` (`title`(191)),
+ KEY `asset_id` (`asset_id`),
+ KEY `default` (`default`)
+) ENGINE=InnoDB DEFAULT COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Dumping data for table `#__workflow_states`
+--
+
+INSERT INTO `#__workflow_states` (`id`, `asset_id`, `ordering`, `workflow_id`, `published`, `title`, `description`, `condition`, `default`) VALUES
+(1, 57, 1, 1, 1, 'Unpublished', '', '0', 0),
+(2, 58, 2, 1, 1, 'Published', '', '1', 1),
+(3, 59, 3, 1, 1, 'Trashed', '', '-2', 0),
+(4, 60, 4, 1, 1, 'Archived', '', '1', 0);
+
+--
+-- Table structure for table `#__workflow_transitions`
+--
+
+CREATE TABLE IF NOT EXISTS `#__workflow_transitions` (
+ `id` int(10) NOT NULL AUTO_INCREMENT,
+ `asset_id` int(10) DEFAULT 0,
+ `ordering` int(11) NOT NULL DEFAULT 0,
+ `workflow_id` int(10) NOT NULL,
+ `published` tinyint(1) NOT NULL DEFAULT 0,
+ `title` varchar(255) NOT NULL,
+ `description` text NOT NULL DEFAULT '',
+ `from_state_id` int(10) NOT NULL,
+ `to_state_id` int(10) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `title` (`title`(191)),
+ KEY `asset_id` (`asset_id`),
+ KEY `from_state_id` (`from_state_id`),
+ KEY `to_state_id` (`to_state_id`),
+ KEY `workflow_id` (`workflow_id`)
+) ENGINE=InnoDB DEFAULT COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Dumping data for table `#__workflow_transitions`
+--
+
+INSERT INTO `#__workflow_transitions` (`id`, `asset_id`, `published`, `ordering`, `workflow_id`, `title`, `description`, `from_state_id`, `to_state_id`) VALUES
+(1, 61, 1, 1, 1, 'Unpublish', '', -1, 1),
+(2, 62, 1, 2, 1, 'Publish', '', -1, 2),
+(3, 63, 1, 3, 1, 'Trash', '', -1, 3),
+(4, 64, 1, 4, 1, 'Archive', '', -1, 4);
diff --git a/language/en-GB/en-GB.com_content.ini b/language/en-GB/en-GB.com_content.ini
index ed16bec545dfc..2df6991b08b65 100644
--- a/language/en-GB/en-GB.com_content.ini
+++ b/language/en-GB/en-GB.com_content.ini
@@ -84,3 +84,5 @@ COM_CONTENT_TITLE_FILTER_LABEL="Title Filter"
COM_CONTENT_VOTES="Vote"
COM_CONTENT_VOTES_COUNT="Vote: %s"
COM_CONTENT_WRITTEN_BY="Written by %s"
+COM_CONTENT_STATE="State"
+COM_CONTENT_TRANSITION="Transition"
diff --git a/language/en-GB/en-GB.mod_articles_archive.ini b/language/en-GB/en-GB.mod_articles_archive.ini
index 50a2161097d4c..83e4b89246b86 100644
--- a/language/en-GB/en-GB.mod_articles_archive.ini
+++ b/language/en-GB/en-GB.mod_articles_archive.ini
@@ -3,8 +3,8 @@
; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php
; Note : All ini files need to be saved as UTF-8
-MOD_ARTICLES_ARCHIVE="Articles - Archived"
+MOD_ARTICLES_ARCHIVE="Articles - List"
MOD_ARTICLES_ARCHIVE_FIELD_COUNT_LABEL="# of Months"
-MOD_ARTICLES_ARCHIVE_XML_DESCRIPTION="This module shows a list of the calendar months with archived articles. After you have changed the status of an article to archived, this list will be automatically generated."
+MOD_ARTICLES_ARCHIVE_XML_DESCRIPTION="This module shows a list of the calendar months containing articles. This list will be automatically generated from the chosen state."
MOD_ARTICLES_ARCHIVE_DATE="%1$s, %2$s"
-
+MOD_ARTICLES_ARCHIVE_CHOOSE_STATE="Choose State"
diff --git a/language/en-GB/en-GB.mod_articles_archive.sys.ini b/language/en-GB/en-GB.mod_articles_archive.sys.ini
index 08b9c2335b787..8e7d74c800044 100644
--- a/language/en-GB/en-GB.mod_articles_archive.sys.ini
+++ b/language/en-GB/en-GB.mod_articles_archive.sys.ini
@@ -3,7 +3,7 @@
; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php
; Note : All ini files need to be saved as UTF-8
-MOD_ARTICLES_ARCHIVE="Articles - Archived"
-MOD_ARTICLES_ARCHIVE_XML_DESCRIPTION="This module shows a list of the calendar months with Archived Articles. After you have changed the status of an Article to Archived, this list will be automatically generated."
+MOD_ARTICLES_ARCHIVE="Articles - List"
+MOD_ARTICLES_ARCHIVE_XML_DESCRIPTION="This module shows a list of the calendar months containing articles. This list will be automatically generated from the chosen state."
MOD_ARTICLES_ARCHIVE_LAYOUT_DEFAULT="Default"
diff --git a/layouts/joomla/edit/global.php b/layouts/joomla/edit/global.php
index 09117f04edd5d..ad43304264c69 100644
--- a/layouts/joomla/edit/global.php
+++ b/layouts/joomla/edit/global.php
@@ -40,6 +40,11 @@
'version_note',
);
+if ($displayData->get('item')->id !== null)
+{
+ array_unshift($fields, 'transition');
+}
+
$hiddenFields = $displayData->get('hidden_fields') ?: array();
if (!$saveHistory)
diff --git a/layouts/joomla/html/batch/workflowstate.php b/layouts/joomla/html/batch/workflowstate.php
new file mode 100644
index 0000000000000..91f575a9c0856
--- /dev/null
+++ b/layouts/joomla/html/batch/workflowstate.php
@@ -0,0 +1,33 @@
+
+
+
+ 'batch-workflowstate-id',
+ 'group.label' => 'text',
+ 'group.items' => null
+);
+
+$groups = JHtml::_('workflowstate.existing', array('title' => JText::_('JLIB_HTML_BATCH_WORKFLOW_STATE_NOCHANGE')));
+
+echo JHtml::_('select.groupedlist', $groups, 'batch[workflowstate_id]', $attr);
diff --git a/libraries/cms/html/workflowstate.php b/libraries/cms/html/workflowstate.php
new file mode 100644
index 0000000000000..f9099f768f9a8
--- /dev/null
+++ b/libraries/cms/html/workflowstate.php
@@ -0,0 +1,84 @@
+getQuery(true);
+
+ // Build the query.
+ $query->select(
+ $db->quoteName(
+ [
+ 'ws.id',
+ 'ws.title',
+ 'w.id',
+ 'w.title'
+ ],
+ [
+ 'workflow_state_id',
+ 'workflow_state_title',
+ 'workflow_id',
+ 'workflow_title'
+ ]
+ )
+ )
+ ->from('#__workflow_states AS ws')
+ ->leftJoin($db->quoteName('#__workflows', 'w') . ' ON w.id = ws.workflow_id')
+ ->order('ws.ordering');
+
+ // Set the query and load the options.
+ $states = $db->setQuery($query)->loadObjectList();
+
+ $workflowStates = array();
+
+ // Grouping the states by workflow
+ foreach ($states as $state)
+ {
+ // Using workflow ID to differentiate workflows having same title
+ $workflowStateKey = $state->workflow_title . ' (' . $state->workflow_id . ')';
+
+ if (!array_key_exists($workflowStateKey, $workflowStates))
+ {
+ $workflowStates[$workflowStateKey] = array();
+ }
+
+ $workflowStates[$workflowStateKey][] = HTMLHelper::_('select.option', $state->workflow_state_id, $state->workflow_state_title);
+ }
+
+ $prefix[] = array(
+ HTMLHelper::_('select.option', '', $options['title'])
+ );
+
+ return array_merge($prefix, $workflowStates);
+ }
+}
diff --git a/libraries/src/Categories/CategoriesServiceInterface.php b/libraries/src/Categories/CategoriesServiceInterface.php
index 42dd144ae8934..469fc2a48b90a 100644
--- a/libraries/src/Categories/CategoriesServiceInterface.php
+++ b/libraries/src/Categories/CategoriesServiceInterface.php
@@ -10,6 +10,8 @@
defined('JPATH_PLATFORM') or die;
+use Joomla\CMS\Form\Form;
+
/**
* Access to component specific categories.
*
@@ -57,4 +59,14 @@ public function countItems(array $items, string $section);
* @throws \Exception
*/
public function countTagItems(array $items, string $extension);
+
+ /**
+ * Prepares the category form
+ *
+ * @param \Joomla\CMS\Categories\Form $form The form to change
+ * @param array|object $data The form data
+ *
+ * @return void
+ */
+ public function prepareForm(Form $form, $data);
}
diff --git a/libraries/src/Categories/CategoriesServiceTrait.php b/libraries/src/Categories/CategoriesServiceTrait.php
index 953f2703d1bfc..f5f06c537fba1 100644
--- a/libraries/src/Categories/CategoriesServiceTrait.php
+++ b/libraries/src/Categories/CategoriesServiceTrait.php
@@ -10,6 +10,7 @@
defined('JPATH_PLATFORM') or die;
+use Joomla\CMS\Form\Form;
use Joomla\CMS\Factory;
/**
@@ -207,6 +208,18 @@ public function countTagItems(array $items, string $extension)
}
}
+ /**
+ * Prepares the category form
+ *
+ * @param Form $form The form to change
+ * @param array|object $data The form data
+ *
+ * @return void
+ */
+ public function prepareForm(Form $form, $data)
+ {
+ }
+
/**
* Returns the table for the count items functions for the given section.
*
diff --git a/libraries/src/Extension/LegacyComponent.php b/libraries/src/Extension/LegacyComponent.php
index 541f10354811a..c105ff3761ed1 100644
--- a/libraries/src/Extension/LegacyComponent.php
+++ b/libraries/src/Extension/LegacyComponent.php
@@ -13,6 +13,7 @@
use Joomla\CMS\Application\CMSApplicationInterface;
use Joomla\CMS\Categories\Categories;
use Joomla\CMS\Categories\CategoriesServiceInterface;
+use Joomla\CMS\Categories\CategoriesServiceTrait;
use Joomla\CMS\Categories\SectionNotFoundException;
use Joomla\CMS\Dispatcher\DispatcherInterface;
use Joomla\CMS\Dispatcher\LegacyDispatcher;
@@ -30,6 +31,8 @@
*/
class LegacyComponent implements ComponentInterface, MVCFactoryServiceInterface, CategoriesServiceInterface, FieldsServiceInterface
{
+ use CategoriesServiceTrait;
+
/**
* @var string
*
diff --git a/libraries/src/Form/Field/TransitionField.php b/libraries/src/Form/Field/TransitionField.php
new file mode 100644
index 0000000000000..5a29d96278100
--- /dev/null
+++ b/libraries/src/Form/Field/TransitionField.php
@@ -0,0 +1,144 @@
+` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result)
+ {
+ $this->extension = $element['extension'] ?? 'com_content';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of JHtml options.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function getOptions()
+ {
+ // Let's get the id for the current item, either category or content item.
+ $jinput = Factory::getApplication()->input;
+
+ // Initialise variable.
+ $db = Factory::getDbo();
+ $extension = $this->element['extension'] ? (string) $this->element['extension'] : (string) $jinput->get('extension', 'com_content');
+ $workflowState = $this->element['workflow_state'] ? (int) $this->element['workflow_state'] : (int) $jinput->getInt('id', 0);
+
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['t.id', 't.title', 's.condition'], ['value', 'text', 'condition']))
+ ->from($db->quoteName('#__workflow_transitions', 't'))
+ ->from($db->quoteName('#__workflow_states', 's'))
+ ->from($db->quoteName('#__workflow_states', 's2'))
+ ->where($db->quoteName('t.from_state_id') . ' IN(-1, ' . (int) $workflowState . ')')
+ ->where($db->quoteName('t.to_state_id') . ' = ' . $db->quoteName('s.id'))
+ ->where($db->quoteName('t.to_state_id') . ' != ' . (int) $workflowState)
+ ->where($db->quoteName('s.workflow_id') . ' = ' . $db->quoteName('s2.workflow_id'))
+ ->where($db->quoteName('s2.id') . ' = ' . (int) $workflowState)
+ ->where($db->quoteName('t.published') . '= 1')
+ ->where($db->quoteName('s.published') . '= 1')
+ ->order($db->quoteName('t.ordering'));
+
+ $items = $db->setQuery($query)->loadObjectList();
+
+ if (count($items))
+ {
+ $user = Factory::getUser();
+
+ $items = array_filter(
+ $items,
+ function ($item) use ($user, $extension)
+ {
+ return $user->authorise('core.execute.transition', $extension . '.transition.' . $item->value);
+ }
+ );
+
+ // Sort by transition name
+ $items = ArrayHelper::sortObjects($items, 'value', 1, true, true);
+
+ Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR);
+
+ $workflow = new Workflow(['extension' => $this->extension]);
+
+ foreach ($items as $item)
+ {
+ $conditionName = $workflow->getConditionName($item->condition);
+
+ $item->text .= ' [' . \JText::_($conditionName) . ']';
+ }
+ }
+
+ // Get state title
+ $query
+ ->clear()
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__workflow_states'))
+ ->where($db->quoteName('id') . ' = ' . (int) $workflowState);
+
+ $workflowName = $db->setQuery($query)->loadResult();
+
+ $default = [\JHtml::_('select.option', '', $workflowName)];
+
+ $options = array_merge(parent::getOptions(), $items);
+
+ if (count($options))
+ {
+ $default[] = \JHtml::_('select.option', '-1', '--------', ['disable' => true]);
+ }
+
+ // Merge with defaults
+ return array_merge($default, $options);
+ }
+}
diff --git a/libraries/src/Form/Field/WorkflowStateField.php b/libraries/src/Form/Field/WorkflowStateField.php
new file mode 100644
index 0000000000000..3982032f1b8d3
--- /dev/null
+++ b/libraries/src/Form/Field/WorkflowStateField.php
@@ -0,0 +1,134 @@
+` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $success = parent::setup($element, $value, $group);
+
+ if ($success)
+ {
+ if (strlen($element['extension']))
+ {
+ $this->extension = (string) $element['extension'];
+ }
+
+ if ((string) $element['activeonly'] == '1' || (string) $element['activeonly'] == 'true')
+ {
+ $this->activeonly = true;
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * Method to get the field option groups.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since __DEPLOY_VERSION__
+ * @throws \UnexpectedValueException
+ */
+ protected function getGroups()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ // Select distinct states for existing articles
+ $query
+ ->select('DISTINCT ' . $db->quoteName('ws.id', 'workflow_state_id'))
+ ->select($db->quoteName(['ws.title', 'w.title', 'w.id', 'w.ordering'], ['workflow_state_title', 'workflow_title', 'workflow_id', 'ordering']))
+ ->from($db->quoteName('#__workflow_states', 'ws'))
+ ->from($db->quoteName('#__workflows', 'w'))
+ ->where($db->quoteName('ws.workflow_id') . ' = ' . $db->quoteName('w.id'))
+ ->where($db->quoteName('w.extension') . ' = ' . $db->quote($this->extension))
+ ->order($db->quoteName('w.ordering'));
+
+ if ($this->activeonly)
+ {
+ $query
+ ->from($db->quoteName('#__workflow_associations', 'wa'))
+ ->where($db->quoteName('wa.state_id') . ' = ' . $db->quoteName('ws.id'))
+ ->where($db->quoteName('wa.extension') . ' = ' . $db->quote($this->extension));
+
+ }
+
+ $states = $db->setQuery($query)->loadObjectList();
+
+ $workflowStates = array();
+
+ // Grouping the states by workflow
+ foreach ($states as $state)
+ {
+ // Using workflow ID to differentiate workflows having same title
+ $workflowStateKey = $state->workflow_title . ' (' . $state->workflow_id . ')';
+
+ if (!array_key_exists($workflowStateKey, $workflowStates))
+ {
+ $workflowStates[$workflowStateKey] = array();
+ }
+
+ $workflowStates[$workflowStateKey][] = HTMLHelper::_('select.option', $state->workflow_state_id, $state->workflow_state_title);
+ }
+
+ // Merge any additional options in the XML definition.
+ return array_merge(parent::getGroups(), $workflowStates);
+ }
+}
diff --git a/libraries/src/MVC/Controller/AdminController.php b/libraries/src/MVC/Controller/AdminController.php
index 1822bcffa9393..8610a2271456f 100644
--- a/libraries/src/MVC/Controller/AdminController.php
+++ b/libraries/src/MVC/Controller/AdminController.php
@@ -84,6 +84,9 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu
$this->registerTask('orderup', 'reorder');
$this->registerTask('orderdown', 'reorder');
+ // Transition
+ $this->registerTask('runTransition', 'runTransition');
+
// Guess the option as com_NameOfController.
if (empty($this->option))
{
@@ -251,7 +254,7 @@ public function publish()
$ntext = $this->text_prefix . '_N_ITEMS_TRASHED';
}
- if ($ntext !== null)
+ if (count($cid))
{
$this->setMessage(Text::plural($ntext, count($cid)));
}
@@ -413,4 +416,49 @@ public function saveOrderAjax()
// Close the application
$this->app->close();
}
+
+ /**
+ * Method to run Transition by id of item.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function runTransition()
+ {
+ // Get the input
+ $pks = $this->input->post->get('cid', array(), 'array');
+
+ if (!count($pks))
+ {
+ return false;
+ }
+
+ $pk = (int) $pks[0];
+
+ $transitionId = $this->input->post->get('transition_' . $pk, -1, 'int');
+
+ // Get the model
+ $model = $this->getModel();
+ $return = $model->runTransition($pk, $transitionId);
+
+ if ($return === false)
+ {
+ // Transition execution failed.
+ $message = \JText::sprintf('JLIB_APPLICATION_ERROR_RUN_TRANSITION', $model->getError());
+ $this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message, 'error');
+
+ return false;
+ }
+ else
+ {
+ // Reorder succeeded.
+ $message = \JText::_('JLIB_APPLICATION_SUCCESS_RUN_TRANSITION');
+ $this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message);
+
+ return true;
+ }
+
+ $this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $extensionURL, false));
+ }
}
diff --git a/libraries/src/MVC/Model/AdminModel.php b/libraries/src/MVC/Model/AdminModel.php
index 72b3bbd3e00c3..53ca4c736af6d 100644
--- a/libraries/src/MVC/Model/AdminModel.php
+++ b/libraries/src/MVC/Model/AdminModel.php
@@ -105,6 +105,7 @@ abstract class AdminModel extends FormModel
'assetgroup_id' => 'batchAccess',
'language_id' => 'batchLanguage',
'tag' => 'batchTag',
+ 'workflowstate_id' => 'batchWorkflowState',
);
/**
diff --git a/libraries/src/Table/Module.php b/libraries/src/Table/Module.php
index 8161cc7ac43fe..ae9faf4d00488 100644
--- a/libraries/src/Table/Module.php
+++ b/libraries/src/Table/Module.php
@@ -200,6 +200,12 @@ public function store($updateNulls = false)
$this->publish_down = $this->_db->getNullDate();
}
+ if (!$this->ordering)
+ {
+ $query = $this->_db->getQuery(true);
+ $this->ordering = $this->getNextOrder($query->quoteName('position') . ' = ' . $query->quote($this->position));
+ }
+
return parent::store($updateNulls);
}
}
diff --git a/libraries/src/Workflow/Workflow.php b/libraries/src/Workflow/Workflow.php
new file mode 100644
index 0000000000000..ed29713f04ab3
--- /dev/null
+++ b/libraries/src/Workflow/Workflow.php
@@ -0,0 +1,326 @@
+ 'COM_WORKFLOW_PUBLISHED',
+ self::UNPUBLISHED => 'COM_WORKFLOW_UNPUBLISHED',
+ self::TRASHED => 'COM_WORKFLOW_TRASHED'
+ ];
+
+ protected $db;
+
+ /**
+ * Every item with a state which has the condition PUBLISHED is visible/active on the page
+ */
+ const PUBLISHED = 1;
+
+ /**
+ * Every item with a state which has the condition UNPUBLISHED is not visible/inactive on the page
+ */
+ const UNPUBLISHED = 0;
+
+ /**
+ * Every sitem with a state which has the condition TRASHED is trashed
+ */
+ const TRASHED = -2;
+
+ /**
+ * Class constructor
+ *
+ * @param array $options Array of options
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function __construct($options)
+ {
+ // Required options
+ $this->extension = $options['extension'];
+
+ // Default some optional options
+ $this->options['access'] = 'true';
+ $this->options['published'] = 1;
+ $this->options['countItems'] = 0;
+
+ $this->setOptions($options);
+ }
+
+ /**
+ * Returns the translated condition name, based on the given number
+ *
+ * @param int $value The condition ID
+ *
+ * @return string
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getConditionName($value)
+ {
+ return ArrayHelper::getValue($this->names, $value, '', 'string');
+ }
+
+ /**
+ * Executes a transition to change the current state in the association table
+ *
+ * @param array|int $pks The item IDs, which should use the transition
+ * @param int $transition_id The transition which should be executed
+ *
+ * @return boolean
+ */
+ public function executeTransition($pks, $transition_id)
+ {
+ if (!is_array($pks))
+ {
+ $pks = [(int) $pks];
+ }
+
+ $pks = ArrayHelper::toInteger($pks);
+ $pks = array_filter($pks);
+
+ if (!count($pks))
+ {
+ return true;
+ }
+
+ $db = Factory::getDbo();
+
+ $query = $db->getQuery(true);
+
+ $select = $db->quoteName(
+ [
+ 't.id',
+ 't.to_state_id',
+ 't.from_state_id',
+ 's.condition',
+ ]
+ );
+
+ $query ->select($select)
+ ->from($db->quoteName('#__workflow_transitions', 't'))
+ ->leftJoin($db->quoteName('#__workflow_states', 's') . ' ON ' . $db->quoteName('s.id') . ' = ' . $db->quoteName('t.to_state_id'))
+ ->where($db->quoteName('t.id') . ' = ' . (int) $transition_id);
+
+ if (!empty($this->options['published']))
+ {
+ $query ->where($db->quoteName('t.published') . ' = 1');
+ }
+
+ $transition = $db->setQuery($query)->loadObject();
+
+ // Check if the items can execute this transition
+ foreach ($pks as $pk)
+ {
+ $assoc = $this->getAssociation($pk);
+
+ if (!in_array($transition->from_state_id, [-1, $assoc->state_id]))
+ {
+ return false;
+ }
+ }
+
+ $parts = explode('.', $this->extension);
+
+ $component = reset($parts);
+
+ $componentInterface = Factory::getApplication()->bootComponent($component);
+
+ if ($componentInterface instanceof WorkflowServiceInterface)
+ {
+ $componentInterface->updateContentState($pks, $transition->condition);
+ }
+
+ return $this->updateAssociations($pks, $transition->to_state_id);
+ }
+
+ /**
+ * Creates an association for the workflow_associations table
+ *
+ * @param int $pk ID of the item
+ * @param int $state ID of state
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function createAssociation($pk, $state)
+ {
+ try
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ $query
+ ->insert($db->quoteName('#__workflow_associations'))
+ ->columns($db->quoteName(array('item_id', 'state_id', 'extension')))
+ ->values((int) $pk . ', ' . (int) $state . ', ' . $db->quote($this->extension));
+
+ $db->setQuery($query)->execute();
+ }
+ catch (\Exception $e)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Update an existing association with a new state
+ *
+ * @param array $pks An Array of item IDs which should be changed
+ * @param int $state The new state ID
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function updateAssociations($pks, $state)
+ {
+ if (!is_array($pks))
+ {
+ $pks = [$pks];
+ }
+
+ $pks = ArrayHelper::toInteger($pks);
+
+ try
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ $query
+ ->update($db->quoteName('#__workflow_associations'))
+ ->set($db->quoteName('state_id') . '=' . (int) $state)
+ ->where($db->quoteName('item_id') . ' IN(' . implode(',', $pks) . ')')
+ ->where($db->quoteName('extension') . '=' . $db->quote($this->extension));
+
+ $db->setQuery($query)->execute();
+ }
+ catch (\Exception $e)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Removes associations form the workflow_associations table
+ *
+ * @param int $pks ID of content
+ *
+ * @return boolean
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function deleteAssociation($pks)
+ {
+ $pks = ArrayHelper::toInteger($pks);
+
+ try
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ $query
+ ->delete($db->quoteName('#__workflow_associations'))
+ ->where($db->quoteName('item_id') . ' IN (' . implode(',', $pks) . ')')
+ ->andWhere($db->quoteName('extension') . '=' . $db->quote($this->extension));
+
+ $db->setQuery($query)->execute();
+ }
+ catch (\Exception $e)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Loads an existing association item with state and item ID
+ *
+ * @param int $item_id The item ID to load
+ *
+ * @return object
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getAssociation($item_id)
+ {
+ $db = Factory::getDbo();
+
+ $query = $db->getQuery(true);
+
+ $select = $db->quoteName(
+ [
+ 'item_id',
+ 'state_id'
+ ]
+ );
+
+ $query ->select($select)
+ ->from($db->quoteName('#__workflow_associations'))
+ ->where($db->quoteName('item_id') . ' = ' . (int) $item_id)
+ ->where($db->quoteName('extension') . ' = ' . $db->quote($this->extension));
+
+ return $db->setQuery($query)->loadObject();
+ }
+
+ /**
+ * Allows to set some optional options, eg. if the access level should be considered.
+ *
+ * @param array $options The new options
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function setOptions(array $options)
+ {
+ if (isset($options['access']))
+ {
+ $this->options['access'] = $options['access'];
+ }
+
+ if (isset($options['published']))
+ {
+ $this->options['published'] = $options['published'];
+ }
+
+ if (isset($options['countItems']))
+ {
+ $this->options['countItems'] = $options['countItems'];
+ }
+ }
+}
diff --git a/libraries/src/Workflow/WorkflowServiceInterface.php b/libraries/src/Workflow/WorkflowServiceInterface.php
new file mode 100644
index 0000000000000..58517b3214642
--- /dev/null
+++ b/libraries/src/Workflow/WorkflowServiceInterface.php
@@ -0,0 +1,43 @@
+get('state', [4]);
+
$query = $db->getQuery(true);
$query->select($query->month($db->quoteName('created')) . ' AS created_month')
->select('MIN(' . $db->quoteName('created') . ') AS created')
->select($query->year($db->quoteName('created')) . ' AS created_year')
- ->from('#__content')
- ->where('state = 2')
- ->group($query->year($db->quoteName('created')) . ', ' . $query->month($db->quoteName('created')))
- ->order($query->year($db->quoteName('created')) . ' DESC, ' . $query->month($db->quoteName('created')) . ' DESC');
+ ->from($db->quoteName('#__content', 'c'))
+ ->innerJoin($db->quoteName('#__workflow_associations', 'wa') . ' ON wa.item_id = c.id')
+ ->group($query->year($db->quoteName('c.created')) . ', ' . $query->month($db->quoteName('c.created')))
+ ->order($query->year($db->quoteName('c.created')) . ' DESC, ' . $query->month($db->quoteName('c.created')) . ' DESC');
+
+ if (!empty($states))
+ {
+ $states = ArrayHelper::toInteger($states);
+
+ $query->where($db->qn('wa.state_id') . ' IN (' . implode(', ', $states) . ')');
+ }
// Filter by language
if ($app->getLanguageFilter())
@@ -64,7 +74,7 @@ public static function getList(&$params)
{
$app->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error');
- return array();
+ return [];
}
$menu = $app->getMenu();
@@ -74,6 +84,16 @@ public static function getList(&$params)
$i = 0;
$lists = array();
+ $states = array_map(
+ function ($el)
+ {
+ return '&state[]=' . (int) $el;
+ },
+ $states
+ );
+
+ $states = implode($states);
+
foreach ($rows as $row)
{
$date = Factory::getDate($row->created);
diff --git a/modules/mod_articles_archive/mod_articles_archive.php b/modules/mod_articles_archive/mod_articles_archive.php
index e66a2b1b8d00e..f0fe5fd371722 100644
--- a/modules/mod_articles_archive/mod_articles_archive.php
+++ b/modules/mod_articles_archive/mod_articles_archive.php
@@ -13,6 +13,7 @@
use Joomla\Module\ArticlesArchive\Site\Helper\ArticlesArchiveHelper;
$params->def('count', 10);
+$params->def('state', 0);
$list = ArticlesArchiveHelper::getList($params);
require ModuleHelper::getLayoutPath('mod_articles_archive', $params->get('layout', 'default'));
diff --git a/modules/mod_articles_archive/mod_articles_archive.xml b/modules/mod_articles_archive/mod_articles_archive.xml
index b216a9999a943..516bf33663866 100644
--- a/modules/mod_articles_archive/mod_articles_archive.xml
+++ b/modules/mod_articles_archive/mod_articles_archive.xml
@@ -29,6 +29,16 @@
label="MOD_ARTICLES_ARCHIVE_FIELD_COUNT_LABEL"
default="10"
/>
+
+
+
diff --git a/plugins/content/pagenavigation/pagenavigation.php b/plugins/content/pagenavigation/pagenavigation.php
index a9d7eccef8dcc..9e3da65113b6c 100644
--- a/plugins/content/pagenavigation/pagenavigation.php
+++ b/plugins/content/pagenavigation/pagenavigation.php
@@ -124,7 +124,7 @@ public function onContentBeforeDisplay($context, &$row, &$params, $page = 0)
break;
}
- $xwhere = ' AND (a.state = 1 OR a.state = -1)'
+ $xwhere = ' AND (ws.condition = 1 OR ws.condition = -2)'
. ' AND (publish_up = ' . $db->quote($nullDate) . ' OR publish_up <= ' . $db->quote($now) . ')'
. ' AND (publish_down = ' . $db->quote($nullDate) . ' OR publish_down >= ' . $db->quote($now) . ')';
@@ -143,7 +143,8 @@ public function onContentBeforeDisplay($context, &$row, &$params, $page = 0)
->select($case_when)
->select($case_when1)
->from('#__content AS a')
- ->join('LEFT', '#__categories AS cc ON cc.id = a.catid');
+ ->join('LEFT', '#__categories AS cc ON cc.id = a.catid')
+ ->join('LEFT', '#__workflow_states AS ws ON ws.id = a.state');
if ($order_method === 'author' || $order_method === 'rauthor')
{
diff --git a/plugins/search/content/content.php b/plugins/search/content/content.php
index 58521a8c924ad..e818c021340df 100644
--- a/plugins/search/content/content.php
+++ b/plugins/search/content/content.php
@@ -279,8 +279,9 @@ public function onContentSearch($text, $phrase = '', $ordering = '', $areas = nu
->select($db->quote('2') . ' AS browsernav')
->from($db->quoteName('#__content', 'a'))
->innerJoin($db->quoteName('#__categories', 'c') . ' ON c.id = a.catid')
+ ->join('LEFT', '#__workflow_states AS ws ON ws.id = state')
->where(
- '(' . $where . ') AND a.state=1 AND c.published = 1 AND a.access IN (' . $groups . ') '
+ '(' . $where . ') AND ws.condition=1 AND c.published = 1 AND a.access IN (' . $groups . ') '
. 'AND c.access IN (' . $groups . ')'
. 'AND (a.publish_up = ' . $db->quote($nullDate) . ' OR a.publish_up <= ' . $db->quote($now) . ') '
. 'AND (a.publish_down = ' . $db->quote($nullDate) . ' OR a.publish_down >= ' . $db->quote($now) . ')'
|