Tuskfish API V1.1.1
  • Package
  • Class

Packages

  • content
  • core
  • database
  • installation
  • security
  • user
  • utilities

Classes

  • someClass
  • someClassHandler
  • TfArticle
  • TfAudio
  • TfBlock
  • TfBlockHandler
  • TfCache
  • TfCollection
  • TfCollectionHandler
  • TfContentHandler
  • TfContentHandlerFactory
  • TfContentObject
  • TfCriteria
  • TfCriteriaFactory
  • TfCriteriaItem
  • TfDatabase
  • TfDownload
  • TfFileHandler
  • TfImage
  • TfLogger
  • TfMetadata
  • TfPaginationControl
  • TfPreference
  • TfPreferenceHandler
  • TfRss
  • TfSearchContent
  • TfSession
  • TfStatic
  • TfTag
  • TfTagHandler
  • TfTaglinkHandler
  • TfTemplate
  • TfTree
  • TfUser
  • TfUtils
  • TfValidator
  • TfValidatorFactory
  • TfVideo
  • TfYubikeyAuthenticator

Traits

  • TfContentTypes
  • TfLanguage
  • TfMagicMethods
  • TfMimetypes

Functions

  • checkPasswordStrength
  • getUrl
  • hashPassword
  • tf_autoload
  • tfContentModuleAutoload
  • tfSomeModuleAutoload
  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 
<?php

/**
 * TfSearchContent class file.
 * 
 * @copyright   Simon Wilkinson 2013+ (https://tuskfish.biz)
 * @license     https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html GNU General Public License (GPL) V2
 * @author      Simon Wilkinson <[email protected]>
 * @version     Release: 1.0
 * @since       1.1
 * @package     content
 */

// Enable strict type declaration.
declare(strict_types=1);

if (!defined("TFISH_ROOT_PATH")) die("TFISH_ERROR_ROOT_PATH_NOT_DEFINED");

/**
 * Provides search functionality for the content module, returning mixed content objects.
 *
 * @copyright   Simon Wilkinson 2013+ (https://tuskfish.biz)
 * @license     https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html GNU General Public License (GPL) V2
 * @author      Simon Wilkinson <[email protected]>
 * @version     Release: 1.0
 * @since       1.1
 * @package     content
 * @var         TfValidator $validator Instance of the Tuskfish data validator class.
 * @var         TfDatabase $db Instance of the Tuskfish database class.
 * @var         TfPreference $preference Instance of the Tuskfish site preferences class.
 * @var         array $searchTerms Search terms provided by user.
 * @var         array $escapedSearchTerms XSS escaped copies of the search terms, used for display.
 * @var         int $limit Number of records to retrieve in a single page view.
 * @var         int $offset Starting point for reading records from a result set.
 * @var         string $operator Type of search, options are 'AND', 'OR' and 'exact'.
 */
class TfSearchContent
{
    protected $validator;
    protected $db;
    protected $preference;
    protected $searchTerms;
    protected $escapedSearchTerms;
    protected $limit;
    protected $offset;
    protected $operator;
    
    /**
     * Constructor.
     * 
     * @param TfValidator $validator An instance of the Tuskfish data validator class.
     * @param TfDatabase $db An instance of the database class.
     * @param TfPreference $preference An instance of the Tuskfish site preferences class.
     */
    public function __construct(TfValidator $validator,
            TfDatabase $db, TfPreference $preference)
    {
        if (is_a($validator, 'TfValidator')) {
            $this->validator = $validator; 
        } else {
            trigger_error(TFISH_ERROR_NOT_OBJECT, E_USER_ERROR);
        }
        
        if (is_a($db, 'TfDatabase')) {
            $this->db = $db; 
        } else {
            trigger_error(TFISH_ERROR_NOT_OBJECT, E_USER_ERROR);
        }
        
        if (is_a($preference, 'TfPreference')) {
            $this->preference = $preference;
        }  else {
            trigger_error(TFISH_ERROR_NOT_OBJECT, E_USER_ERROR);
        }
        
        $this->searchTerms = array();
        $this->escapedSearchTerms = array();
        $this->limit = $preference->searchPagination;
        $this->offset = 0;
        $this->operator = 'AND';
    }
    
