From f479b77c337ec68a1c96f54e47a89afac10e1e02 Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Thu, 24 Oct 2024 11:45:37 +0100 Subject: [PATCH] use a single page for alert form --- classes/AlertView/Common.php | 27 +- classes/AlertView/Standard.php | 401 +++++++++++++++++- classes/Utility/Alert.php | 27 +- .../templates/html/alert/_alert_form.php | 256 +++++++++++ .../templates/html/alert/_list_accordian.php | 276 ++++++++++++ .../templates/html/alert/_mp_alert_form.php | 40 ++ .../templates/html/alert/index.php | 397 +++-------------- 7 files changed, 1070 insertions(+), 354 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/Common.php b/classes/AlertView/Common.php index fd983a2153..85ac84b315 100644 --- a/classes/AlertView/Common.php +++ b/classes/AlertView/Common.php @@ -59,9 +59,28 @@ protected function checkInput() { protected function searchForConstituenciesAndMembers() { // Do the search - if ($this->data['alertsearch']) { + $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'] = []; } @@ -88,6 +107,12 @@ protected 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; + } } protected function addAlert() { diff --git a/classes/AlertView/Standard.php b/classes/AlertView/Standard.php index aaf450cb32..7e37959d80 100644 --- a/classes/AlertView/Standard.php +++ b/classes/AlertView/Standard.php @@ -8,7 +8,14 @@ include_once INCLUDESPATH . '../../commonlib/phplib/auth.php'; include_once INCLUDESPATH . '../../commonlib/phplib/crosssell.php'; -class Standard extends \MySociety\TheyWorkForYou\AlertView\Common { +class Standard extends \MySociety\TheyWorkForYou\AlertView { + public $data; + + public function __construct($THEUSER = null) { + parent::__construct($THEUSER); + $this->data = []; + } + public function display() { global $this_page; $this_page = "alert"; @@ -18,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(); } @@ -41,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); @@ -63,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'; @@ -72,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; @@ -86,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'] = ''; @@ -99,8 +184,308 @@ private function getBasicData() { $ACTIONURL = new \MySociety\TheyWorkForYou\Url($this_page); $ACTIONURL->reset(); $this->data['actionurl'] = $ACTIONURL->generate(); + } + + 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 + // error messages when we show the form again. + + // Check email address is valid and unique. + if (!$this->data['email']) { + $errors["email"] = gettext("Please enter your email address"); + } elseif (!validate_email($this->data["email"])) { + // validate_email() is in includes/utilities.php + $errors["email"] = gettext("Please enter a valid email address"); + } + + if ($this->data['pid'] && !ctype_digit($this->data['pid'])) { + $errors['pid'] = 'Invalid person ID passed'; + } + + $text = $this->data['alertsearch']; + if (!$text) { + $text = $this->data['keyword']; + } + + if ($this->data['submitted'] && !$this->data['pid'] && !$text) { + $errors['alertsearch'] = gettext('Please enter what you want to be alerted about'); + } + + if (strpos($text, '..')) { + $errors['alertsearch'] = gettext('You probably don’t want a date range as part of your criteria, as you won’t be alerted to anything new!'); + } + + $se = new \SEARCHENGINE($text); + if (!$se->valid) { + $errors['alertsearch'] = sprintf(gettext('That search appears to be invalid - %s - please check and try again.'), $se->error); + } - $NEWALERTURL = new \MySociety\TheyWorkForYou\Url('alertnew'); - $this->data['new_alert_url'] = $NEWALERTURL->generate(); + if (strlen($text) > 255) { + $errors['alertsearch'] = gettext('That search is too long for our database; please split it up into multiple smaller alerts.'); + } + + $this->data['errors'] = $errors; + } + + protected function searchForConstituenciesAndMembers() { + // Do the search + $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'] = []; + } + + # If the above search returned one result for constituency + # search by postcode, use it immediately + if (isset($this->data['constituencies']) && count($this->data['constituencies']) == 1 && $this->data['valid_postcode']) { + $MEMBER = new \MEMBER(['constituency' => $this->data['constituencies'][0], 'house' => 1]); + $this->data['pid'] = $MEMBER->person_id(); + $this->data['pc'] = $this->data['alertsearch']; + unset($this->data['constituencies']); + $this->data['alertsearch'] = ''; + } + + if (isset($this->data['constituencies'])) { + $cons = []; + foreach ($this->data['constituencies'] as $constituency) { + try { + $MEMBER = new \MEMBER(['constituency' => $constituency, 'house' => 1]); + $cons[$constituency] = $MEMBER; + } catch (\MySociety\TheyWorkForYou\MemberException $e) { + // do nothing + } + } + $this->data['constituencies'] = $cons; + } + + if (count($this->data["errors"]) > 0) { + $this->data["errors"] = array_merge($this->data["errors"], $errors); + } else { + $this->data["errors"] = $errors; + } + } + + 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; + } elseif ($this->data['email_verified']) { + $confirm = false; + } else { + $confirm = true; + } + + // If this goes well, the alert will be added to the database and a confirmation email + // will be sent to them. + $success = $this->alert->add($this->data, $confirm); + + if ($success > 0 && !$confirm) { + $result = 'alert-added'; + } elseif ($success > 0) { + $result = 'alert-confirmation'; + } elseif ($success == -2) { + // we need to make sure we know that the person attempting to sign up + // for the alert has that email address to stop people trying to work + // out what alerts they are signed up to + if ($this->data['email_verified'] || ($this->user->loggedin && $this->user->email() == $this->data['email'])) { + $result = 'alert-exists'; + } else { + // don't throw an error message as that implies that they have already signed + // up for the alert but instead pretend all is normal but send an email saying + // that someone tried to sign them up for an existing alert + $result = 'alert-already-signed'; + $this->alert->send_already_signedup_email($this->data); + } + } else { + $result = 'alert-fail'; + } + + // don't need these anymore so get rid of them + $this->data['keyword'] = ''; + $this->data['pid'] = ''; + $this->data['alertsearch'] = ''; + $this->data['pc'] = ''; + + $this->data['results'] = $result; + $this->data['criteria'] = $this->alert->criteria; + $this->data['display_criteria'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->alert->criteria); + } + + + 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']; + } else { + $this->data['search_text'] = $this->data['keyword']; + } + } + + protected function checkForCommonMistakes() { + $mistakes = []; + if (strstr($this->data['alertsearch'], ',') > -1) { + $mistakes['multiple'] = 1; + } + + if ( + preg_match('#([A-Z]{1,2}\d+[A-Z]? ?\d[A-Z]{2})#i', $this->data['alertsearch'], $m) && + strlen($this->data['alertsearch']) > strlen($m[1]) && + validate_postcode($m[1]) + ) { + $this->data['postcode'] = $m[1]; + $mistakes['postcode_and'] = 1; + } + + $this->data['mistakes'] = $mistakes; + } + + protected function formatSearchMemberData() { + if (isset($this->data['postcode'])) { + try { + $postcode = $this->data['postcode']; + + $MEMBER = new \MEMBER(['postcode' => $postcode]); + // move the postcode to the front just to be tidy + $tidy_alertsearch = $postcode . " " . trim(str_replace("$postcode", "", $this->data['alertsearch'])); + $alertsearch_display = str_replace("$postcode ", "", $tidy_alertsearch); + + $this->data['member_alertsearch'] = str_replace("$postcode", "speaker:" . $MEMBER->person_id, $tidy_alertsearch); + $this->data['member_displaysearch'] = $alertsearch_display; + $this->data['member'] = $MEMBER; + + if (isset($this->data['mistakes']['postcode_and'])) { + $constituencies = \MySociety\TheyWorkForYou\Utility\Postcode::postcodeToConstituencies($postcode); + if (isset($constituencies['SPC'])) { + $MEMBER = new \MEMBER(['constituency' => $constituencies['SPC'], 'house' => HOUSE_TYPE_SCOTLAND]); + $this->data['scottish_alertsearch'] = str_replace("$postcode", "speaker:" . $MEMBER->person_id, $tidy_alertsearch); + $this->data['scottish_member'] = $MEMBER; + } elseif (isset($constituencies['WAC'])) { + $MEMBER = new \MEMBER(['constituency' => $constituencies['WAC'], 'house' => HOUSE_TYPE_WALES]); + $this->data['welsh_alertsearch'] = str_replace("$postcode", "speaker:" . $MEMBER->person_id, $tidy_alertsearch); + $this->data['welsh_member'] = $MEMBER; + } + } + } catch (\MySociety\TheyWorkForYou\MemberException $e) { + $this->data['member_error'] = 1; + } + } + + if ($this->data['pid']) { + $MEMBER = new \MEMBER(['person_id' => $this->data['pid']]); + $this->data['pid_member'] = $MEMBER; + } + + if ($this->data['keyword']) { + $this->data['display_keyword'] = \MySociety\TheyWorkForYou\Utility\Alert::prettifyCriteria($this->data['keyword']); + } + } + + 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'] = []; + $own_mp_criteria = ''; + if ($this->data['email_verified']) { + if ($this->user->postcode()) { + $current_mp = new \MEMBER(['postcode' => $this->user->postcode()]); + if ($current_mp_alert = !$this->alert->fetch_by_mp($this->data['email'], $current_mp->person_id())) { + $this->data['current_mp'] = $current_mp; + $own_mp_criteria = sprintf('speaker:%s', $current_mp->person_id()); + } + } + $this->data['alerts'] = \MySociety\TheyWorkForYou\Utility\Alert::forUser($this->data['email']); + foreach ($this->data['alerts'] as $alert) { + if (array_key_exists('spokenby', $alert) and sizeof($alert['spokenby']) == 1 and $alert['spokenby'][0] == $own_mp_criteria) { + $this->data['own_member_alerts'][] = $alert; + } elseif (array_key_exists('spokenby', $alert)) { + $this->data['spoken_alerts'][] = $alert; + } else { + $this->data['all_keywords'][] = implode(' ', $alert['words']); + $this->data['keyword_alerts'][] = $alert; + } + } + } + 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 77fb1a16f0..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; } 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..350dc732e9 --- /dev/null +++ b/www/includes/easyparliament/templates/html/alert/_mp_alert_form.php @@ -0,0 +1,40 @@ +
+ +

+ + + + +

+ + +

+ + + + + + + + +

+ + + + + + + + + + + + + + + + + + +
diff --git a/www/includes/easyparliament/templates/html/alert/index.php b/www/includes/easyparliament/templates/html/alert/index.php index f875ee8cd6..a469fdb384 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) || @@ -245,9 +259,12 @@ + +

+ - +
@@ -258,6 +275,30 @@

+
+

Create an alert for a phrase or keyword

+
+ + +
+ +
+ +

or

+ +
+

Create an alert when an MP speaks

+
+ + +
+
@@ -274,343 +315,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()) . ')'; - } ?> - -

- -

- -

- -

- - - -
- -

- - - - -

- - -

- - - - - - - - -

- - - - - - - - - - - - - - - - - - -
@@ -640,9 +359,9 @@
-
- +
- + +