Alpha version of new Search add-on for GLM Associate.
authorChuck Scott <cscott@gaslightmedia.com>
Fri, 24 Jun 2016 19:59:02 +0000 (15:59 -0400)
committerChuck Scott <cscott@gaslightmedia.com>
Fri, 24 Jun 2016 19:59:02 +0000 (15:59 -0400)
Ready for initial integration.

16 files changed:
classes/data/dataManagement.php [new file with mode: 0644]
classes/glmSearch.php [new file with mode: 0644]
css/front.css [new file with mode: 0644]
index.php
models/admin/management/search.php [new file with mode: 0644]
models/front/search/index.php [new file with mode: 0644]
setup/adminTabs.php
setup/databaseScripts/create_database_V0.0.1.sql [new file with mode: 0644]
setup/databaseScripts/dbVersions.php [new file with mode: 0644]
setup/shortcodes.php
setup/validActions.php
views/admin/management/search.html [new file with mode: 0644]
views/front/footer.html [new file with mode: 0644]
views/front/search/Sitemap_XML_0.9.html [new file with mode: 0644]
views/front/search/header.html [new file with mode: 0644]
views/front/search/index.html [new file with mode: 0644]

diff --git a/classes/data/dataManagement.php b/classes/data/dataManagement.php
new file mode 100644 (file)
index 0000000..654c72f
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+/**
+ * GLM Member-DB WordPress Add-On Plugin
+ * Search Management data class
+ *
+ * PHP version 5.3
+ *
+ * @category Data
+ * @package  GLM Member-DB
+ * @author   Chuck Scott <cscott@gaslightmedia.com>
+ * @license  http://www.gaslightmedia.com Gaslightmedia
+ * @release  SVN: $Id: dataSearchManagement.php,v 1.0 2011/01/25 19:31:47 cscott Exp $
+ */
+
+/**
+ * GlmDataManagementSearch class
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package GLM Member DB
+ * @author  Chuck Scott <cscott@gaslightmedia.com>
+ * @license http://www.gaslightmedia.com Gaslightmedia
+ *          @release SVN: $Id: dataSearchManagement.php,v 1.0 2011/01/25 19:31:47 cscott
+ *          Exp $
+ */
+class GlmDataSearchManagement extends GlmDataAbstract
+{
+
+    /**
+     * WordPress Database Object
+     *
+     * @var $wpdb
+     * @access public
+     */
+    public $wpdb;
+    /**
+     * Plugin Configuration Data
+     *
+     * @var $config
+     * @access public
+     */
+    public $config;
+    /**
+     * Data Table Name
+     *
+     * @var $table
+     * @access public
+     */
+    public $table;
+    /**
+     * Field definitions
+     *
+     * 'type' is type of field as defined by the application
+     * text Regular text field
+     * pointer Pointer to an entry in another table
+     * 'filters' is the filter name for a particular filter ID in PHP filter
+     * functions
+     * See PHP filter_id()
+     *
+     * 'use' is when to use the field
+     * l = List
+     * g = Get
+     * n = New
+     * i = Insert
+     * e = Edit
+     * u = Update
+     * d = Delete
+     * a = All
+     *
+     * @var $ini
+     * @access public
+     */
+    public $fields = false;
+
+    /**
+     * Constructor
+     *
+     * @param object $d database connection
+     * @param array $config Configuration array
+     * @param bool $limitedEdit Flag to say indicate limited edit requested
+     *
+     * @return void
+     * @access public
+     */
+    public function __construct($wpdb, $config, $limitedEdit = false)
+    {
+
+        // If this class is not being extended along with existing $wpdb and $config
+        if (!$this->wpdb) {
+
+            // Save WordPress Database object
+            $this->wpdb = $wpdb;
+
+            // Save plugin configuration object
+            $this->config = $config;
+
+        }
+
+        /*
+         * Table Name
+         */
+        $this->table = GLM_MEMBERS_SEARCH_PLUGIN_DB_PREFIX . 'management';
+
+        /*
+         * Table Data Fields
+         */
+
+        $this->fields = array (
+
+            'id' => array (
+                'field' => 'id',
+                'type' => 'integer',
+                'view_only' => true,
+                'use' => 'a'
+            ),
+
+            // OSS Search Index
+            'search_index' => array (
+                'field' => 'search_index',
+                'type' => 'text',
+                'required' => true,
+                'use' => 'a'
+            ),
+
+            // OSS Search Website
+            'website' => array (
+                'field' => 'website',
+                'type' => 'text',
+                'required' => true,
+                'use' => 'a'
+            ),
+
+            // OSS Search Login
+            'login' => array (
+                'field' => 'login',
+                'type' => 'text',
+                'required' => true,
+                'use' => 'a'
+            ),
+
+            // OSS Search Login Key
+            'login_key' => array (
+                'field' => 'login_key',
+                'type' => 'text',
+                'required' => true,
+                'use' => 'a'
+            )
+
+
+        );
+
+    }
+
+
+}
+
+?>
\ No newline at end of file
diff --git a/classes/glmSearch.php b/classes/glmSearch.php
new file mode 100644 (file)
index 0000000..e609c69
--- /dev/null
@@ -0,0 +1,598 @@
+<?php\r
+/**\r
+ * GLM Search Engine - Support Classes\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * @category Support_Services\r
+ * @package  GLMSearch\r
+ * @author   Chuck Scott <cscott@gaslightmedia.com>\r
+ * @license  http://www.gaslightmedia.com Gaslightmedia\r
+ * @release  SVN: $Id: glmSearch.inc,v 1.0 2011/01/25 19:31:47 cscott Exp $\r
+ * @link\r
+ */\r
+\r
+/**** NOTE : THIS FILE HAS NOT HAD A THOUROUGH REVIEW SINCE BEING GRABBED FROM THE OLD GLM COMMON APPS *****/\r
+\r
+/*\r
+ * Set OpenSearchServer base URL and port\r
+ */\r
+define('OpenSearchServer', 'http://oss.gaslightmedia.com:9090');\r
+\r
+/**\r
+ * GLM Search - Web Site Search Support Class\r
+ *\r
+ * PHP version 5\r
+ *\r
+ * @category Support_Services\r
+ * @package  EventManagement\r
+ * @author   Chuck Scott <cscott@gaslightmedia.com>\r
+ * @license  http://www.gaslightmedia.com Gaslightmedia\r
+ * @release  SVN: $Id: GeoCalculations.php,v 1.0 2011/01/25 19:31:47 cscott Exp $\r
+ * @link     http://housing.gaslightmedia.com/admin/\r
+ */\r
+class GLMSearch\r
+{\r
+\r
+    /**\r
+     * Name of the index an instance of this class will search.\r
+     * An index can have multiple Websites in it. If when calling\r
+     * the constructor no $index is specified, the code will assume\r
+     * that the index name and website name are the same. Otherwise\r
+     * it will use $index as the index name.\r
+     *\r
+     * @var    $index\r
+     * @access private\r
+     */\r
+    private $index;\r
+    /**\r
+     * Name of the Web site an instance of this class will work on\r
+     *\r
+     * @var    $website\r
+     * @access private\r
+     */\r
+    private $website;\r
+    /**\r
+     * Authentication paramters for all requests\r
+     *\r
+     * @var    $auth\r
+     * @access private\r
+     */\r
+    private $auth;\r
+    /**\r
+     * General error message describing any failure\r
+     *\r
+     * @var    $curlError\r
+     * @access public\r
+     */\r
+    public $errorMessage;\r
+    /**\r
+     * Exception object caused by curl or other failure\r
+     *\r
+     * @var    $error\r
+     * @access public\r
+     */\r
+    public $error;\r
+    /**\r
+     * Request array\r
+     *\r
+     * This should be the most common request configuration\r
+     * we'll be using for our Web sites. It's public so it\r
+     * can be easily customized for a particular Web site\r
+     * once the class has been instantiated.\r
+     *\r
+     * See clearResults() method for default data\r
+     *\r
+     * @var    $request Array of request information\r
+     * @access public\r
+     */\r
+    public $request = false;\r
+    /**\r
+     * Search type\r
+     *\r
+     * This selects whether a search will be a 'field' search or 'pattern' search\r
+     *\r
+     * @var    $type    String Either 'field' or 'pattern'\r
+     * @access private\r
+     */\r
+    private $type = 'field';\r
+    /**\r
+     * Curl instance\r
+     *\r
+     * @var    $curl\r
+     * @access private\r
+     */\r
+    private $curl = false;\r
+     /**\r
+     * HTTP Status from curl\r
+     *\r
+     * @var    $httpStatus\r
+     * @access public\r
+     */\r
+    public $httpStatus = false;\r
+    /**\r
+     * HTTP Header from curl\r
+     *\r
+     * @var    $httpHeader\r
+     * @access public\r
+     */\r
+    public $httpHeader = false;\r
+     /**\r
+     * Raw Result of curl request\r
+     *\r
+     * Public so it can easily be examined\r
+     *\r
+     * @var    $rawResult\r
+     * @access public\r
+     */\r
+    public $rawResults = false;\r
+    /**\r
+     * Full results as an array\r
+     *\r
+     * @var    $resultArray\r
+     * @access private\r
+     */\r
+    private $resultArray = false;\r
+    /**\r
+     * Compressed results array\r
+     *\r
+     * This array has better processed data for output\r
+     * and has trimmed levels for document data for\r
+     * easier use.\r
+     *\r
+     * @var    $result\r
+     * @access private\r
+     */\r
+    private $result = false;\r
+    /**\r
+     * Host Filter flag\r
+     *\r
+     * @var    $filter\r
+     * @access public\r
+     */\r
+    public $filter = true;\r
+    /**\r
+     * Host Filter Type\r
+     *\r
+     * @var    $filterType\r
+     * @access public\r
+     */\r
+    public $filterType = false;\r
+    /**\r
+     * Host Filter Value\r
+     *\r
+     * @var    $filterValue\r
+     * @access public\r
+     */\r
+    public $filterValue = false;\r
+\r
+    /**\r
+     * Constructor\r
+     *\r
+     * @param text $index       Optional index name if Web site is part of a larger index\r
+     * @param string $website   Name of the Web site\r
+     * @param string $login     Login to OpenSearchServer\r
+     * @param string $key       Key for login\r
+     * @param text $filterType  Optional filter type (when $this->filter is true).\r
+     *                          Filter types are "title", "titleExact", "titlePhonetic", "content",\r
+     *                          "contentExact", "contactPhonetic", "urlSplit", "urlExact", "urlPhonetic",\r
+     *                          "full", "fullExact", "fullPhonetic"\r
+     * @param text $filterValue Filter value to use with $filterType.\r
+     *\r
+     * @return void\r
+     * @access public\r
+     */\r
+    public function __construct($index, $website, $login, $key, $filterType = false, $filterValue = false)\r
+    {\r
+\r
+        // If there's a problem with Filter and validate supplied parameters\r
+        if (\r
+            ($website = filter_var($website, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH)) == false ||\r
+            ($login = filter_var($login, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH)) == false ||\r
+            ($key = filter_var($key, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH)) == false\r
+        ) {\r
+            return false;\r
+        }\r
+\r
+        // Store supplied parameters\r
+        $this->index = $index;\r
+        $this->website = $website;\r
+\r
+        // Build authentication parameters for submissions\r
+        $this->auth = "login=$login&key=$key";\r
+\r
+        // Create curl instance - and set to return response\r
+        $this->curl = curl_init();\r
+        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, TRUE);\r
+\r
+    }\r
+\r
+    /**\r
+     * Perform a search on the Web site\r
+     *\r
+     * @param string    $query      The user's search string\r
+     * @param string    $operator   Operator for the query terms (AND, OR)\r
+     *                                  Default = 'OR'\r
+     * @param integer   $start      Starting result number\r
+     *                                  Default = 0 (first page)\r
+     * @param integer   $rows       Number of rows per result page\r
+     *                                  Default = 10\r
+     *\r
+     * @return string   JSON of results or false if a failure\r
+     *\r
+     * @access public\r
+     */\r
+    public function glmSearch($query, $operator = 'OR', $start = 0, $rows = 10)\r
+    {\r
+\r
+        // Clear all the results data\r
+        $this->clearResults();\r
+\r
+        // Filter and validate supplied parameters\r
+        if (\r
+            (filter_var($query, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH)) == false ||\r
+            (in_array($operator, array('OR', 'AND'))) == false\r
+        ) {\r
+            return false;\r
+        }\r
+\r
+        // strip any characters that cause a problem. Add more characters to array if needed.\r
+        $remove = array("'");\r
+        $query = htmlspecialchars_decode($query, ENT_QUOTES);\r
+        $query = str_replace("'", "", $query);\r
+\r
+        $start = ($start - 0);\r
+        $rows = ($rows - 0);\r
+\r
+\r
+        // Check if we have a valid query\r
+        if (trim($query) == '') {\r
+            return false;\r
+        }\r
+\r
+        // Create request URL\r
+        switch ($this->type) {\r
+               case 'field':\r
+                $url = OpenSearchServer.'/services/rest/index/'.$this->index.'/search/field/?'.$this->auth;\r
+                break;\r
+               case 'pattern':\r
+                   $url = OpenSearchServer.'/services/rest/index/'.$this->index.'/search/pattern/?'.$this->auth;\r
+                   break;\r
+        }\r
+\r
+        // Update query and operator in request\r
+        $this->request['query'] = $query;\r
+        $this->request['start'] = $start;\r
+        $this->request['rows'] = $rows;\r
+        $this->request['operator'] = $operator;\r
+\r
+        // If host filter has not been turned off, set filter to only search the specified Website in the index\r
+        if ($this->filter) {\r
+\r
+            // Determine if there's a specific filter type and value - Default to host:\r
+            $query = 'host:"'.$this->website.'"';\r
+            if ($this->filterType) {\r
+                if ($this->filterValue) {\r
+                    $query = $this->filterType.':"'.$this->filterValue.'"';\r
+                } else {\r
+                    $query = $this->filterType.':"'.$this->website.'"';\r
+                }\r
+            }\r
+\r
+            // Set filter\r
+            $this->request['filters'] = array(\r
+               array(\r
+                    "type" => "QueryFilter",\r
+                    "negative" => false,\r
+                    "query" => $query\r
+               )\r
+            );\r
+\r
+        } else {\r
+            unset($this->request['filters']);\r
+        }\r
+\r
+        // Set curl options\r
+        curl_setopt_array($this->curl, array(\r
+            CURLOPT_URL => $url,\r
+            CURLOPT_POST => true,\r
+            CURLOPT_POSTFIELDS => json_encode($this->request),\r
+            CURLOPT_HTTPHEADER => array("Content-type: application/json; charset=utf-8"),\r
+            CURLOPT_VERBOSE => 1,\r
+            CURLOPT_HEADER => 1\r
+        ));\r
+\r
+        // Do curl call and get result;\r
+        try {\r
+            $res = curl_exec($this->curl);\r
+        } catch (Exception $e) {\r
+            return false;\r
+        }\r
+\r
+        // Parse out results elements\r
+        $this->httpStatus = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);\r
+        $header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);\r
+        $this->httpHeader = substr($res, 0, $header_size);\r
+        $this->rawResults = substr($res, $header_size);\r
+\r
+        // Check HTTP status code\r
+            if ($this->httpStatus != 200) {\r
+            return false;\r
+        }\r
+\r
+        $this->resultArray = json_decode($this->rawResults, true);\r
+\r
+        $res = $this->compressResults();\r
+\r
+        return $res;\r
+    }\r
+\r
+    /**\r
+     * Compress search results as associative array into\r
+     * more succinct result array for use with output template.\r
+     *\r
+     * @param $res Uncompressed search results array\r
+     *\r
+     * @return array Array of results or false if a failure\r
+     *\r
+     * @access public\r
+     */\r
+    public function compressResults()\r
+    {\r
+\r
+        // Check if we don't have valid input\r
+        if (!is_array($this->resultArray) || !$this->resultArray['successful']) {\r
+            return false;\r
+        }\r
+\r
+        // Calculate certain paging values\r
+        $rowsPerPage = $this->resultArray['rows'];\r
+        $thisPageStartIndex = $this->resultArray['start'];                  // Based on first result being 0\r
+        $thisPageStart = $thisPageStartIndex + 1;                           // Displayed first result number\r
+        $numbResults = $this->resultArray['numFound'];\r
+        $numbResultsOnPage = count($this->resultArray['documents']);\r
+        $lastResultOnPage = $thisPageStart + $numbResultsOnPage - 1;\r
+        $previousPageStartIndex = $thisPageStartIndex - $rowsPerPage;\r
+        if ($previousPageStartIndex < 0) {\r
+            $previousPageStartIndex = false;\r
+        }\r
+        $nextPageStartIndex = $thisPageStartIndex + $rowsPerPage;\r
+        if ($nextPageStartIndex > ($numbResults - 1)) {\r
+            $nextPageStartIndex = false;\r
+        }\r
+\r
+        // Build basic search results array\r
+        $this->result = array(\r
+            'resultsPerPage' => $this->resultArray['rows'],\r
+            'operator' => $this->request['operator'],\r
+            'totalResults' => $numbResults,\r
+            'firstResultOnPage' => $thisPageStart,\r
+            'resultsOnPage' => $numbResultsOnPage,\r
+            'lastResultOnPage' => $lastResultOnPage,\r
+            'previousPageStartIndex' => $previousPageStartIndex,\r
+            'nextPageStartIndex' => $nextPageStartIndex,\r
+            'documents' => array()\r
+        );\r
+\r
+        // Add in results for each document\r
+        foreach ($this->resultArray['documents'] as $d) {\r
+\r
+            // Base document information\r
+            $doc = array(\r
+               'score' => $d['score'],\r
+                'collapseCount' => $d['collapseCount']\r
+            );\r
+\r
+            // Document fields (simple text fields)\r
+            foreach ($d['fields'] as $df) {\r
+                $doc[$df['fieldName']] = array(\r
+                    'type' => 'field',\r
+                    'name' => $df['fieldName'],\r
+                    'value' => (isset($df['values'][0]) ? $df['values'][0] : '')\r
+                );\r
+\r
+            }\r
+\r
+            // Document snippets (possible multiple snippets per each)\r
+            foreach ($d['snippets'] as $ds) {\r
+                $doc[$ds['fieldName']] = array(\r
+                    'type' => 'snippets',\r
+                       'name' => $ds['fieldName'],\r
+                    'snippets' => array()\r
+                );\r
+\r
+                // Process possible multiple snippet values\r
+                if (is_array($ds['values']) && count($ds['values']) > 0) {\r
+                    foreach ($ds['values'] as $sv) {\r
+                        $doc[$ds['fieldName']]['snippets'][] = $sv;\r
+                    }\r
+                }\r
+\r
+            }\r
+\r
+            // Need to add processing of "functions" also here\r
+\r
+            // Look for null or blank title\r
+            if (empty($doc['title']['value']) && !empty($doc['url']['value'])) {\r
+                $doc['title']['value'] = $doc['url']['value'];\r
+            }\r
+\r
+            // Clean up lastModifiedDate if included\r
+            if (isset($doc['lastModifiedDate']) && isset($doc['lastModifiedDate']['value'])) {\r
+                $d = $doc['lastModifiedDate']['value'];\r
+                $doc['lastModifiedDate']['textValue'] = '';\r
+                if (trim($d) != '') {\r
+                    $doc['lastModifiedDate']['textValue'] = substr($d, 0, 4).'-'.substr($d,4,2).'-'.substr($d,6,2);\r
+                }\r
+            }\r
+\r
+            // Add this document to result documents array\r
+            $this->result['documents'][] = $doc;\r
+\r
+        }\r
+\r
+        return $this->result;\r
+    }\r
+\r
+    /**\r
+     * Create an XML Sitemap from search results\r
+     *\r
+     * @param string $template Template to use for producing results (override default)\r
+     * @param array $request Array of request parameters\r
+     *      'query'         Required - Search String\r
+     *      'matchAll'      Optional - Match all words option ('AND', 'OR')\r
+     *      'start'         Optional - Start index\r
+     *      'rows'          Optional - Results per page\r
+     *\r
+     * @return void\r
+     * @access public\r
+     */\r
+    public function xmlSitemap($templateName = 'Sitemap_XML_0.9.html')\r
+    {\r
+\r
+        // Search for * (matches everything)\r
+        $request = array(\r
+               'query' => 'a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9',\r
+            'matchAll' => 'OR',\r
+            'start' => 0,\r
+            'rows' => 50000\r
+        );\r
+        $page = $this->doSearch($templateName, GLMSearch_TEMPLATE_PATH, $request);\r
+        return $page;\r
+    }\r
+\r
+    /**\r
+     * Tell OpenSearchServer to crawl a particular URL pattern\r
+     *\r
+     * @param integer   $pattern  URL Pattern\r
+     *\r
+     * @return array    Object    Results from Search Engine or false if failure.\r
+     *\r
+     * @access public\r
+     */\r
+    public function crawlURL($pattern)\r
+    {\r
+\r
+        // Clear all the results data\r
+        $this->clearResults();\r
+\r
+        // Filter and validate supplied parameters\r
+        if (($pattern = filter_var($pattern, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH)) == false) {\r
+            return false;\r
+        }\r
+\r
+        // Create request URL\r
+        $param = 'url='.$pattern;\r
+        $url = OpenSearchServer.'/services/rest/index/'.$this->index.'/crawler/web/crawl?'.$this->auth.'&'.$param;\r
+\r
+        // Set curl options\r
+        curl_setopt_array($this->curl, array(\r
+            CURLOPT_URL => $url,\r
+            CURLOPT_POST => false,\r
+            CURLOPT_VERBOSE => 1,\r
+            CURLOPT_HEADER => 1\r
+        ));\r
+\r
+        // Do curl call and get result;\r
+        try {\r
+            $res = curl_exec($this->curl);\r
+        } catch (Exception $e) {\r
+            return false;\r
+        }\r
+\r
+        // Parse out results elements\r
+        $this->httpStatus = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);\r
+        $header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);\r
+        $this->httpHeader = substr($res, 0, $header_size);\r
+        $this->rawResults = substr($res, $header_size);\r
+\r
+        // Check HTTP status code\r
+        if ($this->httpStatus != 200) {\r
+            return false;\r
+        }\r
+\r
+        // Debug information\r
+        $this->errorMessage = 'crawlURL() - Completed successfully.';\r
+\r
+        return json_decode($this->rawResults);\r
+    }\r
+\r
+    /**\r
+     * Clear all request and results data - used before a new curl call\r
+     *\r
+     * @return void\r
+     * @access private\r
+     */\r
+    private function clearResults()\r
+    {\r
+\r
+        /*\r
+         * Reset request to default\r
+         * (Note: Need to add more explanation for this data.)\r
+         */\r
+        $this->request = array(\r
+            'query' => false,                       // Query String\r
+            'start' => 0,                           // Result number for start of page\r
+            'rows' => 10,                           // Number of rows to display per page\r
+            'lang' => 'ENGLISH',                    // Language (in all caps)\r
+            'operator' => 'AND',                    // Either AND or OR - And matches pages containing all words\r
+            'collapsing' => array(\r
+                'max' => 2,\r
+                'mode' => 'OFF',\r
+                'type' => 'OPTIMIZED'\r
+            ),\r
+            'geo' => null,\r
+            'filters' => null,\r
+            'sorts' => null,\r
+            'returnedFields' => array(\r
+                'url',\r
+                'title',\r
+                'lastModifiedDate'\r
+            ),\r
+            'snippets' => array(\r
+                array(\r
+                    'field' => 'content',\r
+                    'tag' => 'b',\r
+                    'separator' => '...',\r
+                    'maxSize' => 300,\r
+                    'maxNumber' => 1,\r
+                    'fragmenter' => 'SENTENCE'\r
+                )\r
+            ),\r
+            'facets' => null,\r
+            'joins' => null,\r
+            'enableLog' => null,\r
+            'customLogs' => null,\r
+            'searchFields' => array(\r
+                array(\r
+                    'field' => 'title',\r
+                    'boost'=> 10\r
+                ),\r
+                array(\r
+                    'field' => 'content',\r
+                    'boost'=> 1\r
+                ),\r
+                array(\r
+                    'field' => 'titleExact',\r
+                    'boost'=> 10\r
+                ),\r
+                array(\r
+                    'field' => 'contentExact',\r
+                    'boost'=> 1\r
+                )\r
+            )\r
+        );\r
+        // Clear results\r
+        $this->httpStatus = false;\r
+        $this->httpHeader = false;\r
+        $this->rawResults = false;\r
+        $this->resultArray = false;\r
+        $this->result = false;\r
+        $this->errorMessage = false;\r
+        $this->error = false;\r
+\r
+    }\r
+\r
+}\r
+\r
+?>\r
diff --git a/css/front.css b/css/front.css
new file mode 100644 (file)
index 0000000..996ef06
--- /dev/null
@@ -0,0 +1,43 @@
+/* Search Results Styling */
+#GLMSearch {
+    padding: 1em;
+}
+#GLMSformContainer {}
+#GLMSformContainer input {}
+.GLMSresultsHeader {
+    margin-top: 1em;
+    margin-bottom: 2em;
+}
+.GLMSresultsSummary {}
+.GLMSresultsNavContainer {}
+.GLMSresultsNavContainer a {}
+.GLMSresultsNavInactive {
+    color: lightgray;
+}
+#GLMSresultsContainer {
+}
+.GLMSresultContainer {
+    color: #1E130A;
+    font-family: Arial,Helvetica,sans-serif;
+    font-size: 1em;
+    margin-bottom: 2em;
+}
+.GLMSresultTitle {
+    color: #0000CC;
+    text-decoration: underline;
+    font-weight: bold;
+    margin-bottom: .3em;
+}
+.GLMSresultText {}
+.GLMSresultSnippets {}
+.GLMSresultSnippets b {
+    border: solid lightgray 1px;
+    padding: .1em;
+}
+.GLMSresultUrl {
+    color: #008000;
+    margin-top: .2em;
+}
+#slideshow {
+    display: none;
+}
index 4c74af5..8f639bd 100644 (file)
--- a/index.php
+++ b/index.php
@@ -4,7 +4,7 @@
  * Plugin URI: http://www.gaslightmedia.com/
  * Description: Gaslight Media Members Database.
  * Version: 0.0.1
