From e6fe3a1c642468ce1ab3ed5a11ed328a0dd8fb5f Mon Sep 17 00:00:00 2001
From: Tyler Lemburg <lemburg@unl.edu>
Date: Fri, 31 Mar 2017 11:07:08 -0500
Subject: [PATCH] Updated Google Search API to version 2

Google is deprecating version 1 of their custom search element API
sometime in April. While we don't generate actual elements using their
code, we use their javascript libraries to call the search. This
change updates the code for version 2, removing the deprecation warning
and keeping things in compliance.

Before working with this, read
https://developers.google.com/custom-search/docs/element
very carefully. The flow of the Google Search as it relates to UNL_Search
is:

1) end-scripts.tpl.php is rendered with variables that include the custom
search engine ID

2) This script, using search.js (or search.min.js), adds the google search
javascript to the end of the page asynchronously. It also gives the callback
for its loading as window.searchInit, which loads the UNLSearch.initialize
function.

3) This sets up the various search capabilities, binds callbacks onto those
actions, and has all the JS functions that we operate on in the search.

I didn't need to use the <gsce> element as described in the docs,
as we already have a text box to search, and I bind the results to
the unl_results div (local_results if a site-search is executed).

Lastly, this new code does not use the "Linked Search Engine" specification.
Instead, the as_sitesearch parameter is passed into the Google API,
or, if a specific Custom Search Engine is specified (cx parameter),
that is used vs. our regular UNL one.
---
 www/index.php                     |  27 +----
 www/js/search.js                  | 190 +++++++++++++++++-------------
 www/less/search-google.less       |  30 ++++-
 www/templates/end-scripts.tpl.php |  12 +-
 4 files changed, 155 insertions(+), 104 deletions(-)

diff --git a/www/index.php b/www/index.php
index 5cfd645..6975a62 100644
--- a/www/index.php
+++ b/www/index.php
@@ -1,5 +1,7 @@
 <?php
 
