2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
39 class SugarFeed extends Basic {
40 var $new_schema = true;
41 var $module_dir = 'SugarFeed';
42 var $object_name = 'SugarFeed';
43 var $table_name = 'sugarfeed';
44 var $importable = false;
50 var $modified_user_id;
51 var $modified_by_name;
57 var $modified_user_link;
58 var $assigned_user_id;
59 var $assigned_user_name;
60 var $assigned_user_link;
66 static function activateModuleFeed( $module, $updateDB = true ) {
67 if ( $module != 'UserFeed' ) {
68 // UserFeed is a fake module, used for the user postings to the feed
69 // Don't try to load up any classes for it
70 $fileList = SugarFeed::getModuleFeedFiles($module);
72 foreach ( $fileList as $fileName ) {
73 $feedClass = substr(basename($fileName),0,-4);
75 require_once($fileName);
76 $tmpClass = new $feedClass();
77 $tmpClass->installHook($fileName,$feedClass);
80 if ( $updateDB == true ) {
82 $admin = new Administration();
83 $admin->saveSetting('sugarfeed','module_'.$admin->db->quote($module),'1');
87 static function disableModuleFeed( $module, $updateDB = true ) {
88 if ( $module != 'UserFeed' ) {
89 // UserFeed is a fake module, used for the user postings to the feed
90 // Don't try to load up any classes for it
91 $fileList = SugarFeed::getModuleFeedFiles($module);
93 foreach ( $fileList as $fileName ) {
94 $feedClass = substr(basename($fileName),0,-4);
96 require_once($fileName);
97 $tmpClass = new $feedClass();
98 $tmpClass->removeHook($fileName,$feedClass);
102 if ( $updateDB == true ) {
104 $admin = new Administration();
105 $admin->saveSetting('sugarfeed','module_'.$admin->db->quote($module),'0');
109 static function flushBackendCache( ) {
110 // This function will flush the cache files used for the module list and the link type lists
111 sugar_cache_clear('SugarFeedModules');
112 if ( file_exists($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/moduleCache.php') ) {
113 unlink($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/moduleCache.php');
116 sugar_cache_clear('SugarFeedLinkType');
117 if ( file_exists($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/linkTypeCache.php') ) {
118 unlink($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/linkTypeCache.php');
123 static function getModuleFeedFiles( $module ) {
124 $baseDirList = array('modules/'.$module.'/SugarFeeds/', 'custom/modules/'.$module.'/SugarFeeds/');
126 // We store the files in a list sorted by the filename so you can override a default feed by
127 // putting your replacement feed in the custom directory with the same filename
130 foreach ( $baseDirList as $baseDir ) {
131 if ( ! file_exists($baseDir) ) {
135 while ( $file = $d->read() ) {
136 if ( $file{0} == '.' ) { continue; }
137 if ( substr($file,-4) == '.php' ) {
139 $fileList[$file] = $baseDir.$file;
147 static function getActiveFeedModules( ) {
148 // Stored in a cache somewhere
149 $feedModules = sugar_cache_retrieve('SugarFeedModules');
150 if ( $feedModules != null ) {
151 return($feedModules);
154 // Already stored in a file
155 if ( file_exists($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/moduleCache.php') ) {
156 require_once($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/moduleCache.php');
157 sugar_cache_put('SugarFeedModules',$feedModules);
161 // Gotta go looking for it
163 $admin = new Administration();
164 $admin->retrieveSettings();
166 $feedModules = array();
167 if ( isset($admin->settings['sugarfeed_enabled']) && $admin->settings['sugarfeed_enabled'] == '1' ) {
168 // Only enable modules if the feed system is enabled
169 foreach ( $admin->settings as $key => $value ) {
170 if ( strncmp($key,'sugarfeed_module_',17) === 0 ) {
171 // It's a module setting
172 if ( $value == '1' ) {
173 $moduleName = substr($key,17);
174 $feedModules[$moduleName] = $moduleName;
181 sugar_cache_put('SugarFeedModules',$feedModules);
182 if ( ! file_exists($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed') ) { mkdir_recursive($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed'); }
183 $fd = fopen($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/moduleCache.php','w');
184 fwrite($fd,'<'."?php\n\n".'$feedModules = '.var_export($feedModules,true).';');
190 static function getAllFeedModules( ) {
191 // Uncached, only used from the admin panel and during installation currently
192 $feedModules = array('UserFeed'=>'UserFeed');
194 $baseDirList = array('modules/', 'custom/modules/');
195 foreach ( $baseDirList as $baseDir ) {
196 if ( ! file_exists($baseDir) ) {
200 while ( $module = $d->read() ) {
201 if ( file_exists($baseDir.$module.'/SugarFeeds/') ) {
202 $dFeed = dir($baseDir.$module.'/SugarFeeds/');
203 while ( $file = $dFeed->read() ) {
204 if ( $file{0} == '.' ) { continue; }
205 if ( substr($file,-4) == '.php' ) {
207 $feedModules[$module] = $module;
214 return($feedModules);
219 * This method is a wrapper to pushFeed
221 * @param $text String value of the feed's description
222 * @param $bean The SugarBean that is triggering the feed
223 * @param $link_type boolean value indicating whether or not feed is a link type
224 * @param $link_url String value of the URL (for link types only)
226 static function pushFeed2($text, $bean, $link_type=false, $link_url=false) {
227 self::pushFeed($text, $bean->module_dir, $bean->id
228 ,$bean->assigned_user_id
234 static function pushFeed($text, $module, $id,
235 $record_assigned_user_id=false,
239 $feed = new SugarFeed();
240 if((empty($text) && empty($link_url)) || !$feed->ACLAccess('save', true) )
242 $GLOBALS['log']->error('Unable to save SugarFeed record (missing data or no ACL access)');
246 if(!empty($link_url)){
247 $linkClass = SugarFeed::getLinkClass($link_type);
248 if ( $linkClass !== FALSE ) {
249 $linkClass->handleInput($feed,$link_type,$link_url);
252 $text = strip_tags(from_html($text));
253 $text = '<b>{this.CREATED_BY}</b> ' . $text;
254 $feed->name = substr($text, 0, 255);
255 if(strlen($text) > 255){
256 $feed->description = substr($text, 255, 510);
259 if ( $record_assigned_user_id === false ) {
260 $feed->assigned_user_id = $GLOBALS['current_user']->id;
262 $feed->assigned_user_id = $record_assigned_user_id;
264 $feed->related_id = $id;
265 $feed->related_module = $module;
269 static function getLinkTypes() {
270 static $linkTypeList = null;
272 // Fastest, already stored in the static variable
273 if ( $linkTypeList != null ) {
274 return $linkTypeList;
277 // Second fastest, stored in a cache somewhere
278 $linkTypeList = sugar_cache_retrieve('SugarFeedLinkType');
279 if ( $linkTypeList != null ) {
280 return($linkTypeList);
283 // Third fastest, already stored in a file
284 if ( file_exists($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/linkTypeCache.php') ) {
285 require_once($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/linkTypeCache.php');
286 sugar_cache_put('SugarFeedLinkType',$linkTypeList);
287 return $linkTypeList;
290 // Slow, have to actually collect the data
291 $baseDirs = array('custom/modules/SugarFeed/linkHandlers/','modules/SugarFeed/linkHandlers');
293 $linkTypeList = array();
295 foreach ( $baseDirs as $dirName ) {
296 if ( !file_exists($dirName) ) { continue; }
298 while ( $file = $d->read() ) {
299 if ( $file{0} == '.' ) { continue; }
300 if ( substr($file,-4) == '.php' ) {
302 $typeName = substr($file,0,-4);
303 $linkTypeList[$typeName] = $typeName;
308 sugar_cache_put('SugarFeedLinkType',$linkTypeList);
309 if ( ! file_exists($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed') ) { mkdir_recursive($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed'); }
310 $fd = fopen($GLOBALS['sugar_config']['cache_dir'].'modules/SugarFeed/linkTypeCache.php','w');
311 fwrite($fd,'<'."?php\n\n".'$linkTypeList = '.var_export($linkTypeList,true).';');
314 return $linkTypeList;
317 static function getLinkClass( $linkName ) {
318 $linkTypeList = SugarFeed::getLinkTypes();
320 // Have to make sure the linkName is on the list, so they can't pass in linkName's like ../../config.php ... not that they could get anywhere if they did
321 if ( ! isset($linkTypeList[$linkName]) ) {
322 // No class by this name...
326 if ( file_exists('custom/modules/SugarFeed/linkHandlers/'.$linkName.'.php') ) {
327 require_once('custom/modules/SugarFeed/linkHandlers/'.$linkName.'.php');
329 require_once('modules/SugarFeed/linkHandlers/'.$linkName.'.php');
332 $linkClassName = 'FeedLinkHandler'.$linkName;
334 $linkClass = new $linkClassName();
339 function get_list_view_data(){
340 $data = parent::get_list_view_data();
342 if (ACLController::moduleSupportsACL($data['RELATED_MODULE']) && !ACLController::checkAccess($data['RELATED_MODULE'], 'view', $data['CREATED_BY'] == $GLOBALS['current_user']->id) && !ACLController::checkAccess($data['RELATED_MODULE'], 'list', $data['CREATED_BY'] == $GLOBALS['current_user']->id)){
346 if(is_admin($GLOBALS['current_user']) || (isset($data['CREATED_BY']) && $data['CREATED_BY'] == $GLOBALS['current_user']->id) ) {
347 $delete = ' - <a id="sugarFeedDeleteLink'.$data['ID'].'" href="#" onclick=\'SugarFeed.deleteFeed("'. $data['ID'] . '", "{this.id}"); return false;\'>'. $GLOBALS['app_strings']['LBL_DELETE_BUTTON_LABEL'].'</a>';
349 $data['NAME'] .= $data['DESCRIPTION'];
350 $data['NAME'] = '<div style="padding:3px">' . html_entity_decode($data['NAME']);
351 if(!empty($data['LINK_URL'])){
352 $linkClass = SugarFeed::getLinkClass($data['LINK_TYPE']);
353 if ( $linkClass !== FALSE ) {
354 $data['NAME'] .= $linkClass->getDisplay($data);
357 $data['NAME'] .= '<div class="byLineBox"><span class="byLineLeft">';
358 $data['NAME'] .= $this->getTimeLapse($data['DATE_ENTERED']) . ' </span><div class="byLineRight"><a id="sugarFeedReplyLink'.$data['ID'].'" href="#" onclick=\'SugarFeed.buildReplyForm("'.$data['ID'].'", "{this.id}", this); return false;\'>'.$GLOBALS['app_strings']['LBL_EMAIL_REPLY'].'</a>' .$delete. '</div></div>';
360 $data['NAME'] .= $this->fetchReplies($data);
364 function fetchReplies($data) {
365 $seedBean = new SugarFeed;
367 $replies = $seedBean->get_list('date_entered',"related_module = 'SugarFeed' AND related_id = '".$data['ID']."'");
369 if ( count($replies['list']) < 1 ) {
374 $replyHTML = '<div class="clear"></div><blockquote>';
376 foreach ( $replies['list'] as $reply ) {
377 // Setup the delete link
379 if(is_admin($GLOBALS['current_user']) || $data['CREATED_BY'] == $GLOBALS['current_user']->id) {
380 $delete = '<a id="sugarFieldDeleteLink'.$reply->id.'" href="#" onclick=\'SugarFeed.deleteFeed("'. $reply->id . '", "{this.id}"); return false;\'>'. $GLOBALS['app_strings']['LBL_DELETE_BUTTON_LABEL'].'</a>';
383 $image_url = 'include/images/default_user_feed_picture.png';
384 if ( isset($reply->created_by) ) {
385 $user = loadBean('Users');
386 $user->retrieve($reply->created_by);
387 if ( !empty($user->picture) ) {
388 $image_url = 'index.php?entryPoint=download&id='.$user->picture.'&type=SugarFieldImage&isTempFile=1';
391 $replyHTML .= '<div style="float: left; margin-right: 3px; width: 50px; height: 50px;"><img src="'.$image_url.'" style="max-width: 50px; max-height: 50px;"></div> ';
392 $replyHTML .= str_replace("{this.CREATED_BY}",get_assigned_user_name($reply->created_by),html_entity_decode($reply->name)).'<br>';
393 $replyHTML .= '<div class="byLineBox"><span class="byLineLeft">'. $this->getTimeLapse($reply->date_entered) . ' </span><div class="byLineRight"> ' .$delete. '</div></div><div class="clear"></div>';
396 $replyHTML .= '</blockquote>';
401 static function getTimeLapse($startDate)
403 $seconds = $GLOBALS['timedate']->getNow()->ts - $GLOBALS['timedate']->fromUser($startDate)->ts;
404 $minutes = $seconds/60;
405 $seconds = $seconds % 60;
406 $hours = floor( $minutes / 60);
407 $minutes = $minutes % 60;
408 $days = floor( $hours / 24);
409 $hours = $hours % 24;
410 $weeks = floor( $days / 7);
414 $result = translate('LBL_TIME_LAST_WEEK','SugarFeed').' ';
416 }else if($weeks > 1){
417 $result .= $weeks . ' '.translate('LBL_TIME_WEEKS','SugarFeed').' ';
419 $result .= $days . ' '.translate('LBL_TIME_DAYS','SugarFeed').' ';
423 $result = translate('LBL_TIME_YESTERDAY','SugarFeed').' ';
426 $result .= $days . ' '. translate('LBL_TIME_DAYS','SugarFeed').' ';
429 $result .= $hours . ' '.translate('LBL_TIME_HOUR','SugarFeed').' ';
431 $result .= $hours . ' '.translate('LBL_TIME_HOURS','SugarFeed').' ';
435 $result .= $minutes . ' ' . translate('LBL_TIME_MINUTE','SugarFeed'). ' ';
437 $result .= $minutes . ' ' . translate('LBL_TIME_MINUTES','SugarFeed'). ' ';
440 if($hours == 0 && $minutes == 0) {
442 $result = $seconds . ' ' . translate('LBL_TIME_SECOND','SugarFeed');
444 $result = $seconds . ' ' . translate('LBL_TIME_SECONDS','SugarFeed');
449 return $result . ' ' . translate('LBL_TIME_AGO','SugarFeed');
453 * Parse a piece of text and replace with proper display tags.
458 public static function parseMessage($input){
459 $urls = getUrls($input);
460 foreach($urls as $url){
461 $output = "<a href='$url' target='_blank'>".$url."</a>";
462 $input = str_replace($url, $output, $input);