- * Author: Chuck Scott
+ * Author: Gaslight Media
  * Author URI: http://www.gaslightmedia.com/
  * License: GPL2
  */
@@ -38,7 +38,7 @@
  *  version from this plugin.
  */
 define('GLM_MEMBERS_SEARCH_PLUGIN_VERSION', '0.0.1');
-// define('GLM_MEMBERS_SEARCH_PLUGIN_DB_VERSION', '0.0.1');
+define('GLM_MEMBERS_SEARCH_PLUGIN_DB_VERSION', '0.0.1');
 
 // This is the minimum version of the GLM Members DB plugin require for this plugin.
 define('GLM_MEMBERS_SEARCH_PLUGIN_MIN_MEMBERS_REQUIRED_VERSION', '1.0.57');
@@ -143,12 +143,10 @@ if (is_file(GLM_MEMBERS_SEARCH_PLUGIN_DB_SCRIPTS.'/dbVersions.php')) {
 }
 
 // Load Search Management Settings data
-/* None - Need to figure out a smooth way to do this.
 $searchManagementSettings = $wpdb->get_row( "SELECT * FROM ".GLM_MEMBERS_SEARCH_PLUGIN_DB_PREFIX."management WHERE id = 1", ARRAY_A );
 unset($searchManagementSettings['id']);
-*/
 
