Overview

Packages

  • application
    • commands
    • components
      • actions
      • filters
      • leftWidget
      • permissions
      • sortableWidget
      • util
      • webupdater
      • x2flow
        • actions
        • triggers
      • X2GridView
      • X2Settings
    • controllers
    • models
      • embedded
    • modules
      • accounts
        • controllers
        • models
      • actions
        • controllers
        • models
      • calendar
        • controllers
        • models
      • charts
        • models
      • contacts
        • controllers
        • models
      • docs
        • components
        • controllers
        • models
      • groups
        • controllers
        • models
      • marketing
        • components
        • controllers
        • models
      • media
        • controllers
        • models
      • mobile
        • components
      • opportunities
        • controllers
        • models
      • products
        • controllers
        • models
      • quotes
        • controllers
        • models
      • services
        • controllers
        • models
      • template
        • models
      • users
        • controllers
        • models
      • workflow
        • controllers
        • models
      • x2Leads
        • controllers
        • models
  • None
  • system
    • base
    • caching
    • console
    • db
      • ar
      • schema
    • validators
    • web
      • actions
      • auth
      • helpers
      • widgets
        • captcha
        • pagers
  • zii
    • widgets
      • grid

Classes

  • AccountsGridViewProfileWidget
  • ActionMenu
  • ActionsGridViewProfileWidget
  • ActionsQuickCreateRelationshipBehavior
  • ActiveDateRangeInput
  • ApplicationConfigBehavior
  • Attachments
  • ChatBox
  • CommonControllerBehavior
  • ContactMapInlineTags
  • ContactsGridViewProfileWidget
  • CronForm
  • CSaveRelationsBehavior
  • DateRangeInputsWidget
  • DocsGridViewProfileWidget
  • DocViewer
  • DocViewerProfileWidget
  • EButtonColumnWithClearFilters
  • EmailDeliveryBehavior
  • EmailProgressControl
  • EncryptedFieldsBehavior
  • EventsChartProfileWidget
  • FileUploader
  • FontPickerInput
  • Formatter
  • FormView
  • GridViewWidget
  • History
  • IframeWidget
  • ImportExportBehavior
  • InlineActionForm
  • InlineEmailAction
  • InlineEmailForm
  • InlineEmailModelBehavior
  • InlineQuotes
  • JSONEmbeddedModelFieldsBehavior
  • JSONFieldsDefaultValuesBehavior
  • LeadRoutingBehavior
  • LeftWidget
  • LoginThemeHelper
  • LoginThemeHelperBase
  • MarketingGridViewProfileWidget
  • MediaBox
  • MessageBox
  • MobileFormatter
  • MobileFormLayoutRenderer
  • MobileLayoutRenderer
  • MobileLoginThemeHelper
  • MobileViewLayoutRenderer
  • ModelFileUploader
  • NewWebLeadsGridViewProfileWidget
  • NormalizedJSONFieldsBehavior
  • NoteBox
  • OnlineUsers
  • OpportunitiesGridViewProfileWidget
  • Panel
  • ProfileDashboardManager
  • ProfileGridViewWidget
  • ProfileLayoutEditor
  • ProfilesGridViewProfileWidget
  • Publisher
  • PublisherActionTab
  • PublisherCalendarEventTab
  • PublisherCallTab
  • PublisherCommentTab
  • PublisherEventTab
  • PublisherSmallCalendarEventTab
  • PublisherTab
  • PublisherTimeTab
  • QuickContact
  • QuickCreateRelationshipBehavior
  • QuotesGridViewProfileWidget
  • RecordAliasesWidget
  • RecordViewLayoutManager
  • RecordViewWidgetManager
  • RememberPagination
  • Reminders
  • ResponseBehavior
  • ResponsiveHtml
  • SearchIndexBehavior
  • ServicesGridViewProfileWidget
  • SmallCalendar
  • SmartActiveDataProvider
  • SmartDataProviderBehavior
  • SmartSort
  • SocialForm
  • SortableWidgetManager
  • SortableWidgets
  • TagBehavior
  • TagCloud
  • TemplatesGridViewProfileWidget
  • TimeZone
  • TopContacts
  • TopSites
  • TransformedFieldStorageBehavior
  • TranslationLogger
  • TwitterFeed
  • TwoColumnSortableWidgetManager
  • UpdaterBehavior
  • UpdatesForm
  • UserIdentity
  • UsersChartProfileWidget
  • WorkflowBehavior
  • X2ActiveGridView
  • X2ActiveGridViewForSortableWidgets
  • X2AssetManager
  • X2AuthManager
  • X2ChangeLogBehavior
  • X2ClientScript
  • X2Color
  • X2DateUtil
  • X2FixtureManager
  • X2FlowFormatter
  • X2GridView
  • X2GridViewBase
  • X2GridViewForSortableWidgets
  • X2GridViewSortableWidgetsBehavior
  • X2LeadsGridViewProfileWidget
  • X2LinkableBehavior
  • X2ListView
  • X2PillBox
  • X2ProgressBar
  • X2SmartSearchModelBehavior
  • X2TimestampBehavior
  • X2TranslationBehavior
  • X2UrlRule
  • X2WebModule
  • X2Widget
  • X2WidgetList
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /*****************************************************************************************
  3:  * X2Engine Open Source Edition is a customer relationship management program developed by
  4:  * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
  5:  * 
  6:  * This program is free software; you can redistribute it and/or modify it under
  7:  * the terms of the GNU Affero General Public License version 3 as published by the
  8:  * Free Software Foundation with the addition of the following permission added
  9:  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 10:  * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
 11:  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 12:  * 
 13:  * This program is distributed in the hope that it will be useful, but WITHOUT
 14:  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 15:  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 16:  * details.
 17:  * 
 18:  * You should have received a copy of the GNU Affero General Public License along with
 19:  * this program; if not, see http://www.gnu.org/licenses or write to the Free
 20:  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 21:  * 02110-1301 USA.
 22:  * 
 23:  * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
 24:  * California 95067, USA. or at email address contact@x2engine.com.
 25:  * 
 26:  * The interactive user interfaces in modified source and object code versions
 27:  * of this program must display Appropriate Legal Notices, as required under
 28:  * Section 5 of the GNU Affero General Public License version 3.
 29:  * 
 30:  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
 31:  * these Appropriate Legal Notices must retain the display of the "Powered by
 32:  * X2Engine" logo. If the display of the logo is not reasonably feasible for
 33:  * technical reasons, the Appropriate Legal Notices must display the words
 34:  * "Powered by X2Engine".
 35:  *****************************************************************************************/
 36: 
 37: /**
 38:  * TagBehavior class file.
 39:  *
 40:  * @package application.components
 41:  * TagBehavior adds and removes tags from x2_tags when a record is created, updated or deleted
 42:  */
 43: class TagBehavior extends X2ActiveRecordBehavior {
 44: 
 45:     /**
 46:      * @var bool $disableTagScanning
 47:      */
 48:     public $disableTagScanning = false; 
 49: 
 50:     /**
 51:      * @var a cache of all tags associated with the owner model
 52:      */
 53:     protected $_tags = null;
 54: 
 55:     private $flowTriggersEnabled = true; 
 56: 
 57:     public function rules () {
 58:         return array (
 59:             array ('tags', 'safe', 'on' => 'search'),
 60:         );
 61:     }
 62: 
 63:     public function enableTagTriggers () {
 64:         $this->flowTriggersEnabled = true;
 65:     }
 66: 
 67:     public function disableTagTriggers () {
 68:         $this->flowTriggersEnabled = false;
 69:     }
 70: 
 71:     /**
 72:      * Responds to {@link CModel::onAfterSave} event.
 73:      *
 74:      * Matches tags provided they:
 75:      *    - start with a #
 76:      *    - consist of these characters: UTF-8 letters, numbers, _ and - (but only in the middle 
 77:      *        of the tag)
 78:      *    - come after a space or . or are at the beginning
 79:      *    - are not in quotes
 80:      *
 81:      * Looks up any current tag records, and saves a tag record for each new tag.
 82:      * Note: does not delete tags when they are removed from text fields (this would screw with 
 83:      *  manual tagging)
 84:      *
 85:      * @param CModelEvent $event event parameter
 86:      */
 87:     public function afterSave($event) {
 88:         // look up current tags
 89:         $oldTags = $this->getTags();
 90:         $newTags = array();
 91: 
 92:         foreach ($this->scanForTags() as $tag) {
 93:             if (!$this->hasTag ($tag, $oldTags)) { // don't add duplicates if there are already tags
 94:                 $tagModel = new Tags;
 95:                 $tagModel->tag = $tag;  
 96:                 $tagModel->type = get_class($this->getOwner());
 97:                 $tagModel->itemId = $this->getOwner()->id;
 98:                 $tagModel->itemName = $this->getOwner()->name;
 99:                 $tagModel->taggedBy = Yii::app()->getSuName();
100:                 $tagModel->timestamp = time();
101:                 if ($tagModel->save())
102:                     $newTags[] = $tag;
103:             }
104:         }
105:         $this->_tags = $newTags + $oldTags; // update tag cache
106: 
107:         if (!empty($newTags) && $this->flowTriggersEnabled) {
108:             X2Flow::trigger('RecordTagAddTrigger', array(
109:                 'model' => $this->getOwner(),
110:                 'tags' => $newTags,
111:             ));
112:         }
113:     }
114: 
115:     /**
116:      * Responds to {@link CActiveRecord::onAfterDelete} event.
117:      * Deletes all the tags for this model
118:      *
119:      * @param CModelEvent $event event parameter
120:      */
121:     public function afterDelete($event) {
122:         $this->clearTags();
123:     }
124: 
125:     /**
126:      * Scans through every 'varchar' and 'text' field in the owner model for tags.
127:      *
128:      * @return array an array of tags
129:      */
130:     public function scanForTags() {
131:         if ($this->disableTagScanning) return array ();
132:         $tags = array();
133: 
134:         if (Yii::app()->settings->disableAutomaticRecordTagging) {
135:             return array();
136:         }
137: 
138:         // Type of fields to search in 
139:         $fieldTypes = array (
140:             'text'
141:         );
142: 
143:         foreach ($this->getOwner()->getFields(true) as $fieldName => $field) {
144:             if (!in_array($field->type, $fieldTypes)) {
145:                 continue;
146:             }
147: 
148:             $text = $this->owner->$fieldName;
149: 
150:             $matches = $this->matchTags ($text);
151:             $tags = array_merge($matches, $tags);
152:         }
153:         $tags = array_unique($tags);
154:         return $tags;
155:     }
156: 
157:     /**
158:      * Finds all tag matches in text
159:      * @param string $text
160:      * @return array
161:      */
162:     public function matchTags($text) {
163: 
164:         // Array of excludes such as style tags, href attributes, etc
165:         $excludes = array(
166:             '/<style[^<]*<\/style>/',
167:             '/style="[^"]*"/',
168:             '/style=\'[^\']*\'/',
169:         );
170: 
171:         foreach ($excludes as $exp) {
172:             $text = preg_replace($exp, '', $text);
173:         }
174: 
175:         // Primary expression to filter out tags
176:         $exp = '/(?:|\s)(#(?:\w+|\w[-\w]+\w))(?:$|\s)/u';
177: 
178:         $matches = array();
179:         preg_match_all($exp, $text, $matches);
180: 
181:         return $matches[1];
182:     }
183: 
184:     /**
185:      * @param string $tag 
186:      * @param array|null $oldTags 
187:      * @return true if record has tag already, false otherwise
188:      */
189:     public function hasTag ($tag, array $oldTags=null, $refresh=false) {
190:         $oldTags = $oldTags === null ? $this->getTags ($refresh) : $oldTags;
191:         return in_array (strtolower (Tags::normalizeTag ($tag)), array_map (function ($tag) {
192:             return strtolower ($tag); 
193:         }, $oldTags));
194:     }
195: 
196:     /**
197:      * Tests whether the owner model has any (OR mode) or all (AND mode) of the provided tags
198:      *
199:      * @param mixed $tags sring or array of strings containing tags
200:      * @param array $mode logic mode (either "AND" or "OR") for the test
201:      * @return boolean the test result
202:      */
203:     public function hasTags($tags, $mode = 'OR') {
204:         $matches = array_intersect($this->getTags(), Tags::normalizeTags((array) $tags));
205: 
206:         if ($mode === 'AND')
207:             return count($matches) === count((array) $tags);  // all tags must be present
208:         else
209:             return count($matches) > 0;  // at least one tag must be present
210:     }
211: 
212:     /**
213:      * Looks up the tags associated with the owner model.
214:      * Uses {@link $tags} as a cache to prevent repeated queries.
215:      *
216:      * @return array an array of tags
217:      */
218:     public function getTags($refreshCache = false) {
219:         if ($this->_tags === null || $refreshCache) {
220:             $this->_tags = Yii::app()->db->createCommand()
221:                 ->select('tag')
222:                 ->from(CActiveRecord::model('Tags')->tableName())
223:                 ->where(
224:                     'type=:type AND itemId=:itemId', 
225:                     array(
226:                         ':type' => get_class($this->getOwner()), 
227:                         ':itemId' => $this->getOwner()->id))
228:                 ->queryColumn();
229:         }
230:         return $this->_tags;
231:     }
232: 
233:     public function setTags ($tags, $rawInput=false) {
234:         if (!$rawInput)
235:             $tags = is_string ($tags) ? array_map (function ($tag) {
236:                 return trim ($tag);
237:             }, explode (Tags::DELIM, $tags)) : $tags;
238:         $this->_tags = $tags;
239:     }
240: 
241:     public function compareTags (CDbCriteria $criteria) {
242:         $tags = $this->tags;
243:         $inQuery = array ();
244:         $params = array (
245:             ':type' => get_class ($this->owner),
246:         );
247:         for ($i = 0; $i < count ($tags); $i++) {
248:             if ($tags[$i] === '') {
249:                 unset ($tags[$i]);
250:                 $i--;
251:                 continue;
252:             } else {
253:                 $inQuery[] = 'b.tag LIKE :'.$i;
254:                 $params[':'.$i] = '%'.$tags[$i].'%';
255:             }
256:         }
257:         $tagConditions = implode (' OR ',$inQuery);
258: 
259:         if ($tagConditions) {
260:             $criteria->distinct = true;
261:             $criteria->join .= ' JOIN x2_tags b ON (b.itemId=t.id AND b.type=:type '.
262:                 'AND ('.$tagConditions.'))';
263:             $criteria->params = $params;
264:         }
265: 
266:         return $criteria;
267:     }
268: 
269:     public function renderTagInput () {
270:         $clone = clone $this->owner;  
271:         $clone->setTags (implode (', ', $this->tags), true);
272: 
273:         return CHtml::activeTextField ($clone, 'tags');
274:     }
275: 
276:     /**
277:      * Adds the specified tag(s) to the owner model, but not
278:      * if the tag has already been added.
279:      * @param mixed $tags a string or array of strings containing tags
280:      * @return boolean whether or not at least one tag was added successfully
281:      */
282:     public function addTags($tags) {
283:         $result = false;
284:         $addedTags = array();
285: 
286:         foreach ((array) $tags as $tagName) {
287:             if (empty($tagName))
288:                 continue;
289:             if (!$this->hasTag ($tagName)) { // check for duplicate tag
290:                 $tag = new Tags;
291:                 $tag->tag = Tags::normalizeTag ($tagName);
292:                 $tag->itemId = $this->getOwner()->id;
293:                 $tag->type = get_class($this->getOwner());
294:                 $tag->taggedBy = Yii::app()->getSuName();
295:                 $tag->timestamp = time();
296:                 $tag->itemName = $this->getOwner()->name;
297: 
298:                 if ($tag->save()) {
299:                     $this->_tags[] = $tag->tag; // update tag cache
300:                     $addedTags[] = $tagName;
301:                     $result = true;
302:                 } else {
303:                     throw new CHttpException(
304:                         422, 'Failed saving tag due to errors: ' . json_encode($tag->errors));
305:                 }
306:             }
307:         }
308:         if ($this->flowTriggersEnabled)
309:             X2Flow::trigger('RecordTagAddTrigger', array(
310:                 'model' => $this->getOwner(),
311:                 'tags' => $addedTags,
312:             ));
313: 
314:         return $result;
315:     }
316: 
317:     /**
318:      * Removes the specified tag(s) from the owner model
319:      * @param mixed $tags a string or array of strings containing tags
320:      * @return boolean whether or not at least one tag was deleted successfully
321:      */
322:     public function removeTags($tags) {
323:         $result = false;
324:         $removedTags = array();
325:         $tags = Tags::normalizeTags((array) $tags);
326: 
327:         foreach ((array) $tags as $tag) {
328:             if (empty($tag))
329:                 continue;
330: 
331:             $attributes = array(
332:                 'type' => get_class($this->getOwner()),
333:                 'itemId' => $this->getOwner()->id,
334:                 'tag' => $tag
335:             );
336:             if ($this->hasTag ($tag) &&
337:                 CActiveRecord::model('Tags')->deleteAllByAttributes($attributes) > 0) {
338: 
339:                 if (false !== $offset = array_search($tag, $this->_tags))
340:                     unset($this->_tags[$offset]); // update tag cache
341: 
342:                 $removedTags[] = $tag;
343:                 $result = true;
344:             }
345:         }
346:         if ($this->flowTriggersEnabled)
347:             X2Flow::trigger('RecordTagRemoveTrigger', array(
348:                 'model' => $this->getOwner(),
349:                 'tags' => $removedTags,
350:             ));
351: 
352:         return $result;
353:     }
354: 
355:     /**
356:      * Deletes all tags associated with the owner model
357:      */
358:     public function clearTags() {
359:         $this->_tags = array(); // clear tag cache
360: 
361:         return (bool) CActiveRecord::model('Tags')->deleteAllByAttributes(array(
362:             'type' => get_class($this->getOwner()),
363:             'itemId' => $this->getOwner()->id)
364:         );
365:     }
366: 
367: }
368: 
X2CRM Documentation API documentation generated by ApiGen 2.8.0