1: <?php
2:
3: /**
4: *
5: * The tag class used in tag clouds and search
6: *
7: * @package alpha::model
8: * @since 1.0
9: * @author John Collins <dev@alphaframework.org>
10: * @version $Id: TagObject.inc 1548 2012-07-29 17:07:07Z alphadevx $
11: * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
12: * @copyright Copyright (c) 2012, John Collins (founder of Alpha Framework).
13: * All rights reserved.
14: *
15: * <pre>
16: * Redistribution and use in source and binary forms, with or
17: * without modification, are permitted provided that the
18: * following conditions are met:
19: *
20: * * Redistributions of source code must retain the above
21: * copyright notice, this list of conditions and the
22: * following disclaimer.
23: * * Redistributions in binary form must reproduce the above
24: * copyright notice, this list of conditions and the
25: * following disclaimer in the documentation and/or other
26: * materials provided with the distribution.
27: * * Neither the name of the Alpha Framework nor the names
28: * of its contributors may be used to endorse or promote
29: * products derived from this software without specific
30: * prior written permission.
31: *
32: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
33: * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
34: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
35: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36: * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
37: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
39: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
40: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
41: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
42: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
43: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
44: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45: * </pre>
46: *
47: */
48: class TagObject extends AlphaDAO {
49: /**
50: * The name of the class of the object which is tagged
51: *
52: * @var String
53: * @since 1.0
54: */
55: protected $taggedClass;
56:
57: /**
58: * The OID of the object which is tagged
59: *
60: * @var Integer
61: * @since 1.0
62: */
63: protected $taggedOID;
64:
65: /**
66: * The content of the tag
67: *
68: * @var String
69: * @since 1.0
70: */
71: protected $content;
72:
73: /**
74: * An array of data display labels for the class properties
75: *
76: * @var array
77: * @since 1.0
78: */
79: protected $dataLabels = array("OID"=>"Tag ID#","taggedClass"=>"Class Name","taggedOID"=>"Tagged Object ID#","content"=>"Tag");
80:
81: /**
82: * The name of the database table for the class
83: *
84: * @var string
85: * @since 1.0
86: */
87: const TABLE_NAME = 'Tag';
88:
89: /**
90: * Trace logger
91: *
92: * @var Logger
93: * @since 1.0
94: */
95: private static $logger = null;
96:
97: /**
98: * The constructor
99: *
100: * @since 1.0
101: */
102: public function __construct() {
103: self::$logger = new Logger('TagObject');
104:
105: // ensure to call the parent constructor
106: parent::__construct();
107: $this->taggedClass = new String();
108: $this->taggedOID = new Integer();
109: $this->content = new String();
110:
111: $this->markUnique('taggedClass', 'taggedOID', 'content');
112: }
113:
114: /**
115: * Returns an array of TagObjects matching the class and OID provided
116: *
117: * @param $taggedClass The class name of the DAO that has been tagged.
118: * @param $taggedOID The Object ID of the DAO that has been tagged.
119: * @return array
120: * @since 1.0
121: * @throws AlphaException
122: * @throws IllegalArguementException
123: */
124: public function loadTags($taggedClass, $taggedOID) {
125:
126: global $config;
127:
128: if($taggedClass == '' || $taggedOID == '')
129: throw new IllegalArguementException('The taggedClass or taggedOID provided are empty');
130:
131: $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
132:
133: try {
134: $tags = $provider->loadAllByAttributes(array('taggedOID','taggedClass'), array($taggedOID, $taggedClass));
135: return $tags;
136: }catch(BONotFoundException $bonf) {
137: return array();
138: }catch(Exception $e) {
139: self::$logger->error($e->getMessage());
140: throw new AlphaException($e->getMessage());
141: }
142: }
143:
144: /**
145: * Returns a hash array of the most popular tags based on their occurence in the database,
146: * ordered by alphabet and restricted to the a count matching the $limit supplied. The
147: * returned has array uses the tag content as a key and the database value as a value.
148: *
149: * @param $limit
150: * @return array
151: * @since 1.0
152: * @throws AlphaException
153: */
154: public static function getPopularTagsArray($limit) {
155: global $config;
156:
157: $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new TagObject());
158:
159: $sqlQuery = "SELECT content, count(*) as count FROM ".TagObject::TABLE_NAME." GROUP BY content ORDER BY count DESC LIMIT $limit";
160:
161: try{
162: $result = $provider->query($sqlQuery);
163: }catch(CustomQueryException $e) {
164: throw new AlphaException('Failed to query the tags table, error is ['.$e->getMessage().']');
165: return array();
166: }
167:
168: // now build an array of tags to be returned
169: $popTags = array();
170:
171: foreach($result as $row) {
172: $popTags[$row['content']] = $row['count'];
173: }
174:
175: // sort the array by content key before returning
176: ksort($popTags);
177: return $popTags;
178: }
179:
180: /**
181: * Splits the passed content by spaces, filters (removes) stop words from stopwords.ini,
182: * and returns an array of TagObject instances.
183: *
184: * @param $content
185: * @param $taggedClass Optionally provide a BO class name
186: * @param $taggedOID Optionally provide a BO instance OID
187: * @param $applyStopwords Defaults true, set to false if you want to ignore the stopwords.
188: * @return array
189: * @throws AlphaException
190: * @since 1.0
191: */
192: public static function tokenize($content, $taggedClass='', $taggedOID='', $applyStopwords=true) {
193: if(self::$logger == null)
194: self::$logger = new Logger('TagObject');
195:
196: global $config;
197:
198: // apply stop words
199: $lowerWords = preg_split("/[\s,.:-]+/", $content);
200:
201: array_walk($lowerWords, 'TagObject::lowercaseArrayElement');
202:
203: if($applyStopwords) {
204: if(file_exists($config->get('app.root').'config/stopwords-'.$config->get('search.stop.words.size').'.ini')) {
205: $stopwords = file($config->get('app.root').'config/stopwords-'.$config->get('search.stop.words.size').'.ini', FILE_IGNORE_NEW_LINES);
206: }elseif(file_exists($config->get('app.root').'alpha/stopwords-'.$config->get('search.stop.words.size').'.ini')) {
207: $stopwords = file($config->get('app.root').'alpha/stopwords-'.$config->get('search.stop.words.size').'.ini', FILE_IGNORE_NEW_LINES);
208: }else{
209: throw new AlphaException('Unable to find a stopwords-'.$config->get('search.stop.words.size').'.ini file in the application!');
210: }
211:
212: array_walk($stopwords, 'TagObject::lowercaseArrayElement');
213:
214: $filtered = array_diff($lowerWords, $stopwords);
215: }else{
216: $filtered = $lowerWords;
217: }
218:
219: $tagObjects = array();
220: $tagContents = array();
221: foreach($filtered as $tagContent) {
222: // we only want to create word tags
223: if(AlphaValidator::isAlpha($tagContent)) {
224: // just making sure that we haven't added this one in already
225: if(!in_array($tagContent, $tagContents) && !empty($tagContent)) {
226: $tag = new TagObject();
227: $tag->set('content', trim(strtolower($tagContent)));
228: if(!empty($taggedClass))
229: $tag->set('taggedClass', $taggedClass);
230: if(!empty($taggedOID))
231: $tag->set('taggedOID', $taggedOID);
232:
233: array_push($tagObjects, $tag);
234: array_push($tagContents, $tagContent);
235: }
236: }
237: }
238:
239: self::$logger->debug('Tags generated: ['.var_export($tagContents, true).']');
240: return $tagObjects;
241: }
242:
243: /**
244: * Applies trim() and strtolower to the array element passed by reference
245: *
246: * @param $element
247: * @param $key (not required)
248: */
249: private static function lowercaseArrayElement(&$element, $key) {
250: $element = trim(strtolower($element));
251: }
252:
253: /**
254: * Cleans tag content by removing white spaces and converting to lowercase.
255: *
256: * @param $content
257: * @return string
258: */
259: public static function cleanTagContent($content) {
260: return trim(strtolower(str_replace(' ', '', $content)));
261: }
262: }
263:
264: ?>