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('DUPLICATE') ~ lang('COLON') }}
-
+ {{ lang('DUPLICATE') ~ lang('COLON') }}
+
{{ 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 %}
+ {{ lang('IMPLEMENTED_VERSION') ~ lang('COLON') }}
+
+ {{ IDEA_IMPLEMENTED }}
+ {% 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()
*