    /**
     * Provides global search functionality for content objects.
     * 
     * Escaping of search terms is handled through use of a PDO prepared statement with named 
     * placeholders; search terms are inserted indirectly by binding them to the placeholders.
     * Search terms must NEVER be inserted into a query directly (creates an SQL injection
     * vulnerability), otherwise do us all a favour and go shoot yourself now.
     * 
     * Search terms have entity encoding (htmlspecialchars) applied on the teaser and description
     * fields (only) to ensure consistency with the entity encoding treatment that these HTML fields
     * have been subjected to, otherwise searches involving entities will not return results.
     * 
     * @return object|bool Content objects if results found, false if no results or on failure.
     */
    public function searchContent()
    {
        $sql = $count = '';
        $searchTermPlaceholders = $escapedTermPlaceholders = $results = array();
        
        $sqlCount = "SELECT count(*) ";
        $sqlSearch = "SELECT * ";
        $result = array();

        $sql = "FROM `content` ";
        $count = count($this->searchTerms);
        
        if ($count) {
            $sql .= "WHERE ";
            
            for ($i = 0; $i < $count; $i++) {
                $searchTermPlaceholders[$i] = ':searchTerm' . (string) $i;
                $escapedTermPlaceholders[$i] = ':escapedSearchTerm' . (string) $i;
                $sql .= "(";
                $sql .= "`title` LIKE " . $searchTermPlaceholders[$i] . " OR ";
                $sql .= "`teaser` LIKE " . $escapedTermPlaceholders[$i] . " OR ";
                $sql .= "`description` LIKE " . $escapedTermPlaceholders[$i] . " OR ";
                $sql .= "`caption` LIKE " . $searchTermPlaceholders[$i] . " OR ";
                $sql .= "`creator` LIKE " . $searchTermPlaceholders[$i] . " OR ";
                $sql .= "`publisher` LIKE " . $searchTermPlaceholders[$i];
                $sql .= ")";
                
                if ($i != ($count - 1)) {
                    $sql .= $this->operator;
                }
            }
        }
        
        $sql .= " AND `online` = 1 AND `type` != 'TfBlock' ";
        $sql .= "ORDER BY `date` DESC, `submissionTime` DESC ";
        $sqlCount .= $sql;

        // Bind the search term values and execute the statement.
        $statement = $this->db->preparedStatement($sqlCount);

        if ($statement) {
            
            for ($i = 0; $i < $count; $i++) {
                $statement->bindValue($searchTermPlaceholders[$i], "%" . $this->searchTerms[$i] . "%",
                        PDO::PARAM_STR);
                $statement->bindValue($escapedTermPlaceholders[$i], "%" . $this->escapedSearchTerms[$i] . "%",
                        PDO::PARAM_STR);
            }
        } else {
            return false;
        }
        
        // Execute the statement.
        $statement->execute();

        $row = $statement->fetch(PDO::FETCH_NUM);
        $result[0] = reset($row);
        unset($statement, $row);

        // Retrieve the subset of objects actually required.
        if (!$this->limit) {
            $limit = $this->preference->searchPagination;
        }
        
        $sql .= "LIMIT :limit ";
        
        if ($this->offset) {
            $sql .= "OFFSET :offset ";
        }

        $sqlSearch .= $sql;
        $statement = $this->db->preparedStatement($sqlSearch);
        if ($statement) {
            for ($i = 0; $i < $count; $i++) {
                $statement->bindValue($searchTermPlaceholders[$i], "%" . $this->searchTerms[$i] . "%",
                        PDO::PARAM_STR);
                $statement->bindValue($escapedTermPlaceholders[$i], "%" . $this->escapedSearchTerms[$i]
                        . "%", PDO::PARAM_STR);
                $statement->bindValue(":limit", (int) $this->limit, PDO::PARAM_INT);
                
                if ($this->offset) {
                    $statement->bindValue(":offset", (int) $this->offset, PDO::PARAM_INT);
                }
            }
        } else {
            return false;
        }

        $statement->execute();

        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
            $object = new $row['type']($this->validator);
            $object->loadPropertiesFromArray($row, true);
            $result[$object->id] = $object;
            unset($object, $row);
        }
        
