diff --git a/controller/idea_controller.php b/controller/idea_controller.php index 4ba2d2ba..7c4b3313 100644 --- a/controller/idea_controller.php +++ b/controller/idea_controller.php @@ -202,6 +202,23 @@ public function ticket() return false; } + /** + * Implemented action (sets an idea's implemented phpBB version) + * + * @return bool True if set, false if not + * @access public + */ + public function implemented() + { + if ($this->is_mod() && check_link_hash($this->get_hash(), "implemented_{$this->data['idea_id']}")) + { + $version = $this->request->variable('implemented', ''); + return $this->ideas->set_implemented($this->data['idea_id'], $version); + } + + return false; + } + /** * Title action (sets an idea's title) * diff --git a/event/listener.php b/event/listener.php index b619b019..d67e6bc9 100644 --- a/event/listener.php +++ b/event/listener.php @@ -219,6 +219,7 @@ public function show_idea($event) 'IDEA_DUPLICATE' => $idea['duplicate_id'], 'IDEA_RFC' => $idea['rfc_link'], 'IDEA_TICKET' => $idea['ticket_id'], + 'IDEA_IMPLEMENTED' => $idea['implemented_version'], 'U_IDEA_DUPLICATE' => $this->link_helper->get_idea_link((int) $idea['duplicate_id']), @@ -229,6 +230,7 @@ public function show_idea($event) 'U_CHANGE_STATUS' => $this->link_helper->get_idea_link($idea['idea_id'], 'status', true), 'U_EDIT_DUPLICATE' => $this->link_helper->get_idea_link($idea['idea_id'], 'duplicate', true), 'U_EDIT_RFC' => $this->link_helper->get_idea_link($idea['idea_id'], 'rfc', true), + 'U_EDIT_IMPLEMENTED'=> $this->link_helper->get_idea_link($idea['idea_id'], 'implemented', true), 'U_EDIT_TICKET' => $this->link_helper->get_idea_link($idea['idea_id'], 'ticket', true), 'U_REMOVE_VOTE' => $this->link_helper->get_idea_link($idea['idea_id'], 'removevote', true), 'U_IDEA_VOTE' => $this->link_helper->get_idea_link($idea['idea_id'], 'vote', true), diff --git a/factory/ideas.php b/factory/ideas.php index 9dc11d13..67fb94b0 100644 --- a/factory/ideas.php +++ b/factory/ideas.php @@ -357,6 +357,31 @@ public function set_ticket($idea_id, $ticket) return true; } + /** + * Sets the implemented version of an idea. + * + * @param int $idea_id ID of the idea to be updated. + * @param string $version Version of phpBB the idea was implemented in. + * + * @return bool True if set, false if invalid. + */ + public function set_implemented($idea_id, $version) + { + $match = '/^\d\.\d\.\d+(\-\w+)?$/'; + if ($version && !preg_match($match, $version)) + { + return false; + } + + $sql_ary = array( + 'implemented_version' => $version, // string is escaped by build_array() + ); + + $this->update_idea_data($sql_ary, $idea_id, $this->table_ideas); + + return true; + } + /** * Sets the title of an idea. * diff --git a/language/en/common.php b/language/en/common.php index 07257f5c..5826bd97 100644 --- a/language/en/common.php +++ b/language/en/common.php @@ -43,7 +43,9 @@ 'IDEAS_TITLE' => 'phpBB Ideas', 'IDEAS_NOT_AVAILABLE' => 'Ideas is not available at this time.', 'IMPLEMENTED' => 'Implemented', + 'IMPLEMENTED_ERROR' => 'Must be a valid phpBB version number.', 'IMPLEMENTED_IDEAS' => 'Recently Implemented Ideas', + 'IMPLEMENTED_VERSION' => 'phpBB version', 'IN_PROGRESS' => 'In Progress', 'INVALID' => 'Invalid', 'INVALID_VOTE' => 'Invalid vote; the number you entered was invalid.', diff --git a/migrations/m8_implemented_version.php b/migrations/m8_implemented_version.php new file mode 100644 index 00000000..160ffeb1 --- /dev/null +++ b/migrations/m8_implemented_version.php @@ -0,0 +1,51 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\ideas\migrations; + +class m8_implemented_version extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'ideas_ideas', 'implemented_version'); + } + + static public function depends_on() + { + return array( + '\phpbb\ideas\migrations\m1_initial_schema', + '\phpbb\ideas\migrations\m4_update_statuses', + '\phpbb\ideas\migrations\m6_migrate_old_tables', + '\phpbb\ideas\migrations\m7_drop_old_tables', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'ideas_ideas' => array( + 'implemented_version' => array('VCHAR', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'ideas_ideas' => array( + 'implemented_version', + ), + ), + ); + } +} diff --git a/styles/prosilver/template/idea_body.html b/styles/prosilver/template/idea_body.html index 7d7bf608..a8e7bdfe 100644 --- a/styles/prosilver/template/idea_body.html +++ b/styles/prosilver/template/idea_body.html @@ -75,8 +75,8 @@ {% endif %} {% if IDEA_DUPLICATE or S_IS_MOD %} - -
+
+
{{ lang('IDEA_NUM') }}{{ IDEA_DUPLICATE }}{% else %}style="display:none">{% endif %} {% if S_IS_MOD %} {% if IDEA_DUPLICATE %}{{ lang('EDIT') }}{% else %}{{ lang('ADD') }}{% endif %} @@ -84,6 +84,16 @@ {% endif %}
{% endif %} + {% if IDEA_IMPLEMENTED or S_IS_MOD %} + +
+ + {% if S_IS_MOD %} + {% if IDEA_IMPLEMENTED %}{{ lang('EDIT') }}{% else %}{{ lang('ADD') }}{% endif %} + + {% endif %} +
+ {% endif %} diff --git a/styles/prosilver/template/ideas.js b/styles/prosilver/template/ideas.js index c5886415..ba0ab468 100644 --- a/styles/prosilver/template/ideas.js +++ b/styles/prosilver/template/ideas.js @@ -19,6 +19,10 @@ rfcEdit: $('#rfcedit'), rfcEditInput: $('#rfceditinput'), rfcLink: $('#rfclink'), + implementedEdit: $('#implementededit'), + implementedEditInput: $('#implementededitinput'), + implementedVersion: $('#implementedversion'), + implementedToggle: $('.implementedtoggle'), removeVote: $('.removevote'), status: $('#status'), successVoted: $('.successvoted'), @@ -126,11 +130,8 @@ .removeClass() .addClass('status-badge status-' + $this.find(':selected').val()); - if (idea_is_duplicate()) { - $obj.duplicateToggle.show(); - } else { - $obj.duplicateToggle.hide(); - } + $obj.duplicateToggle.toggle(idea_is_duplicate()); + $obj.implementedToggle.toggle(idea_is_implemented()); } }); }); @@ -168,11 +169,7 @@ $this.hide(); - $obj.rfcEdit.text(function() { - return value ? $(this).attr('data-l-edit') : $(this).attr('data-l-add'); - }).prepend($('').addClass(function() { - return value ? 'fa-pencil' : 'fa-plus-circle'; - })).show(); + $obj.rfcEdit.toggleAddEdit(value); } }); } else if (e.keyCode === keymap.ESC) { @@ -226,11 +223,7 @@ $this.hide(); - $obj.ticketEdit.text(function() { - return value ? $(this).attr('data-l-edit') : $(this).attr('data-l-add'); - }).prepend($('').addClass(function() { - return value ? 'fa-pencil' : 'fa-plus-circle'; - })).show(); + $obj.ticketEdit.toggleAddEdit(value); } }); @@ -287,11 +280,7 @@ $this.hide(); - $obj.duplicateEdit.text(function() { - return value ? $(this).attr('data-l-edit') : $(this).attr('data-l-add'); - }).prepend($('').addClass(function() { - return value ? 'fa-pencil' : 'fa-plus-circle'; - })).show(); + $obj.duplicateEdit.toggleAddEdit(value); } }); } else if (e.keyCode === keymap.ESC) { @@ -308,6 +297,61 @@ } }); + $obj.implementedEdit.on('click', function(e) { + e.preventDefault(); + + $obj.implementedEdit.add($obj.implementedVersion).hide(); + $obj.implementedEditInput.show().focus(); + }); + + $obj.implementedEditInput.on('keydown', function(e) { + if (e.keyCode === keymap.ENTER) { + e.preventDefault(); + e.stopPropagation(); + + var $this = $(this), + find = /^\d\.\d\.\d+(\-\w+)?$/, + url = $obj.implementedEdit.attr('href'), + value = $this.val(); + + if (value && !find.test(value)) { + phpbb.alert($this.attr('data-l-err'), $this.attr('data-l-msg')); + return; + } + + $.get(url, {implemented: value}, function(res) { + if (res) { + $obj.implementedVersion.text(value); + + if (value) { + $obj.implementedVersion.show(); + } + + $this.hide(); + + $obj.implementedEdit.toggleAddEdit(value); + } + }); + } else if (e.keyCode === keymap.ESC) { + e.preventDefault(); + + $(this).hide(); + $obj.implementedEdit.show(); + + if ($obj.implementedVersion.text()) { + $obj.implementedVersion.show(); + } + } + }); + + $.fn.toggleAddEdit = function(value) { + $(this).text(function() { + return value ? $(this).attr('data-l-edit') : $(this).attr('data-l-add'); + }).prepend($('').addClass(function() { + return value ? 'fa-pencil' : 'fa-plus-circle'; + })).show(); + }; + /** * Returns true if idea is a duplicate. Bit hacky. */ @@ -316,6 +360,14 @@ return href && href.indexOf('status=4') !== -1; } + /** + * Returns true if idea is implemented. Bit hacky. + */ + function idea_is_implemented() { + var href = $obj.status.prev('a').attr('href'); + return href && href.indexOf('status=3') !== -1; + } + function displayVoters(data) { var upVoters = [], diff --git a/tests/controller/idea_controller_test.php b/tests/controller/idea_controller_test.php index 5fe9e8a2..c729d73f 100644 --- a/tests/controller/idea_controller_test.php +++ b/tests/controller/idea_controller_test.php @@ -23,23 +23,25 @@ public function controller_test_data() { return array( array(1, '', '', false, null, null, 302), // non-ajax - array(2, 'delete', 'delete', true, true, '{}', 200), // ajax delete success (confirm fail) - array(2, 'delete', 'delete', true, false, 'NO_AUTH_OPERATION', 403), // ajax delete fail + array(2, 'delete', '', true, true, '{}', 200), // ajax delete success (confirm fail) + array(2, 'delete', '', true, false, 'NO_AUTH_OPERATION', 403), // ajax delete fail array(2, 'delete', 'delete', true, true, 'trigger_error', 200), // ajax delete success (confirm true) array(3, 'duplicate', 'set_duplicate', true, true, 'true', 200), // ajax set duplicate success - array(3, 'duplicate', 'set_duplicate', true, false, 'false', 200), // ajax set duplicate fail + array(3, 'duplicate', '', true, false, 'false', 200), // ajax set duplicate fail array(4, 'removevote', 'remove_vote', true, true, 'true', 200), // ajax set title success - array(4, 'removevote', 'remove_vote', true, false, '"You do not have the necessary permissions to complete this operation."', 200), // ajax set title fail + array(4, 'removevote', '', true, false, '"You do not have the necessary permissions to complete this operation."', 200), // ajax set title fail array(5, 'rfc', 'set_rfc', true, true, 'true', 200), // ajax set rfc success - array(5, 'rfc', 'set_rfc', true, false, 'false', 200), // ajax set rfc fail - array(6, 'status', 'set_status', true, true, 'true', 200), // ajax set status success - array(6, 'status', 'set_status', true, false, 'false', 200), // ajax set status fail + array(5, 'rfc', '', true, false, 'false', 200), // ajax set rfc fail + array(6, 'status', 'change_status', true, true, 'true', 200), // ajax set status success + array(6, 'status', '', true, false, 'false', 200), // ajax set status fail array(7, 'ticket', 'set_ticket', true, true, 'true', 200), // ajax set ticket success - array(7, 'ticket', 'set_ticket', true, false, 'false', 200), // ajax set ticket fail + array(7, 'ticket', '', true, false, 'false', 200), // ajax set ticket fail array(8, 'title', 'set_title', true, true, 'true', 200), // ajax set title success - array(8, 'title', 'set_title', true, false, 'false', 200), // ajax set title fail + array(8, 'title', '', true, false, 'false', 200), // ajax set title fail array(9, 'vote', 'vote', true, true, 'true', 200), // ajax set title success - array(9, 'vote', 'vote', true, false, '"You do not have the necessary permissions to complete this operation."', 200), // ajax set title fail + array(9, 'vote', '', true, false, '"You do not have the necessary permissions to complete this operation."', 200), // ajax set title fail + array(10, 'implemented', 'set_implemented', true, true, 'true', 200), // ajax set implemented success + array(10, 'implemented', '', true, false, 'false', 200), // ajax set implemented fail ); } @@ -56,7 +58,7 @@ public function test_controller($idea_id, $mode, $callback, $is_ajax, $authorise ->will($this->returnValue(array('idea_id' => $idea_id, 'idea_author' => 2))); // mock a result from each method called by the idea controller - $this->ideas->expects($this->any()) + $this->ideas->expects(($callback !== '' ? $this->once() : $this->never())) ->method($callback) ->will($this->returnValue($authorised)); diff --git a/tests/fixtures/ideas.xml b/tests/fixtures/ideas.xml index 5c05a4dd..353bee6c 100644 --- a/tests/fixtures/ideas.xml +++ b/tests/fixtures/ideas.xml @@ -12,6 +12,7 @@ ticket_id duplicate_id rfc_link + implemented_version 1 2 @@ -24,6 +25,7 @@ 1234 0 + 2 @@ -37,6 +39,7 @@ 0 0 + 3 @@ -50,6 +53,7 @@ 0 0 + 4 @@ -63,6 +67,7 @@ 0 0 + 5 @@ -76,6 +81,7 @@ 0 0 + 6 @@ -89,6 +95,7 @@ 0 0 + diff --git a/tests/ideas/idea_attributes_test.php b/tests/ideas/idea_attributes_test.php index cc740c2f..fabd5f69 100644 --- a/tests/ideas/idea_attributes_test.php +++ b/tests/ideas/idea_attributes_test.php @@ -86,6 +86,7 @@ public function idea_attribute_test_data() 'idea_id' => 1, 'duplicate_id' => 2, 'rfc_link' => 'http://area51.phpbb.com/phpBB/viewtopic.php?foo&bar', + 'implemented_version' => '3.1.0', 'ticket_id' => 1111, 'idea_title' => 'Foo Idea 1', ), true @@ -95,6 +96,7 @@ public function idea_attribute_test_data() 'idea_id' => 2, 'duplicate_id' => 1, 'rfc_link' => 'https://area51.phpbb.com/phpBB/viewtopic.php?bar&foo', + 'implemented_version' => '3.2.0', 'ticket_id' => 2222, 'idea_title' => 'Foo Idea 2', ), true @@ -104,6 +106,7 @@ public function idea_attribute_test_data() 'idea_id' => 3, 'duplicate_id' => '5', 'rfc_link' => '', + 'implemented_version' => '', 'ticket_id' => '3333', 'idea_title' => 'Føó Îdéå', ), true @@ -113,6 +116,7 @@ public function idea_attribute_test_data() 'idea_id' => 4, 'duplicate_id' => 'foo', 'rfc_link' => 'https://www.phpbb.com/phpBB/viewtopic.php?foo', + 'implemented_version' => 'foo', 'ticket_id' => 'foo', 'idea_title' => '', ), false @@ -122,6 +126,7 @@ public function idea_attribute_test_data() 'idea_id' => 5, 'duplicate_id' => array(1), 'rfc_link' => 'foobar', + 'implemented_version' => '1', 'ticket_id' => array(1), 'idea_title' => '', ), false @@ -149,6 +154,16 @@ public function test_set_rfc($data, $expected) $this->set_attribute_test('set_rfc', 'rfc_link', $data, $expected); } + /** + * Test set_implemented() + * + * @dataProvider idea_attribute_test_data + */ + public function test_set_implemented($data, $expected) + { + $this->set_attribute_test('set_implemented', 'implemented_version', $data, $expected); + } + /** * Test set_ticket() *