+$search_engine_id = '015236299699564929946:nk1siew10ie';
+
 $config_file = __DIR__ . '/../config.sample.php';
 if (file_exists(__DIR__ . '/../config.inc.php')) {
     $config_file = __DIR__ . '/../config.inc.php';
@@ -101,10 +103,10 @@ if (isset($_GET['u']) && $scanned = UNL_Search::getScannedPage($_GET['u'])) {
 
     if (isset($_GET['cx'])) {
         // Use their custom search engine instead of the linked one.
-        $context = $_GET['cx'];
+        $search_engine_id = $_GET['cx'];
     } else {
-        // Auto-build a custom search engine
-        $context = array('crefUrl' => UNL_Search::getLinkedCSEUrl($_GET['u']));
+        // send the site value as the context to search on
+        $context = $_GET['u'];
     }
 
     $localResults = renderTemplate('templates/google-results.tpl.php', array(
@@ -116,7 +118,6 @@ if (isset($_GET['u']) && $scanned = UNL_Search::getScannedPage($_GET['u'])) {
     loadDefaultSections($page);
 }
 
-
 $maincontent = '';
 if (!$isEmbed) {
     $maincontent .= renderTemplate('templates/search-form.tpl.php', array('local_results' => $localResults));
@@ -130,25 +131,9 @@ $maincontent .= renderTemplate('templates/search-results.tpl.php', array(
 $initialQuery = json_encode(isset($_GET['q']) ? $_GET['q'] : '');
 $context = json_encode($context);
 
-$apiKey = UNL_Search::getJSAPIKey();
-$params = array(
-    'autoload' => json_encode(array('modules' => array(
-        array(
-            'name' => 'search',
-            'version' => '1.0',
-            'callback' => 'searchInit',
-            'style' => '//www.google.com/cse/style/look/v2/default.css'
-        ),
-    ))),
-);
-
-if (!empty($apiKey)) {
-    $params['key'] = $apiKey;
-}
-
 $maincontent .= renderTemplate('templates/end-scripts.tpl.php', array(
     'localScriptUrl' => $localScriptUrl,
-    'googleLoaderUrl' => 'https://www.google.com/jsapi?' . http_build_query($params),
+    'googleLoaderUrl' => 'https://cse.google.com/cse.js?cx=' . $search_engine_id,
     'initialQuery' => $initialQuery,
     'localContext' => $context,
 ));
diff --git a/www/js/search.js b/www/js/search.js
index dabf7b3..6f70781 100644
--- a/www/js/search.js
+++ b/www/js/search.js
@@ -132,106 +132,111 @@ define(['jquery', 'analytics'], function ($, analytics) {
 		$(this._renderTo).empty();
 	};
 
+	// Google Search Wrapper Class
+	var GoogleSearcher = function(search_element, root_of_element) {
+		this.control = search_element;
+		this.control.root = root_of_element;
+	}
+
+	GoogleSearcher.prototype.setSearchCompleteCallback = function(callback) {
+		this.complete_callback = callback;
+	}
+
+	GoogleSearcher.prototype.setSearchStartingCallback = function(callback) {
+		this.starting_callback = callback;
+	}
+
+	GoogleSearcher.prototype.execute = function(query) {
+		this.starting_callback(this.control, null, query);
+		this.control.execute(query);
+		this.complete_callback(this.control);
+	}
+
 	return {
 		initialize: function(firstQ, localContext) {
 			// query related
 			var query = '';
 			var actCls = 'active';
 
-				// CustomSearchControl instances and config
-			var unlSearch;
-			var localSearch;
-			var activeSearch;
+			// CustomSearchControl instances and config
+			var activeSearch; // this is the search that is currently shown
+			var unlGoogleSearch;
+			var localGoogleSearch;
 			var directorySearch;
-			var drawOp = new google.search.DrawOptions();
 			var searchToggleLock = false;
 
 			var trackQuery = function(q) {
-					var loc = window.location,
-						qs = loc.search.replace(/(?:(\?)|&)q=[^&]*(?:&|$)/, '$1'),
-						page = [
-						    loc.pathname,
-						    qs || '?',
-						    (qs && qs != '?') ? '&' : '',
-						    'q=',
-						    encodeURIComponent(q)
-				        ].join('');
-
-					analytics.callTrackPageview(page);
-
-					if (window.history.pushState) {
-						window.history.pushState({query: q}, '', page);
-					}
+				var loc = window.location,
+					qs = loc.search.replace(/(?:(\?)|&)q=[^&]*(?:&|$)/, '$1'),
+					page = [
+					    loc.pathname,
+					    qs || '?',
+					    (qs && qs != '?') ? '&' : '',
+					    'q=',
+					    encodeURIComponent(q)
+			        ].join('');
+
+				analytics.callTrackPageview(page);
+
+				if (window.history.pushState) {
+					window.history.pushState({query: q}, '', page);
+				}
 			};
 
 			var queryComplete = function(control) {
-					var $root = $(control.root);
+				var $root = $(control.root);
 
-					// a11y patching
-					$('img.gs-image', $root).each(function() {
-						if (!this.alt) {
-							this.alt = $(this).closest('.gsc-table-result').find('.gs-title').first().text();
-						}
-					});
-					$('img.gcsc-branding-img-noclear', $root).attr('alt', 'Google™');
-
-					if (!searchToggleLock && control == localSearch && $('.gs-no-results-result', $root).length) {
-						$root.closest('.results-group').find('.result-tab li:last-child').click();
-						return;
+				// a11y patching
+				$('img.gs-image', $root).each(function() {
+					if (!this.alt) {
+						this.alt = $(this).closest('.gsc-table-result').find('.gs-title').first().text();
 					}
+				});
+				$('img.gcsc-branding-img-noclear', $root).attr('alt', 'Google™');
+
+				if (!searchToggleLock && localGoogleSearch && control == localGoogleSearch.control && $('.gs-no-results-result', $root).length) {
+					$root.closest('.results-group').find('.result-tab li:last-child').click();
+					return;
+				}
 
-					$root.closest(resultSel).addClass(actCls);
-					$root.closest(googleSel).slideDown();
+				$root.closest(resultSel).addClass(actCls);
+				$root.closest(googleSel).slideDown();
 
-					searchToggleLock = false;
+				searchToggleLock = false;
 			};
 
 			var fullQuery = function(q, track) {
-					if (track !== false) {
-						trackQuery(q);
-					}
-					try {
-						activeSearch.execute(q, undefined, {});
-					} catch (e) {
-						queryComplete(activeSearch);
-					}
-					directorySearch.execute(q);
-					$(wrapperMain).fadeIn();
+				if (track !== false) {
+					trackQuery(q);
+				}
+				try {
+					activeSearch.execute(q);
+				} catch (e) {
+					queryComplete(activeSearch.control);
+				}
+				directorySearch.execute(q);
+				$(wrapperMain).fadeIn();
 			};
 
 			var fullStop = function() {
-					activeSearch.cancelSearch();
-					directorySearch.cancelSearch();
-					$(resultSel).removeClass(actCls);
-					$(wrapperMain).fadeOut();
-					setTimeout(function() {
-						activeSearch.clearAllResults();
-						directorySearch.clearAllResults();
-					}, transitionDelay);
+				//activeSearch.cancelSearch(): // TODO: implement this
+				directorySearch.cancelSearch();
+				$(resultSel).removeClass(actCls);
+				$(wrapperMain).fadeOut();
+				setTimeout(function() {
+					activeSearch.control.clearAllResults();
+					directorySearch.clearAllResults();
+				}, transitionDelay);
 			};
 
 			var queryStart = function(control, searcher, q) {
-					$(control.root).closest(googleSel).slideUp(0);
-					if (q !== query) {
-						trackQuery(q);
-						directorySearch.execute(q);
-					}
+				$(control.root).closest(googleSel).slideUp(0);
+				if (q !== query) {
+					trackQuery(q);
+					directorySearch.execute(q);
+				}
 			};
 
-			drawOp.enableSearchResultsOnly();
-
-			unlSearch = activeSearch = new google.search.CustomSearchControl(unlContext);
-			unlSearch.setResultSetSize(google.search.Search.FILTERED_CSE_RESULTSET);
-			unlSearch.setSearchCompleteCallback(window, queryComplete);
-			unlSearch.setSearchStartingCallback(window, queryStart);
-
-			if (localContext) {
-				localSearch = activeSearch = new google.search.CustomSearchControl(localContext);
-				localSearch.setResultSetSize('small');
-				localSearch.setSearchCompleteCallback(window, queryComplete);
-				localSearch.setSearchStartingCallback(window, queryStart);
-			}
-
 			directorySearch = new Directory(directoryServer, dirResults);
 
 			// Setup DOM on ready
@@ -285,11 +290,39 @@ define(['jquery', 'analytics'], function ($, analytics) {
 						}
 					};
 
-				// draw the Google search controls
-				unlSearch.draw(unlResults, drawOp);
+				var render_attrs = {
+					div: 'unl_results',
+					tag: 'searchresults-only',
+					attributes: {
+						enableImageSearch: false
+					}
+				}
+				// draw the Google search results stuff
+				google.search.cse.element.render(render_attrs);
+				// bind the search results element to this class
+				var searchElement = google.search.cse.element.getAllElements()['searchresults-only0'];
+				window.googleSearchElement = searchElement;
+				unlGoogleSearch = new GoogleSearcher(searchElement, $('#unl_results'));
+				unlGoogleSearch.setSearchStartingCallback(queryStart);
+				unlGoogleSearch.setSearchCompleteCallback(queryComplete);
+				activeSearch = unlGoogleSearch;
 
 				if (localContext) {
-					localSearch.draw(localResults, drawOp);
+					var render_attrs_local = {
+						div: 'local_results',
+						tag: 'searchresults-only',
+						attributes: {
+							enableImageSearch: false,
+							as_sitesearch: localContext
+						}
+					}
+					google.search.cse.element.render(render_attrs_local);
+					var localSearchElement = google.search.cse.element.getAllElements()['searchresults-only1'];
+					window.localGoogleSearchElement = searchElement;
+					localGoogleSearch = new GoogleSearcher(localSearchElement, $('#local_results'));
+					localGoogleSearch.setSearchStartingCallback(queryStart);
+					localGoogleSearch.setSearchCompleteCallback(queryComplete);
+					activeSearch = localGoogleSearch;
 				}
 
 				// a11y patch Google search box
@@ -311,13 +344,12 @@ define(['jquery', 'analytics'], function ($, analytics) {
 						directorySearch.changeViewState(i);
 					} else if ($par.is(wrapperWeb)) {
 						searchToggleLock = true;
-						$(activeSearch.root).closest(googleSel).slideUp().trigger(evtStateChange, [i, 0 + !i]);
+						$(activeSearch.control.root).closest(googleSel).slideUp().trigger(evtStateChange, [i, 0 + !i]);
 						if (i === 0) {
-							activeSearch = localSearch;
+							activeSearch = localGoogleSearch;
 						} else {
-							activeSearch = unlSearch;
+							activeSearch = unlGoogleSearch;
 						}
-
 						activeSearch.execute(query);
 					}
 				});
diff --git a/www/less/search-google.less b/www/less/search-google.less
index 1c4e7eb..2af06ab 100644
--- a/www/less/search-google.less
+++ b/www/less/search-google.less
@@ -11,6 +11,29 @@
 	font-size: inherit;
 }
 
+.gsc-thumbnail-inside {
+	padding-left: 0;
+	padding-right: 0;
+}
+
+.gsc-control-cse .gs-snippet {
+	color: inherit;
+}
+
+.gsc-thumbnail .gs-image-box {
+	padding-right: 0.5em;
+}
+
+.gsc-url-top {
+	font-size: 80%;
+	padding-left: 0;
+	padding-right: 0;
+}
+
+.gsc-control-cse {
+	padding: 0;
+}
+
 .gsc-result {
 
 	.gsc-webResult & {
@@ -21,6 +44,10 @@
 	.gs-title {
 		height: 1.662em;
 	}
+
+	&.gsc-webResult:hover {
+		border: none;
+	}
 }
 
 .gs-result {
@@ -29,7 +56,7 @@
 		text-decoration: none;
 	}
 
-	a.gs-visibleUrl, .gs-visibleUrl {
+	a.gs-visibleUrl, .gs-visibleUrl, .gsc-url-top {
 		color: @light-neutral;
 	}
 }
@@ -69,6 +96,7 @@
 
 .gcsc-branding {
 	margin-bottom: 1.3333em;
+	display: none;
 }
 
 td.gcsc-branding-text {
diff --git a/www/templates/end-scripts.tpl.php b/www/templates/end-scripts.tpl.php
index 63c71e6..81688c6 100644
--- a/www/templates/end-scripts.tpl.php
+++ b/www/templates/end-scripts.tpl.php
@@ -1,15 +1,21 @@
 <script>
 require(['jquery', '<?php echo $localScriptUrl ?>'], function($, UNLSearch) {
-	var gSearchDefer = $.Deferred();
+    var gSearchDefer = $.Deferred();
 	window.searchInit = function() {
         window.searchInit = $.noop;
         gSearchDefer.resolve(google);
     };
 
+    window.__gcse = {
+      parsetags: 'explicit',
+      callback: window.searchInit
+    };
+
     $('<script>', {
         src: '<?php echo $googleLoaderUrl ?>',
-        asycn: 'async',
-        defer: 'defer'
+        async: 'async',
+        defer: 'defer',
+        type: 'text/javascript'
     }).appendTo($('body'));
 
     var $localCss = $('<link>', {
-- 
GitLab