From a2d63fae6c9c62f49adae5acb6850a2e6b2bc08c Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Thu, 24 Oct 2024 14:53:27 +0100 Subject: [PATCH] new forms for editing keyword and speaker alerts This splits out the two forms to have a multi step one for keyword alerts that allows easier access to exlusions, limiting to sections and also to limit it to a speaker. The speaker only form handle the rest (search by postcode, name and constituency) but only does when x speaks. --- classes/AlertView/Standard.php | 193 +++++++- classes/Utility/Alert.php | 48 +- www/docs/js/main.js | 29 -- www/docs/style/sass/parts/_accordion.scss | 24 +- www/includes/easyparliament/alert.php | 21 + .../templates/html/alert/_alert_form.php | 256 ++++++++++ .../templates/html/alert/_list_accordian.php | 276 +++++++++++ .../templates/html/alert/_mp_alert_form.php | 47 ++ .../templates/html/alert/index.php | 454 +++--------------- 9 files changed, 892 insertions(+), 456 deletions(-) create mode 100644 www/includes/easyparliament/templates/html/alert/_alert_form.php create mode 100644 www/includes/easyparliament/templates/html/alert/_list_accordian.php create mode 100644 www/includes/easyparliament/templates/html/alert/_mp_alert_form.php diff --git a/classes/AlertView/Standard.php b/classes/AlertView/Standard.php index 3a14cbccd9..de6bda7d44 100644 --- a/classes/AlertView/Standard.php +++ b/classes/AlertView/Standard.php @@ -25,7 +25,9 @@ public function display() { $this->checkInput(); $this->searchForConstituenciesAndMembers(); - if (!sizeof($this->data['errors']) && ($this->data['keyword'] || $this->data['pid'])) { + if ($this->data['step'] || $this->data['addword']) { + $this->processStep(); + } elseif (!$this->data['results'] == 'changes-abandoned' && !sizeof($this->data['errors']) && ($this->data['keyword'] || $this->data['pid'])) { $this->addAlert(); } @@ -48,7 +50,8 @@ private function processAction() { $success = $this->confirmAlert($token); if ($success) { $this->data['results'] = 'alert-confirmed'; - $this->data['criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria); + $this->data['criteria'] = $this->alert->criteria; + $this->data['display_criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria); } } elseif ($action == 'Suspend') { $success = $this->suspendAlert($token); @@ -70,6 +73,8 @@ private function processAction() { if ($success) { $this->data['results'] = 'all-alerts-deleted'; } + } elseif ($action == 'Abandon') { + $this->data['results'] = 'changes-abandoned'; } if (!$success) { $this->data['results'] = 'alert-fail'; @@ -79,6 +84,25 @@ private function processAction() { $this->data['alert'] = $alert; } + private function processStep() { + if ($this->data['step'] == 'confirm') { + $success = true; + if ($this->data['alert']) { + $success = $this->updateAlert($this->data['alert']['id'], $this->data); + if ($success) { + $this->data['results'] = 'alert-confirmed'; + $this->data['step'] = ''; + } else { + $this->data['results'] = 'alert-fail'; + $this->data['step'] = 'review'; + } + } else { + $success = $this->addAlert(); + $this->data['step'] = ''; + } + } + } + private function getBasicData() { global $this_page; @@ -93,12 +117,66 @@ private function getBasicData() { $this->data["email"] = trim(get_http_var("email")); $this->data['email_verified'] = false; } - $this->data['keyword'] = trim(get_http_var("keyword")); + + $this->data['token'] = get_http_var('t'); + $this->data['step'] = trim(get_http_var("step")); + $this->data['addword'] = trim(get_http_var("addword")); + $this->data['this_step'] = trim(get_http_var("this_step")); + + if ($this->data['addword'] || $this->data['step']) { + $alert = $this->alert->check_token($this->data['token']); + + $criteria = ''; + if ($alert) { + $criteria = $alert['criteria']; + } + + $this->data['alert'] = $alert; + + $this->data['alert_parts'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($criteria, true); + + $existing_rep = ''; + if (isset($this->data['alert_parts']['spokenby'])) { + $existing_rep = $this->data['alert_parts']['spokenby'][0]; + } + + $existing_section = ''; + if (count($this->data['alert_parts']['sections'])) { + $existing_section = $this->data['alert_parts']['sections'][0]; + } + + $words = get_http_var('words', $this->data['alert_parts']['words'], true); + + $this->data['words'] = []; + $this->data['keywords'] = []; + foreach ($words as $word) { + if (trim($word) != '') { + $this->data['keywords'][] = $word; + $this->data['words'][] = $this->wrap_phrase_in_quotes($word); + } + } + $this->data['exclusions'] = trim(get_http_var("exclusions", implode('', $this->data['alert_parts']['exclusions']))); + $this->data['representative'] = trim(get_http_var("representative", $existing_rep)); + + $this->data['search_section'] = trim(get_http_var("search_section", $existing_section)); + + $this->data['keyword'] = implode(' ', $this->data['words']); + if ($this->data['exclusions']) { + $this->data['keyword'] .= " -" . $this->data["exclusions"]; + } + + $this->data['results'] = ''; + + $this->getSearchSections(); + } else { + $this->data['keyword'] = trim(get_http_var("keyword")); + $this->data['search_section'] = ''; + } + $this->data['pid'] = trim(get_http_var("pid")); $this->data['alertsearch'] = trim(get_http_var("alertsearch")); $this->data['pc'] = get_http_var('pc'); - $this->data['submitted'] = get_http_var('submitted') || $this->data['pid'] || $this->data['keyword']; - $this->data['token'] = get_http_var('t'); + $this->data['submitted'] = get_http_var('submitted') || $this->data['pid'] || $this->data['keyword'] || $this->data['step']; $this->data['sign'] = get_http_var('sign'); $this->data['site'] = get_http_var('site'); $this->data['message'] = ''; @@ -108,11 +186,45 @@ private function getBasicData() { $this->data['actionurl'] = $ACTIONURL->generate(); } - private function checkInput() { + private function wrap_phrase_in_quotes($phrase) { + if (strpos($phrase, ' ') > 0) { + $phrase = '"' . trim($phrase, '"') . '"'; + } + + return $phrase; + } + + private function getRecentResults($text) { + global $SEARCHENGINE; + $se = new \SEARCHENGINE($text); + $this->data['search_result_count'] = $se->run_count(0, 10); + $se->run_search(0, 1, 'date'); + } + + private function getSearchSections() { + $this->data['sections'] = []; + if ($this->data['search_section']) { + foreach (explode(' ', $this->data['search_section']) as $section) { + $this->data['sections'][] = \MySociety\TheyWorkForYou\Utility\Alert::sectionToTitle($section); + } + } + } + + protected function updateAlert($token) { + $success = $this->alert->update($token, $this->data); + return $success; + } + + protected function checkInput() { global $SEARCHENGINE; $errors = []; + if ($this->data['step'] == 'define' || $this->data['step'] == 'mp_alert') { + $this->data['errors'] = $errors; + return; + } + // Check each of the things the user has input. // If there is a problem with any of them, set an entry in the $errors array. // This will then be used to (a) indicate there were errors and (b) display @@ -155,11 +267,32 @@ private function checkInput() { $this->data['errors'] = $errors; } - private function searchForConstituenciesAndMembers() { - // Do the search - if ($this->data['alertsearch']) { + protected function searchForConstituenciesAndMembers() { + if ($this->data['results'] == 'changes-abandoned') { + return; + } + $errors = []; + if ($this->data['alertsearch'] != '') { $this->data['members'] = \MySociety\TheyWorkForYou\Utility\Search::searchMemberDbLookupWithNames($this->data['alertsearch'], true); [$this->data['constituencies'], $this->data['valid_postcode']] = \MySociety\TheyWorkForYou\Utility\Search::searchConstituenciesByQuery($this->data['alertsearch']); + } elseif ($this->data['pid']) { + $MEMBER = new \MEMBER(['person_id' => $this->data['pid']]); + $this->data['members'] = [[ + "person_id" => $MEMBER->person_id, + "given_name" => $MEMBER->given_name, + "family_name" => $MEMBER->family_name, + ]]; + } elseif (isset($this->data['representative']) && $this->data['representative'] != '') { + $this->data['members'] = \MySociety\TheyWorkForYou\Utility\Search::searchMemberDbLookupWithNames($this->data['representative'], true); + + $member_count = count($this->data['members']); + if ($member_count == 0) { + $errors["representative"] = gettext("No matching representative found"); + } elseif ($member_count > 1) { + $errors["representative"] = gettext("Multiple matching representatives found, please select one."); + } else { + $this->data['pid'] = $this->data['members'][0]['person_id']; + } } else { $this->data['members'] = []; } @@ -186,9 +319,15 @@ private function searchForConstituenciesAndMembers() { } $this->data['constituencies'] = $cons; } + + if (count($this->data["errors"]) > 0) { + $this->data["errors"] = array_merge($this->data["errors"], $errors); + } else { + $this->data["errors"] = $errors; + } } - private function addAlert() { + protected function addAlert() { $external_auth = auth_verify_with_shared_secret($this->data['email'], OPTION_AUTH_SHARED_SECRET, get_http_var('sign')); if ($external_auth) { $confirm = false; @@ -230,11 +369,12 @@ private function addAlert() { $this->data['pc'] = ''; $this->data['results'] = $result; - $this->data['criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria); + $this->data['criteria'] = $this->alert->criteria; + $this->data['display_criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria); } - private function formatSearchTerms() { + protected function formatSearchTerms() { if ($this->data['alertsearch']) { $this->data['alertsearch_pretty'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->data['alertsearch']); $this->data['search_text'] = $this->data['alertsearch']; @@ -243,7 +383,7 @@ private function formatSearchTerms() { } } - private function checkForCommonMistakes() { + protected function checkForCommonMistakes() { $mistakes = []; if (strstr($this->data['alertsearch'], ',') > -1) { $mistakes['multiple'] = 1; @@ -261,7 +401,7 @@ private function checkForCommonMistakes() { $this->data['mistakes'] = $mistakes; } - private function formatSearchMemberData() { + protected function formatSearchMemberData() { if (isset($this->data['postcode'])) { try { $postcode = $this->data['postcode']; @@ -302,13 +442,31 @@ private function formatSearchMemberData() { } } - private function setUserData() { + protected function setUserData() { + if (!isset($this->data['criteria'])) { + $criteria = $this->data['keyword']; + if ($this->data['search_section']) { + $criteria .= " section:" . $this->data['search_section']; + } + if ($this->data['pid']) { + $criteria .= " speaker:" . $this->data['pid']; + } + $this->getRecentResults($criteria); + + $this->data['criteria'] = $criteria; + $this->data['display_criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($criteria); + } $this->data['current_mp'] = false; $this->data['alerts'] = []; $this->data['keyword_alerts'] = []; $this->data['speaker_alerts'] = []; + $this->data['spoken_alerts'] = []; $this->data['own_member_alerts'] = []; $this->data['all_keywords'] = []; + if ($this->data['results'] == 'changes-abandoned') { + $this->data['members'] = false; + $this->data['alertsearch'] = ''; + } $own_mp_criteria = ''; if ($this->data['email_verified']) { if ($this->user->postcode()) { @@ -330,5 +488,10 @@ private function setUserData() { } } } + if ($this->data['addword'] != '' || ($this->data['step'] && count($this->data['errors']) > 0)) { + $this->data["step"] = get_http_var('this_step'); + } else { + $this->data['this_step'] = ''; + } } } diff --git a/classes/Utility/Alert.php b/classes/Utility/Alert.php index 79f6d42f7d..d2c636b373 100644 --- a/classes/Utility/Alert.php +++ b/classes/Utility/Alert.php @@ -10,12 +10,23 @@ class Alert { #XXX don't calculate this every time - private static function sectionToTitle($section) { - global $hansardmajors; - $section_map = []; - foreach ($hansardmajors as $major => $details) { - $section_map[$details["page_all"]] = $details["title"]; - } + public static function sectionToTitle($section) { + $section_map = [ + "uk" => gettext('All UK'), + "debates" => gettext('House of Commons debates'), + "whalls" => gettext('Westminster Hall debates'), + "lords" => gettext('House of Lords debates'), + "wrans" => gettext('Written answers'), + "wms" => gettext('Written ministerial statements'), + "standing" => gettext('Bill Committees'), + "future" => gettext('Future Business'), + "ni" => gettext('Northern Ireland Assembly Debates'), + "scotland" => gettext('All Scotland'), + "sp" => gettext('Scottish Parliament Debates'), + "spwrans" => gettext('Scottish Parliament Written answers'), + "wales" => gettext('Welsh parliament record'), + "lmqs" => gettext('Questions to the Mayor of London'), + ]; return $section_map[$section]; } @@ -30,6 +41,10 @@ public static function detailsToCriteria($details) { $criteria[] = 'speaker:' . $details['pid']; } + if (!empty($details['search_section'])) { + $criteria[] = 'section:' . $details['search_section']; + } + $criteria = join(' ', $criteria); return $criteria; } @@ -74,10 +89,23 @@ public static function forUser($email) { public static function prettifyCriteria($alert_criteria, $as_parts = false) { $text = ''; + $parts = ['words' => [], 'sections' => [], 'exclusions' => []]; if ($alert_criteria) { - $criteria = explode(' ', $alert_criteria); - $parts = []; + # check for phrases + if (strpos($alert_criteria, '"') !== false) { + # match phrases + preg_match_all('/"([^"]*)"/', $alert_criteria, $phrases); + # and then remove them from the criteria + $alert_criteria = trim(preg_replace('/ +/', ' ', str_replace($phrases[0], "", $alert_criteria))); + + # and then create an array with the words and phrases + $criteria = explode(' ', $alert_criteria); + $criteria = array_merge($criteria, $phrases[1]); + } else { + $criteria = explode(' ', $alert_criteria); + } $words = []; + $exclusions = []; $sections = []; $sections_verbose = []; $spokenby = array_values(\MySociety\TheyWorkForYou\Utility\Search::speakerNamesForIDs($alert_criteria)); @@ -86,6 +114,8 @@ public static function prettifyCriteria($alert_criteria, $as_parts = false) { if (preg_match('#^section:(\w+)#', $c, $m)) { $sections[] = $m[1]; $sections_verbose[] = self::sectionToTitle($m[1]); + } elseif (strpos($c, '-') === 0) { + $exclusions[] = str_replace('-', '', $c); } elseif (!preg_match('#^speaker:(\d+)#', $c, $m)) { $words[] = $c; } @@ -107,6 +137,8 @@ public static function prettifyCriteria($alert_criteria, $as_parts = false) { $parts['sections'] = $sections; $parts['sections_verbose'] = $sections_verbose; } + + $parts['exclusions'] = $exclusions; } if ($as_parts) { return $parts; diff --git a/www/docs/js/main.js b/www/docs/js/main.js index 467482ce81..cd3d79f979 100644 --- a/www/docs/js/main.js +++ b/www/docs/js/main.js @@ -464,35 +464,6 @@ document.addEventListener('DOMContentLoaded', function() { createAccordion('.accordion-button', '.accordion-content'); }); -// Create alert form -$(document).ready(function() { - let currentStep = 0; - let steps = $(".alert-step"); - - // Show the first step - $(steps[currentStep]).show(); - - // Focus management: Set focus to the first input on each step change - function focusFirstInput(stepIndex) { - $(steps[stepIndex]).find('input, button').first().focus(); - } - - // Next button click - $(".next").click(function() { - $(steps[currentStep]).hide(); - currentStep++; - $(steps[currentStep]).show(); - focusFirstInput(currentStep); // Set focus to the first input of the new step - }); - - // Previous button click - $(".prev").click(function() { - $(steps[currentStep]).hide(); - currentStep--; - $(steps[currentStep]).show(); - focusFirstInput(currentStep); // Set focus to the first input of the new step - }); -}); $(function() { diff --git a/www/docs/style/sass/parts/_accordion.scss b/www/docs/style/sass/parts/_accordion.scss index 8499959cc9..b8408fd3fa 100644 --- a/www/docs/style/sass/parts/_accordion.scss +++ b/www/docs/style/sass/parts/_accordion.scss @@ -76,6 +76,10 @@ .alert-controller-wrapper { margin-bottom: 2rem; + form { + display: inline; + } + button { margin-bottom: 0; span { @@ -114,6 +118,12 @@ } } +.alert-page-alert-controls { + form { + display: inline; + } +} + .keyword-list { ul { list-style: none; @@ -174,6 +184,11 @@ button { } +.alert-page-option-label { + display: inline; + margin-left: 0.5em; +} + .button.red { background-color: $color-red; color: #fff; @@ -183,15 +198,6 @@ button { } } -// FORM -.alert-step { - display: none; -} - -.alert-step.active { - display: block; -} - #create-alert-form { label { color: $body-font-color; diff --git a/www/includes/easyparliament/alert.php b/www/includes/easyparliament/alert.php index 66493d36e8..3746aa7f33 100644 --- a/www/includes/easyparliament/alert.php +++ b/www/includes/easyparliament/alert.php @@ -104,6 +104,27 @@ public function fetch($confirmed, $deleted) { return $data; } + public function update($id, $details) { + $criteria = \MySociety\TheyWorkForYou\Utility\Alert::detailsToCriteria($details); + + $q = $this->db->query("SELECT * FROM alerts + WHERE alert_id = :id", [ + ':id' => $id, + ])->first(); + if ($q) { + $q = $this->db->query("UPDATE alerts SET deleted = 0, criteria = :criteria, confirmed = 1 + WHERE alert_id = :id", [ + ":criteria" => $criteria, + ":id" => $id, + ]); + + if ($q->success()) { + return 1; + } + } + return -1; + } + public function add($details, $confirmation_email = false, $instantly_confirm = true) { // Adds a new alert's info into the database. diff --git a/www/includes/easyparliament/templates/html/alert/_alert_form.php b/www/includes/easyparliament/templates/html/alert/_alert_form.php new file mode 100644 index 0000000000..2ba812e7e7 --- /dev/null +++ b/www/includes/easyparliament/templates/html/alert/_alert_form.php @@ -0,0 +1,256 @@ +
+
+
+
+

+ + + + +

+ +
+ + + + +
+

+ + +
+ + + + + +
+ + +
+ + + + + + $word) { ?> + + + + + + +
+ +
+ + +
+ +
+ + +
+ +
+ + + 0) { ?> + + $member) { + $name = member_full_name($member['house'], $member['title'], $member['given_name'], $member['family_name'], $member['lordofname']); + if ($member['constituency']) { + $name .= ' (' . gettext($member['constituency']) . ')'; + } ?> + +
+ + +

+ + +
+ + + +
+ + + + + + + + + + + +
+

Adding some extras

+
+

Current keywords in this alert:

+
    + +
  • +
  • + +
+
+ + +
+
+ +
+ 0) { ?> +
+
This week
+
mentions
+
+ + +
+
Date of last mention
+
30 May 2024
+
+ + + +
+ + + +
+ + + + + + + + + + + + +
+

+ +
+

:

+
    + +
  • +
  • + +
+
+ + +
+

:

+
    + +
  • +
  • + +
+
+ + +
+ 0) { ?> +

:

+
    + +
  • +
  • + +
+ +

+ +
+ + 0) { ?> +
+

+
    + +
  • +
  • + +
+
+ + +
+ 0) { ?> +
+
+
+
+ + + +
+
+
30 May 2024
+
+ + + +
+ + + + + + + +
+ +
+ +
+
+
+
diff --git a/www/includes/easyparliament/templates/html/alert/_list_accordian.php b/www/includes/easyparliament/templates/html/alert/_list_accordian.php new file mode 100644 index 0000000000..0c05ee1666 --- /dev/null +++ b/www/includes/easyparliament/templates/html/alert/_list_accordian.php @@ -0,0 +1,276 @@ +
+ $alert) { ?> +
+ + +
+ + +
+ +
+
+

Representative alerts

+
+
+ + +
+
+ + + + +
+

﹒ XXX

+ +

+
+
+ + + + + + + + +
+
+ + + +
+ + + +

Alert when is mentioned

+
+ + +
+ +
+
+ + + + +
+

+ +

+

+
+ + + + + + + + +
+
+ + + + +
+ + +

Alert when is mentioned

+
+ + +
+ +
+
+ +
diff --git a/www/includes/easyparliament/templates/html/alert/_mp_alert_form.php b/www/includes/easyparliament/templates/html/alert/_mp_alert_form.php new file mode 100644 index 0000000000..85ee60fb5c --- /dev/null +++ b/www/includes/easyparliament/templates/html/alert/_mp_alert_form.php @@ -0,0 +1,47 @@ +
+ +

+ + + + +

+ + +

+ + + + + + + +

+ +

+ + +

+ + + + + + + + + + + + + + + + + + +
diff --git a/www/includes/easyparliament/templates/html/alert/index.php b/www/includes/easyparliament/templates/html/alert/index.php index 1ee4f9243c..311c0a728d 100644 --- a/www/includes/easyparliament/templates/html/alert/index.php +++ b/www/includes/easyparliament/templates/html/alert/index.php @@ -20,7 +20,7 @@

- +

@@ -83,7 +83,7 @@

- +

@@ -104,6 +104,11 @@

+ +

+

+ +

@@ -115,7 +120,16 @@ - +

+
+

+ +
+
+ + 0) || @@ -172,82 +186,16 @@ -

+ if (!$member_options) { ?> +

-

+

- + - +
@@ -258,6 +206,30 @@

+
+

Create an alert for a phrase or keyword

+
+ + +
+ +
+ +

or

+ +
+

Create an alert when an MP speaks

+
+ + +
+
@@ -274,329 +246,21 @@

- - - - + + + +
- - - -
- $alert) { ?> -
- - -
- - -
- -
-
-

Representative alerts

- -
    -
  • - full_name()) ?>. -
    - - - -
    -
  • -
- - -
-

﹒ XXX

- -

-
- -
- - - - - - - - -
-
- - -

Alert when is mentioned

-
- - - -
-
- - - - - -
-

- -

-

- -
- - - - - - - - -
-
- - -

Alert when is mentioned

-
- - - -
-
- - -
- - - - -
-
- - - -

- full_name(); - if ($pid_member->constituency()) { - $name .= ' (' . _htmlspecialchars($pid_member->constituency()) . ')'; - } ?> - -

- -

- -

- -

- -

+ - -
- -

- - - - -

- - -

- - - - - - - - -

- - - - - - - - - - - - - - - - - - -
@@ -626,9 +290,9 @@
-
- + - + +