      1: <?php
      2:
      3: /**
      4:  *
      5:  * Uses the TagObject business oject to store searchable tags in the main
      6:  * application database.
      7:  *
      8:  * @package alpha::util::search
      9:  * @since 1.2.3
     10:  * @author John Collins <dev@alphaframework.org>
     11:  * @version $Id: SearchProviderTags.inc 1701 2013-12-18 22:33:25Z alphadevx $
     12:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
     13:  * @copyright Copyright (c) 2013, John Collins (founder of Alpha Framework).
     14:  * All rights reserved.
     15:  *
     16:  * <pre>
     17:  * Redistribution and use in source and binary forms, with or
     18:  * without modification, are permitted provided that the
     19:  * following conditions are met:
     20:  *
     21:  * * Redistributions of source code must retain the above
     22:  *   copyright notice, this list of conditions and the
     23:  *   following disclaimer.
     24:  * * Redistributions in binary form must reproduce the above
     25:  *   copyright notice, this list of conditions and the
     26:  *   following disclaimer in the documentation and/or other
     27:  *   materials provided with the distribution.
     28:  * * Neither the name of the Alpha Framework nor the names
     29:  *   of its contributors may be used to endorse or promote
     30:  *   products derived from this software without specific
     31:  *   prior written permission.
     32:  *
     33:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
     34:  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
     35:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     36:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     37:  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
     38:  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     39:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     40:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     41:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     42:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     43:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
     44:  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     45:  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     46:  * </pre>
     47:  *
     48:  */
     49: class SearchProviderTags implements SearchProviderInterface {
     50:
     51:     /**
     52:      * Trace logger
     53:      *
     54:      * @var Logger
     55:      * @since 1.2.3
     56:      */
     57:     private static $logger;
     58:
     59:     /**
     60:      * The number of matches found for the current search.
     61:      *
     62:      * @var integer
     63:      * @since 1.2.3
     64:      */
     65:     private $numberFound = 0;
     66:
     67:     /**
     68:      * constructor to set up the object
     69:      *
     70:      * @since 1.2.3
     71:      */
     72:     public function __construct() {
     73:         self::$logger = new Logger('SearchProviderTags');
     74:     }
     75:
     76:     /**
     77:      * {@inheritdoc}
     78:      */
     79:     public function search($query, $returnType = 'all', $start = 0, $limit = 10) {
     80:
     81:         // explode the user's query into a set of tokenized transient TagObjects
     82:         $queryTags = TagObject::tokenize($query, '', '', false);
     83:         $matchingTags = array();
     84:
     85:         // load TagObjects from the DB where content equals the content of one of our transient TagObjects
     86:         foreach($queryTags as $queryTag) {
     87:             $tags = $queryTag->loadAllByAttribute('content', $queryTag->get('content'));
     88:             $matchingTags = array_merge($matchingTags, $tags);
     89:         }
     90:
     91:         // the result objects
     92:         $results = array();
     93:         // matching tags with weights
     94:         $matches = array();
     95:
     96:         /*
     97:          * Build an array of BOs for the matching tags from the DB:
     98:          * array key = BO ID
     99:          * array value = weight (the amount of tags matching the BO)
    100:          */
    101:         foreach($matchingTags as $tag) {
    102:             if ($returnType == 'all' || $tag->get('taggedClass') == $returnType) {
    103:
    104:                 $key = $tag->get('taggedClass').'-'.$tag->get('taggedOID');
    105:
    106:                 if(isset($matches[$key])) {
    107:                     // increment the weight if the same BO is tagged more than once
    108:                     $weight = intval($matches[$key]) + 1;
    109:                     $matches[$key] = $weight;
    110:                 }else{
    111:                     $matches[$key] = 1;
    112:                 }
    113:             }
    114:         }
    115:
    116:         // sort the matches based on tag frequency weight
    117:         arsort($matches);
    118:
    119:         $this->numberFound = count($matches);
    120:
    121:         // now paginate
    122:         $matches = array_slice($matches, $start, $limit+5); // the +5 is just some padding in case of orphans
    123:
    124:         // now load each object
    125:         foreach ($matches as $key => $weight) {
    126:             if(count($results) < $limit) {
    127:                 $parts = explode('-', $key);
    128:
    129:                 try {
    130:
    131:                     $BO = new $parts[0];
    132:                     $BO->load($parts[1]);
    133:
    134:                     $results[] = $BO;
    135:
    136:                 }catch(BONotFoundException $e) {
    137:                     self::$logger->warn('Orpaned TagObject detected pointing to a non-existant BO of OID ['.$parts[1].'] and type ['.$parts[0].'].');
    138:                 }
    139:             }
    140:         }
    141:
    142:         return $results;
    143:     }
    144:
    145:     /**
    146:      * {@inheritdoc}
    147:      */
    148:     public function getRelated(AlphaDAO $sourceObject, $returnType = 'all', $start = 0, $limit = 10) {
    149:
    150:         // all the tags on the source object for comparison
    151:         $tags = $sourceObject->getPropObject('tags')->getRelatedObjects();
    152:
    153:         // the result objects
    154:         $results = array();
    155:         // matching tags with weights
    156:         $matches = array();
    157:
    158:         foreach($tags as $tag) {
    159:             $tagObject = new TagObject();
    160:             $matchingTags = $tagObject->query("SELECT * FROM ".$tagObject->getTableName()." WHERE
    161:                 content='".$tag->get('content')."' AND NOT
    162:                 (taggedOID = '".$sourceObject->getOID()."' AND taggedClass = '".get_class($sourceObject)."');");
    163:
    164:             foreach($matchingTags as $matchingTag) {
    165:                 if ($returnType == 'all' || $tag->get('taggedClass') == $returnType) {
    166:
    167:                     $key = $matchingTag['taggedClass'].'-'.$matchingTag['taggedOID'];
    168:
    169:                     if(isset($matches[$key])) {
    170:                         // increment the weight if the same BO is tagged more than once
    171:                         $weight = intval($matches[$key]) + 1;
    172:                         $matches[$key] = $weight;
    173:                     }else{
    174:                         $matches[$key] = 1;
    175:                     }
    176:                 }
    177:             }
    178:         }
    179:
    180:         // sort the matches based on tag frequency weight
    181:         arsort($matches);
    182:
    183:         $this->numberFound = count($matches);
    184:
    185:         // now paginate
    186:         $matches = array_slice($matches, $start, $limit);
    187:
    188:         // now load each object
    189:         foreach ($matches as $key => $weight) {
    190:             $parts = explode('-', $key);
    191:
    192:             $BO = new $parts[0];
    193:             $BO->load($parts[1]);
    194:
    195:             $results[] = $BO;
    196:         }
    197:
    198:         return $results;
    199:     }
    200:
    201:     /**
    202:      * {@inheritdoc}
    203:      */
    204:     public function index(AlphaDAO $sourceObject) {
    205:         $taggedAttributes = $sourceObject->getTaggedAttributes();
    206:
    207:         foreach($taggedAttributes as $tagged) {
    208:             $tags = TagObject::tokenize($sourceObject->get($tagged), get_class($sourceObject), $sourceObject->getOID());
    209:
    210:             foreach($tags as $tag) {
    211:                 try {
    212:                     $tag->save();
    213:                 }catch(ValidationException $e){
    214:                     /*
    215:                      * The unique key has most-likely been violated because this BO is already tagged with this
    216:                      * value, so we can ignore in this case.
    217:                      */
    218:                 }
    219:             }
    220:         }
    221:     }
    222:
    223:     /**
    224:      * {@inheritdoc}
    225:      */
    226:     public function delete(AlphaDAO $sourceObject) {
    227:         $tags = $sourceObject->getPropObject('tags')->getRelatedObjects();
    228:
    229:         foreach ($tags as $tag)
    230:             $tag->delete();
    231:     }
    232:
    233:     /**
    234:      * {@inheritdoc}
    235:      */
    236:     public function getNumberFound() {
    237:         return $this->numberFound;
    238:     }
    239: }
    240:
    241: ?>