Overview

Packages

  • alpha::controller
  • alpha::controller::front
  • alpha::exceptions
  • alpha::model
  • alpha::model::types
  • alpha::tasks
  • alpha::tests
  • alpha::util
  • alpha::util::cache
  • alpha::util::codehighlight
  • alpha::util::convertors
  • alpha::util::feeds
  • alpha::util::filters
  • alpha::util::graphs
  • alpha::util::helpers
  • alpha::util::metrics
  • alpha::util::search
  • alpha::view
  • alpha::view::renderers
  • alpha::view::widgets

Classes

  • SearchProviderFactory
  • SearchProviderTags

Interfaces

  • SearchProviderInterface
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  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 1815 2014-08-27 21:03:08Z alphadevx $
 12:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
 13:  * @copyright Copyright (c) 2014, 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:         global $config;
 81: 
 82:         // explode the user's query into a set of tokenized transient TagObjects
 83:         $queryTags = TagObject::tokenize($query, '', '', false);
 84:         $matchingTags = array();
 85: 
 86:         // load TagObjects from the DB where content equals the content of one of our transient TagObjects
 87:         foreach($queryTags as $queryTag) {
 88:             $tags = $queryTag->loadAllByAttribute('content', $queryTag->get('content'));
 89:             $matchingTags = array_merge($matchingTags, $tags);
 90:         }
 91: 
 92:         // the result objects
 93:         $results = array();
 94:         // matching tags with weights
 95:         $matches = array();
 96: 
 97:         if ($config->get('cache.provider.name') != '' && count($queryTags) == 1) { // for now, we are only caching on single tags
 98:             $key = $queryTags[0]->get('content');
 99:             $matches = $this->loadFromCache($key);
100:         }
101: 
102:         if (count($matches) == 0) {
103:             /*
104:              * Build an array of BOs for the matching tags from the DB:
105:              * array key = BO ID
106:              * array value = weight (the amount of tags matching the BO)
107:              */
108:             foreach($matchingTags as $tag) {
109:                 if ($returnType == 'all' || $tag->get('taggedClass') == $returnType) {
110: 
111:                     $key = $tag->get('taggedClass').'-'.$tag->get('taggedOID');
112: 
113:                     if(isset($matches[$key])) {
114:                         // increment the weight if the same BO is tagged more than once
115:                         $weight = intval($matches[$key]) + 1;
116:                         $matches[$key] = $weight;
117:                     }else{
118:                         $matches[$key] = 1;
119:                     }
120:                 }
121:             }
122: 
123:             if ($config->get('cache.provider.name') != '' && count($queryTags) == 1) { // for now, we are only caching on single tags
124:                 $key = $queryTags[0]->get('content');
125:                 $this->addToCache($key, $matches);
126:             }
127:         }
128: 
129:         // sort the matches based on tag frequency weight
130:         arsort($matches);
131: 
132:         $this->numberFound = count($matches);
133: 
134:         // now paginate
135:         $matches = array_slice($matches, $start, $limit+5); // the +5 is just some padding in case of orphans
136: 
137:         // now load each object
138:         foreach ($matches as $key => $weight) {
139:             if(count($results) < $limit) {
140:                 $parts = explode('-', $key);
141: 
142:                 try {
143: 
144:                     $BO = new $parts[0];
145:                     $BO->load($parts[1]);
146: 
147:                     $results[] = $BO;
148: 
149:                 }catch(BONotFoundException $e) {
150:                     self::$logger->warn('Orpaned TagObject detected pointing to a non-existant BO of OID ['.$parts[1].'] and type ['.$parts[0].'].');
151:                 }
152:             }
153:         }
154: 
155:         return $results;
156:     }
157: 
158:     /**
159:      * {@inheritdoc}
160:      */
161:     public function getRelated(AlphaDAO $sourceObject, $returnType = 'all', $start = 0, $limit = 10, $distinct = '') {
162: 
163:         global $config;
164: 
165:         // the result objects
166:         $results = array();
167:         // matching tags with weights
168:         $matches = array();
169:         // only used in conjunction with distinct param
170:         $distinctValues = array();
171: 
172:         if ($config->get('cache.provider.name') != '') {
173:             $key = get_class($sourceObject).'-'.$sourceObject->getOID().'-related'.($distinct == '' ? '' : '-distinct');
174:             $matches = $this->loadFromCache($key);
175:         }
176: 
177:         if (count($matches) == 0) {
178:             // all the tags on the source object for comparison
179:             $tags = $sourceObject->getPropObject('tags')->getRelatedObjects();
180: 
181:             foreach($tags as $tag) {
182:                 $tagObject = new TagObject();
183: 
184:                 if ($distinct == '') {
185:                     $matchingTags = $tagObject->query("SELECT * FROM ".$tagObject->getTableName()." WHERE 
186:                         content='".$tag->get('content')."' AND NOT 
187:                         (taggedOID = '".$sourceObject->getOID()."' AND taggedClass = '".get_class($sourceObject)."');");
188:                 }else{
189:                     // filter out results where the source object field is identical to distinct param
190:                     $matchingTags = $tagObject->query("SELECT * FROM ".$tagObject->getTableName()." WHERE 
191:                         content='".$tag->get('content')."' AND NOT 
192:                         (taggedOID = '".$sourceObject->getOID()."' AND taggedClass = '".get_class($sourceObject)."')
193:                         AND taggedOID IN (SELECT OID FROM ".$sourceObject->getTableName()." WHERE ".$distinct." != '".addslashes($sourceObject->get($distinct))."');");
194:                 }
195: 
196:                 foreach($matchingTags as $matchingTag) {
197:                     if ($returnType == 'all' || $tag->get('taggedClass') == $returnType) {
198: 
199:                         $key = $matchingTag['taggedClass'].'-'.$matchingTag['taggedOID'];
200: 
201:                         // matches on the distinct if defined need to be skipped
202:                         if ($distinct != '') {
203:                             try {
204: 
205:                                 $BO = new $matchingTag['taggedClass'];
206:                                 $BO->load($matchingTag['taggedOID']);
207: 
208:                                 // skip where the source object field is identical
209:                                 if ($sourceObject->get($distinct) == $BO->get($distinct))
210:                                     continue;
211: 
212:                                 if(!in_array($BO->get($distinct), $distinctValues)) {
213:                                     $distinctValues[] = $BO->get($distinct);
214:                                 }else{
215:                                     continue;
216:                                 }
217:                             }catch (BONotFoundException $e) {
218:                                 self::$logger->warn('Error loading object ['.$matchingTag['taggedOID'].'] of type ['.$matchingTag['taggedClass'].'], probable orphan');
219:                             }
220:                         }
221: 
222:                         if(isset($matches[$key])) {
223:                             // increment the weight if the same BO is tagged more than once
224:                             $weight = intval($matches[$key]) + 1;
225:                             $matches[$key] = $weight;
226:                         }else{
227:                             $matches[$key] = 1;
228:                         }
229:                     }
230:                 }
231: 
232:                 if ($config->get('cache.provider.name') != '') {
233:                     $key = get_class($sourceObject).'-'.$sourceObject->getOID().'-related'.($distinct == '' ? '' : '-distinct');
234:                     $this->addToCache($key, $matches);
235:                 }
236:             }
237:         }
238: 
239:         // sort the matches based on tag frequency weight
240:         arsort($matches);
241: 
242:         $this->numberFound = count($matches);
243: 
244:         // now paginate
245:         $matches = array_slice($matches, $start, $limit);
246: 
247:         // now load each object
248:         foreach ($matches as $key => $weight) {
249:             $parts = explode('-', $key);
250: 
251:             $BO = new $parts[0];
252:             $BO->load($parts[1]);
253: 
254:             $results[] = $BO;
255:         }
256: 
257:         return $results;
258:     }
259: 
260:     /**
261:      * {@inheritdoc}
262:      */
263:     public function index(AlphaDAO $sourceObject) {
264:         $taggedAttributes = $sourceObject->getTaggedAttributes();
265: 
266:         foreach($taggedAttributes as $tagged) {
267:             $tags = TagObject::tokenize($sourceObject->get($tagged), get_class($sourceObject), $sourceObject->getOID());
268: 
269:             foreach($tags as $tag) {
270:                 try {
271:                     $tag->save();
272:                 }catch(ValidationException $e){
273:                     /*
274:                      * The unique key has most-likely been violated because this BO is already tagged with this
275:                      * value, so we can ignore in this case.
276:                      */
277:                 }
278:             }
279:         }
280:     }
281: 
282:     /**
283:      * {@inheritdoc}
284:      */
285:     public function delete(AlphaDAO $sourceObject) {
286:         $tags = $sourceObject->getPropObject('tags')->getRelatedObjects();
287: 
288:         foreach ($tags as $tag)
289:             $tag->delete();
290:     }
291: 
292:     /**
293:      * {@inheritdoc}
294:      */
295:     public function getNumberFound() {
296:         return $this->numberFound;
297:     }
298: 
299:     /**
300:      * Load the tag search matches from the cache
301:      *
302:      * @since 1.2.4
303:      */
304:     private function loadFromCache($key) {
305:         global $config;
306: 
307:         try {
308:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
309:             $matches = $cache->get($key);
310: 
311:             if(!$matches) {
312:                 self::$logger->debug('Cache miss on key ['.$key.']');
313:                 return array();
314:             }else{
315:                 self::$logger->debug('Cache hit on key ['.$key.']');
316:                 return $matches;
317:             }
318:         }catch(Exception $e) {
319:             self::$logger->error('Error while attempting to load a search result from ['.$config->get('cache.provider.name').'] 
320:              instance: ['.$e->getMessage().']');
321: 
322:             return array();
323:         }
324:     }
325: 
326:     /**
327:      * Add the tag search matches to the cache
328:      *
329:      * @since 1.2.4
330:      */
331:     public function addToCache($key, $matches) {
332:         global $config;
333: 
334:         try {
335:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
336:             $cache->set($key, $matches, 86400); // cache search matches for a day
337: 
338:         }catch(Exception $e) {
339:             self::$logger->error('Error while attempting to store a search matches array to the ['.$config->get('cache.provider.name').'] 
340:                 instance: ['.$e->getMessage().']');
341:         }
342:     }
343: }
344: 
345: ?>
Alpha Framework 1.2.4 API Documentation API documentation generated by ApiGen 2.8.0