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: ?>