-function glmMembersRegisterSsearch($addOns) {
+function glmMembersRegisterSearch($addOns) {
 
     // Add this add-on to the add-ons array
     $addOns[GLM_MEMBERS_SEARCH_PLUGIN_SLUG] =  array(
@@ -158,6 +156,7 @@ function glmMembersRegisterSsearch($addOns) {
         'slug' => GLM_MEMBERS_SEARCH_PLUGIN_SLUG,
         'actions' => $GLOBALS['glmMembersSearchAddOnValidActions'],
         'config' => array(
+                'settings' => $GLOBALS['searchManagementSettings']
         ),
         'shortcodes' => $GLOBALS['glmMembersSearchShortcodes'],
         'shortcodesDescription' => $GLOBALS['glmMembersSearchShortcodesDescription']
diff --git a/models/admin/management/search.php b/models/admin/management/search.php
new file mode 100644 (file)
index 0000000..9e7f066
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+/**
+ * Gaslight Media Members Database
+ * GLM Members DB - Search Add-on - Management Search Tab
+ *
+ * PHP version 5.5
+ *
+ * @category glmWordPressPlugin
+ * @package  glmMembersDatabase
+ * @author   Chuck Scott <cscott@gaslightmedia.com>
+ * @license  http://www.gaslightmedia.com Gaslightmedia
+ * @release  search.php,v 1.0 2014/10/31 19:31:47 cscott Exp $
+ * @link     http://dev.gaslightmedia.com/
+ */
+
+// Load Management Search data abstract
+require_once(GLM_MEMBERS_SEARCH_PLUGIN_CLASS_PATH.'/data/dataManagement.php');
+
+/**
+ * GlmMembersAdmin_management_search
+ *
+ * PHP version 5
+ *
+ * @category Model
+ * @package GLM Member DB
+ * @author  Chuck Scott <cscott@gaslightmedia.com>
+ * @license http://www.gaslightmedia.com Gaslightmedia
+ *          @release SVN: $Id: search.php,v 1.0 2011/01/25 19:31:47 cscott
+ *          Exp $
+ */
+class GlmMembersAdmin_management_search extends GlmDataSearchManagement
+{
+
+    /**
+     * WordPress Database Object
+     *
+     * @var $wpdb
+     * @access public
+     */
+    public $wpdb;
+
+    /*
+     * Constructor
+     *
+     * This contructor performs the work for this model. This model returns
+     * an array containing the following.
+     *
+     * 'status'
+     *
+     * True if successfull and false if there was a fatal failure.
+     *
+     * 'view'
+     *
+     * A suggested view name that the contoller should use instead of the
+     * default view for this model or false to indicate that the default view
+     * should be used.
+     *
+     * 'data'
+     *
+     * Data that the model is returning for use in merging with the view to
+     * produce output.
+     *
+     * @wpdb object WordPress database object
+     *
+     * @return array Array containing status, suggested view, and any data
+     */
+    public function __construct ($wpdb, $config)
+    {
+
+        // Save WordPress Database object
+        $this->wpdb = $wpdb;
+
+        // Save plugin configuration object
+        $this->config = $config;
+
+        // Run constructor for members data class
+        parent::__construct(false, false);
+
+     }
+
+    public function modelAction($actionData = false)
+    {
+        $searchSettigns = false;
+        $settingsUpdated = false;
+        $settingsUpdateError = false;
+
+
+        // Determine if current user can edit configurations
+        if (!current_user_can('glm_members_management')) {
+            return array(
+                    'status' => false,
+                    'menuItemRedirect' => 'error',
+                    'modelRedirect' => 'index',
+                    'view' => 'admin/error/index.html',
+                    'data' => array(
+                            'reason' => 'User does not have rights to make configuration changes.'
+                    )
+            );
+        }
+
+        // Check for submission option
+        $option = '';
+        if (isset($_REQUEST['option']) && $_REQUEST['option'] == 'submit') {
+            $option = $_REQUEST['option'];
+        }
+
+        switch($option) {
+
+            // Update the settings and redisplay the form
+            case 'submit':
+
+                // Update the search management settings
+                $searchSettings = $this->updateEntry(1);
+                if ($searchSettings['status']) {
+                    $settingsUpdated = true;
+                } else {
+                    $settingsUpdateError = true;
+                }
+
+                break;
+
+            // Default is to get the current settings and display the form
+            default:
+
+                // Try to get the first (should be only) entry for general settings.
+                $searchSettings = $this->editEntry(1);
+
+                if ($searchSettings === false) {
+
+                    if (GLM_MEMBERS_PLUGIN_ADMIN_DEBUG) {
+                        glmMembersAdmin::addNotice("<b>&nbsp;&nbsp;/models/admin/management/search.php: Unable to load search management settings.", 'Alert');
+                    }
+
+                }
+
+                break;
+
+        }
+
+        // Compile template data
+        $templateData = array(
+            'reason' => '',
+            'searchSettings' => $searchSettings,
+            'settingsUpdated' => $settingsUpdated,
+            'settingsUpdateError' => $settingsUpdateError
+       );
+
+        // Return status, suggested view, and data to controller
+        return array(
+            'status' => true,
+            'menuItemRedirect' => false,
+            'modelRedirect' => false,
+            'view' => 'admin/management/search.html',
+            'data' => $templateData
+        );
+
+
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/models/front/search/index.php b/models/front/search/index.php
new file mode 100644 (file)
index 0000000..d27edd5
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Gaslight Media Members Database
+ * Front Search using OSS
+ *
+ * PHP version 5.5
+ *
+ * @category glmWordPressPlugin
+ * @package  glmMembersDatabase
+ * @author   Chuck Scott <cscott@gaslightmedia.com>
+ * @license  http://www.gaslightmedia.com Gaslightmedia
+ * @version  0.1
+ */
+
+// Load GLM Search class
+require_once(GLM_MEMBERS_SEARCH_PLUGIN_CLASS_PATH.'/glmSearch.php');
+
+/*
+ * Perform searches using the Gaslight Media Open Search Server
+ * and display results
+ */
+class GlmMembersFront_search_index extends GLMSearch
+{
+
+    /**
+     * WordPress Database Object
+     *
+     * @var $wpdb
+     * @access public
+     */
+    public $wpdb;
+    /**
+     * Plugin Configuration Data
+     *
+     * @var $config
+     * @access public
+     */
+    public $config;
+
+    /*
+     * Constructor
+     *
+     * This contructor sets up this model. At this time that only includes
+     * storing away the WordPress data object.
+     *
+     * @return object Class object
+     *
+     */
+    public function __construct ($wpdb, $config)
+    {
+
+        // Save WordPress Database object
+        $this->wpdb = $wpdb;
+
+        // Save plugin configuration object
+        $this->config = $config;
+
+        // Get the Open Search Server parameters from management settings
+        $index = $this->config['settings']['search_index'];
+        $website = $this->config['settings']['website'];
+        $login = $this->config['settings']['login'];
+        $key = $this->config['settings']['login_key'];
+
+        // Run constructor for members data class
+        parent::__construct($index, $website, $login, $key);
+
+    }
+
+    /*
+     * Perform Model Action
+     *
+     * This method does the work for this model and returns any resulting data
+     *
+     * @return array Status and data array
+     *
+     * 'status'
+     *
+     * True if successfull and false if there was a fatal failure.
+     *
+     * 'menuItemRedirect'
+     *
+     * If not false, provides a menu item the controller should
+     * execute after this one. Normally if this is used, there would also be a
+     * modelRedirect value supplied as well.
+     *
+     * 'modelRedirect'
+     *
+     * If not false, provides an action the controller should execute after
+     * this one.
+     *
+     * 'view'
+     *
+     * A suggested view name that the contoller should use instead of the
+     * default view for this model or false to indicate that the default view
+     * should be used.
+     *
+     * 'data'
+     *
+     * Data that the model is returning for use in merging with the view to
+     * produce output.
+     *
+     */
+    public function modelAction ($actionData = false)
+    {
+
+        $haveSearchResult = false;
+        $status = true;
+        $query = false;
+        $searchResult = array(
+            'operator' => 'AND'
+        );
+
+
+        /*
+         * Get input from form
+         */
+        if (isset($_REQUEST['query'])) {
+
+            $query = $_REQUEST['query'];
+            $operator = 'OR';
+            if (isset($_REQUEST['matchAll']) && $_REQUEST['matchAll'] == 'on') {
+                $operator = 'AND';
+            }
+
+            $start = $_REQUEST['start'];
+            $rows = $_REQUEST['rows'];
+
+            if (trim($query) != '') {
+                $searchResult = $this->glmSearch($query, $operator, $start, $rows);
+                if (is_array($searchResult) && isset($searchResult['totalResults']) && $searchResult['totalResults'] > 0 ) {
+                    $haveSearchResult = true;
+                }
+            }
+
+        }
+
+        // Compile template data
+        $templateData = array(
+            'haveSearchResult'  => $haveSearchResult,
+            'searchResult'      => $searchResult,
+            'query'             => $query
+        );
+
+        $view = 'index.html';
+
+        // Return status, suggested view, and data to controller - also return any modified settings
+        return array(
+            'status'            => $status,
+            'menuItemRedirect'  => false,
+            'modelRedirect'     => false,
+            'view'              => 'front/search/'.$view,
+            'data'              => $templateData
+        );
+
+    }
+
+
+}
+
+?>
\ No newline at end of file
index f9ead31..3f5a6fb 100644 (file)
  */
 
 
+if (apply_filters('glm_members_permit_admin_members_search_tab', true)) {
+    add_filter('glm-member-db-add-tab-for-management',
+        function($addOnTabs) {
+            $newTabs = array(
+                array(
+                    'text' => 'Search',
+                    'menu' => 'management',
+                    'action' => 'search'
+                )
+            );
+            $addOnTabs = array_merge($addOnTabs, $newTabs);
+            return $addOnTabs;
+        }
+    );
+}
diff --git a/setup/databaseScripts/create_database_V0.0.1.sql b/setup/databaseScripts/create_database_V0.0.1.sql
new file mode 100644 (file)
index 0000000..958f06a
--- /dev/null
@@ -0,0 +1,27 @@
+-- Gaslight Media Members Database - Search 
+-- File Created: 12/02/15 15:27:15
+-- Database Version: 0.0.1
+-- Database Creation Script
+-- 
+-- To permit each query below to be executed separately,
+-- all queries must be separated by a line with four dashes
+
+-- Search Management Settings
+CREATE TABLE {prefix}management (
+  id INT NOT NULL AUTO_INCREMENT,
+  search_index TINYTEXT NULL,           -- OSS Search Index to use (normally "Index_1")
+  website TINYTEXT NULL,                -- Hostname for the Website
+  login TINYTEXT NULL,                  -- OSS API Login 
+  login_key TINYTEXT NULL,              -- OSS API Login Key
+  PRIMARY KEY (id)
+);
+
+----
+
+-- Set default package management entry
+INSERT INTO {prefix}management
+    ( id, search_index, website, login, login_key )
+   VALUES
+    ( 1, 'Index_1', 'www.gaslightmedia.com', 'WebsiteServices', '829800701e8440b67a78e3afbefa1049' )
+;
+
diff --git a/setup/databaseScripts/dbVersions.php b/setup/databaseScripts/dbVersions.php
new file mode 100644 (file)
index 0000000..1419080
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Gaslight Media Members Database
+ * GLM Members Packaging DB Versions
+ *
+ * PHP version 5.5
+ *
+ * @category glmWordPressPlugin
+ * @package  glmMembersDatabase
+ * @author   Chuck Scott <cscott@gaslightmedia.com>
+ * @license  http://www.gaslightmedia.com Gaslightmedia
+ * @release  dbVersions.php,v 1.0 2014/10/31 19:31:47 cscott Exp $
+ * @link     http://dev.gaslightmedia.com/
+ */
+
+$glmMembersSearchDbVersions = array(
+            '0.0.1' => array('version' => '0.0.1', 'tables' => 1)
+);
+
index b8fdf75..61f7769 100644 (file)
  *
  */
 
-$glmMembersSampleShortcodes = array(
+$glmMembersSearchShortcodes = array(
+    'glm-members-search' => array(
+        'plugin' => GLM_MEMBERS_SEARCH_PLUGIN_SLUG,
+        'menu' => 'search',
+        'action' => 'index',
+        'table' => false,
+        'attributes' => array(
+            'type' => 'standard',                       // 'standard', 'xml-sitemap'
+            'order' => 'relevance',                     // 'relevance', 'date'
+            'search' => false                           // Text to search for or false
+        )
+    )
 );
 
-$glmMembersSampleShortcodesDescription = '';
+$glmMembersSearchShortcodesDescription = '
+    <tr><th>Shortcode</th><th>Attribute</th><th>Description</th></tr>
+    <tr>
+        <th>[glm-members-search]</th>
+        <td>&nbsp;</td>
+        <td width="50%">
+            Displays a search form for searching the entire Web site using the Gaslight Media search
+            engine. If the search form is submitted or the shortcode includes a pre-set search
+            string, the search form will be displayed with the search parameters and the results
+            displayed below.
+        </td>
+    </tr>
+    <tr>
+        <td>&nbsp;</td>
+        <th>type="{search type}"</th>
+        <td>
+            The type of search
+            <p>
+                <table width="100%">
+                    <tr><th colspan=3">Search Types</th></tr>
+                    <tr><td>standard</td><td>Typical search engine results</td></tr>
+                    <tr><td>sitemap</td><td>A user-readable list of all pages in the site with links to the page.</td></tr>
+                    <tr><td>xml-sitemap</td><td>An XML sitemap of all pages for this Web site</td></tr>
+                </table>
+            </p>
+        </td>
+    </tr>
+    <tr>
+        <td>&nbsp;</td>
+        <th>order="{order type}"</th>
+        <td>
+            Order of search results
+            <p>
+                <table width="100%">
+                    <tr><th colspan=3">Order Types</th></tr>
+                    <tr><td>relevance</td><td>Sort results by relevance with most relevant result first</td></tr>
+                    <tr><td>date/td><td>Sort results by last date update with most recent first.</td></tr>
+                </table>
+            </p>
+        </td>
+    </tr>
+    <tr>
+        <td>&nbsp;</td>
+        <th>search="{search string}"</th>
+        <td>
+            Specified text to search for. Only use if you want to start with a particular search.
+        </td>
+    </tr>
+';
 
index 00e7305..c29e80e 100644 (file)
  * This array is integrated into the valid actions array in the main GLM Member
  * DB plugin when this plugin registers itself.
  */
-
+/*
 $glmMembersSampleAddOnValidActions = array(
     'adminActions' => array(
     ),
     'frontActions' => array(
     )
 );
+*/
 
-?>
\ No newline at end of file
+$glmMembersSearchAddOnValidActions = array(
+    'adminActions' => array(
+        'management' => array(
+            'search' => GLM_MEMBERS_SEARCH_PLUGIN_SLUG
+        )
+    ),
+    'frontActions' => array(
+        'search' => array(
+            'index' => GLM_MEMBERS_SEARCH_PLUGIN_SLUG
+        )
+    )
+);
diff --git a/views/admin/management/search.html b/views/admin/management/search.html
new file mode 100644 (file)
index 0000000..79a5a46
--- /dev/null
@@ -0,0 +1,60 @@
+{include file='admin/management/header.html'}
+    
+    <form action="{$thisUrl}?page={$thisPage}" method="post" enctype="multipart/form-data">
+        <input type="hidden" name="glm_action" value="search">
+        <input type="hidden" name="option" value="submit">
+        
+        <table class="glm-admin-table">
+        
+            <!-- Settings for talking with the Open Search Server -->
+        
+            <tr>
+                <td colspan="2">
+                    {if $settingsUpdated}<h2 class="glm-notice glm-flash-updated glm-right">Settings Updated</h2>{/if}
+                    {if $settingsUpdateError}<span class="glm-error glm-flash-updated glm-right">Settings Update Error</span>{/if}
+                    <h2>General Search Settings</h2>
+                </td>
+            </tr>
+            <tr>
+                <th {if $searchSettings.fieldRequired.search_index}class="glm-required"{/if}>Search Index:</th>
+                <td {if $searchSettings.fieldFail.search_index}class="glm-form-bad-input glm-form-bad-input-misc"{/if}>
+                    <input type="text" name="search_index" value="{$searchSettings.fieldData.search_index}" class="glm-form-text-input-medium">
+                    {if $searchSettings.fieldFail.search_index}<p>{$searchSettings.fieldFail.search_index}</p>{/if}
+                </td>
+            </tr>
+            <tr>
+                <th {if $searchSettings.fieldRequired.website}class="glm-required"{/if}>Website:</th>
+                <td {if $searchSettings.fieldFail.website}class="glm-form-bad-input glm-form-bad-input-misc"{/if}>
+                    <input type="text" name="website" value="{$searchSettings.fieldData.website}" class="glm-form-text-input-medium">
+                    {if $searchSettings.fieldFail.website}<p>{$searchSettings.fieldFail.website}</p>{/if}
+                </td>
+            </tr>
+            <tr>
+                <th {if $searchSettings.fieldRequired.login}class="glm-required"{/if}>OSS Login:</th>
+                <td {if $searchSettings.fieldFail.login}class="glm-form-bad-input glm-form-bad-input-misc"{/if}>
+                    <input type="text" name="login" value="{$searchSettings.fieldData.login}" class="glm-form-text-input-medium">
+                    {if $searchSettings.fieldFail.login}<p>{$searchSettings.fieldFail.login}</p>{/if}
+                </td>
+            </tr>
+            <tr>
+                <th {if $searchSettings.fieldRequired.login_key}class="glm-required"{/if}>OSS login key:</th>
+                <td {if $searchSettings.fieldFail.login_key}class="glm-form-bad-input glm-form-bad-input-misc"{/if}>
+                    <input type="text" name="login_key" value="{$searchSettings.fieldData.login_key}" class="glm-form-text-input-medium">
+                    {if $searchSettings.fieldFail.login_key}<p>{$searchSettings.fieldFail.login_key}</p>{/if}
+                </td>
+            </tr>
+        </table>
+        <input type="submit" value="Update Settings" class="button-primary">
+    </form>
+    
+    <script type="text/javascript">
+        
+        jQuery(document).ready(function($) {
+
+            // Flash certain elements for a short time after display      
+            $(".glm-flash-updated").fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500);
+                    
+        });
+    </script>
+
+{include file='admin/footer.html'}
diff --git a/views/front/footer.html b/views/front/footer.html
new file mode 100644 (file)
index 0000000..f8b0015
--- /dev/null
@@ -0,0 +1,10 @@
+
+    </div> <!-- / front content area -->
+          
+  {if $frontDebug}
+    <script>
+        window.open('{$thisUrl}?glmDebugWindow=true','GLM_Plugin_Debug','width=800,height=800,left=50,top=50,resizable=yes,scrollbars=yes');
+    </script>
+  {/if}
+  
+</div> <!-- / wrap -->
\ No newline at end of file
diff --git a/views/front/search/Sitemap_XML_0.9.html b/views/front/search/Sitemap_XML_0.9.html
new file mode 100644 (file)
index 0000000..dc32586
--- /dev/null
@@ -0,0 +1,12 @@
+{if $haveResult}<?xml version="1.0" encoding="UTF-8"?>\r
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\r
+{foreach $result->documents as $doc}\r
+    <url>\r
+        <loc>{$doc->url->value|escape:'html'}</loc>\r
+{if $doc->lastModifiedDate->textValue}\r
+        <lastmod>{$doc->lastModifiedDate->textValue}</lastmod>\r
+{/if}\r
+        <changefreq>weekly</changefreq>\r
+    </url>\r
+{/foreach}\r
+</urlset>{else}Sitemap Error: No results available{/if}\r
diff --git a/views/front/search/header.html b/views/front/search/header.html
new file mode 100644 (file)
index 0000000..5e9f93d
--- /dev/null
@@ -0,0 +1,3 @@
+<div class="wrap">
+    <div id="glm-member-search-front-container" class="glm-member-front-container">
+    
\ No newline at end of file
diff --git a/views/front/search/index.html b/views/front/search/index.html
new file mode 100644 (file)
index 0000000..47d8063
--- /dev/null
@@ -0,0 +1,104 @@
+{include file='front/search/header.html'}
+    <form action="{$thisUrl}?glm_action=index" method="post" enctype="multipart/form-data">
+        Search for: <input type="hidden" name="GLMSearch" value="true">
+        <input type="hidden" name="start" value="0">
+        <input type="hidden" name="rows" value="10">
+        <input type="text" name="query" value="{$query}">
+        <input type="checkbox" name="matchAll" {if $searchResult.operator == 'AND'}checked{/if} value="on"> Match all words
+        <input type="submit" id="submit" value="Search">
+    </form>
+    
+{if $haveSearchResult}
+  
+  {if $searchResult.totalResults > 0}
+  
+            <!-- Results Header - Top -->
+            
+            <div class="GLMSresultsHeader">
+
+                <div class="GLMSresultsSummary">
+                    Showing {$searchResult.firstResultOnPage} through {$searchResult.lastResultOnPage} of {$searchResult.totalResults} results.
+                </div> 
+                
+                <!-- Results Navigation -->
+                
+                <div class="GLMSresultsNavContainer">
+
+        {if $searchResult.previousPageStartIndex !== false} 
+                    <a href="{$thisUrl}?GLMSearch=true&query={$query|escape:'url'}&{if $searchResult.operator == 'AND'}matchAll=on&{/if}start={$searchResult.previousPageStartIndex}&rows={$searchResult.resultsPerPage}">Previous Results</a>
+        {else}
+                    <span class="GLMSresultsNavInactive">Previous Results</span>
+        {/if}
+                    -
+        {if $searchResult.nextPageStartIndex !== false}
+                    <a href="{$thisUrl}?GLMSearch=true&query={$query|escape:'url'}&{if $searchResult.operator == 'AND'}matchAll=on&{/if}start={$searchResult.nextPageStartIndex}&rows={$searchResult.resultsPerPage}">Next Results</a>    
+        {else}
+                    <span class="GLMSresultsNavInactive">Next Results</span>
+        {/if}
+                </div>
+    
+            </div>
+                        
+            <!-- Search results -->
+            
+            <div id="GLMSresultsContainer">
+
+        {foreach $searchResult.documents as $doc}
+                <div class="GLMSresultContainer">
+                
+                    <div class="GLMSresultTitle">
+                        <a href="{$doc.url.value}">{$doc.title.value}</a>
+                    </div>
+                    
+                    <div class="GLMSresultSnippets">
+            {foreach from=$doc.content.snippets item="snip" name="snip"}
+                        {$snip}
+               {if (!$smarty.foreach.snip.last)}<br>----<br>{/if} 
+            {/foreach}
+                    </div>
+                    <div class="GLMSresultUrl">{$doc.url.value}</div>
+                </div>                 
+        {/foreach}
+                
+            </div>
+
+            <!-- Results Header - Bottom -->
+            
+            <div class="GLMSresultsHeader">
+
+                <div class="GLMSresultsSummary">
+                    Showing {$searchResult.firstResultOnPage} through {$searchResult.lastResultOnPage} of {$searchResult.totalResults} results.
+                </div> 
+                
+                <!-- Results Navigation -->
+                
+                <div class="GLMSresultsNavContainer">
+
+        {if $searchResult.previousPageStartIndex !== false} 
+                    <a href="{$thisUrl}?GLMSearch=true&query={$query|escape:'url'}&{if $searchResult.operator == 'AND'}matchAll=on&{/if}start={$searchResult.previousPageStartIndex}&rows={$searchResult.resultsPerPage}">Previous Results</a>
+        {else}
+                    <span class="GLMSresultsNavInactive">Previous Results</span>
+        {/if}
+                    -
+        {if $searchResult.nextPageStartIndex !== false}
+                    <a href="{$thisUrl}?GLMSearch=true&query={$query|escape:'url'}&{if $searchResult.operator == 'AND'}matchAll=on&{/if}start={$searchResult.nextPageStartIndex}&rows={$searchResult.resultsPerPage}">Next Results</a>    
+        {else}
+                    <span class="GLMSresultsNavInactive">Next Results</span>
+        {/if}
+                </div>
+    
+            </div>
+                        
+                
+  {else}
+            <h3>No results matching your search.</h3>
+  {/if}
+  
+{else}
+    (Please enter your desired search above.)
+{/if}
+
+</div> <!-- /search wrapper -->
+
+{include file='front/footer.html'}