        return $result;
    }
    
    /**
     * Set a limit on the number of results to retrieve.
     * 
     * Usually this will be the number of objects you want to display in a single page view, as
     * it is related to pagination, for example a search may return 50 results but you only want
     * to display 10 per page.
     * 
     * @param int $limit Number of objects to retrieve.
     */
    public function setLimit(int $limit)
    {
        $this->limit = $this->validator->isInt($limit, 0) ? (int) $limit : 0;
    }
    
    /**
     * Set a starting point for retrieving objects from a result set.
     * 
     * Related to pagination, for example you have 50 search results and display 10 per page, and
     * are currently viewing the third page of results, you would set this as 29.
     * 
     * @param int $offset Starting point to retrieve objects from a result set.
     */
    public function setOffset(int $offset)
    {
        $this->offset = $this->validator->isInt($offset, 0) ? (int) $offset : 0;
    }
    
    /**
     * Set the search type operator (AND, OR or exact).
     * 
     * Determines whether the search terms will be used inclusively (OR), exclusively (AND) or
     * exactly.
     * 
     * @param string $operator AND, OR or exact.
     */
    public function setOperator(string $operator)
    {
        $cleanOperator = $this->validator->trimString($operator);
        
        if (in_array($cleanOperator, array('AND', 'OR', 'exact'), true)) {
            $this->operator = $cleanOperator;
        } else {
            $this->operator = "AND";
        }
    }
    
    /**
     * Set and escape search terms for use in a query.
     * 
     * As some content fields require entities to be encoded (the HTML fields, ie. teaser,
     * description and icon) and others don't, both encoded and unencoded copies of the terms are
     * required for a comprehensive database search. Terms that do not meet the minimum length
     * preference requirement are discarded.
     * 
     * Note that search operator MUST be set before the search terms are set, otherwise the default
     * AND operator will be used.
     * 
     * @param string $searchTerms Search terms provided by user.
     */
    public function setSearchTerms(string $searchTerms)
    {
        $searchTerms = $this->validator->trimString($searchTerms);
        
        $cleanSearchTerms = $escapedSearchTerms = $cleanEscapedSearchTerms = array();
        
        // Create an escaped copy that will be used to search the HTML teaser and description fields.
        $escapedSearchTerms = htmlspecialchars($searchTerms, ENT_NOQUOTES, "UTF-8");

        if ($this->operator === 'AND' || $this->operator === 'OR') {
            $searchTerms = explode(" ", $searchTerms);
            $escapedSearchTerms = explode(" ", $escapedSearchTerms);
        } else {
            $searchTerms = array($searchTerms);
            $escapedSearchTerms = array($escapedSearchTerms);
        }
        
        // Trim search terms and discard any that are less than the minimum search length characters.
        foreach ($searchTerms as $term) {
            $term = $this->validator->trimString($term);
            
            if (!empty($term) && mb_strlen($term, 'UTF-8') >= $this->preference->minSearchLength) {
                $cleanSearchTerms[] = (string) $term;
            }
        }
        
        $this->searchTerms = $cleanSearchTerms;
        
        foreach ($escapedSearchTerms as $escapedTerm) {
            $escapedTerm = $this->validator->trimString($escapedTerm);
            
            if (!empty($escapedTerm) && mb_strlen($escapedTerm, 'UTF-8')
                    >= $this->preference->minSearchLength) {
                $cleanEscapedSearchTerms[] = (string) $escapedTerm;
            }
        }
        
        $this->escapedSearchTerms = $escapedSearchTerms;
    }
    
}
Tuskfish API V1.1.1 API documentation generated by ApiGen