2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2013 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 * Implementation class (following a Bridge Pattern) for handling loading and saving deployed module metadata
40 * For example, listview or editview viewdefs
43 require_once 'modules/ModuleBuilder/parsers/views/AbstractMetaDataImplementation.php' ;
44 require_once 'modules/ModuleBuilder/parsers/views/MetaDataImplementationInterface.php' ;
45 require_once 'modules/ModuleBuilder/parsers/views/ListLayoutMetaDataParser.php' ;
46 require_once 'modules/ModuleBuilder/parsers/views/GridLayoutMetaDataParser.php' ;
47 require_once 'modules/ModuleBuilder/parsers/views/PopupMetaDataParser.php' ;
48 require_once 'modules/ModuleBuilder/Module/StudioModuleFactory.php' ;
49 require_once 'modules/ModuleBuilder/parsers/constants.php' ;
51 class DeployedMetaDataImplementation extends AbstractMetaDataImplementation implements MetaDataImplementationInterface
57 * @param string $moduleName
58 * @throws Exception Thrown if the provided view doesn't exist for this module
60 function __construct ($view , $moduleName)
64 if (! isset ( $GLOBALS [ 'beanList' ] [ $moduleName ] ))
66 sugar_die ( get_class ( $this ) . ": Modulename $moduleName is not a Deployed Module" ) ;
70 $this->_view = strtolower ( $view ) ;
71 $this->_moduleName = $moduleName ;
73 $module = StudioModuleFactory::getStudioModule( $moduleName ) ;
74 $this->module_dir = $module->seed->module_dir;
75 $fielddefs = $module->getFields();
77 //Load any custom views
78 $sm = StudioModuleFactory::getStudioModule($moduleName);
79 foreach($sm->sources as $file => $def)
81 if (!empty($def['view'])) {
82 $viewVar = "viewdefs";
83 if (!empty($def['type']) && !empty($this->_fileVariables[$def["type"]]))
84 $viewVar = $this->_fileVariables[$def["type"]];
85 $this->_fileVariables[$def['view']] = $viewVar;
90 foreach ( array ( MB_BASEMETADATALOCATION , MB_CUSTOMMETADATALOCATION , MB_WORKINGMETADATALOCATION , MB_HISTORYMETADATALOCATION ) as $type )
92 $this->_sourceFilename = $this->getFileName ( $view, $moduleName, $type ) ;
93 if($view == MB_POPUPSEARCH || $view == MB_POPUPLIST){
94 global $current_language;
95 $mod = return_module_language($current_language , $moduleName);
96 $layout = $this->_loadFromPopupFile ( $this->_sourceFilename , $mod, $view);
98 $layout = $this->_loadFromFile ( $this->_sourceFilename );
100 if ( null !== $layout )
102 // merge in the fielddefs from this layout
103 $this->_mergeFielddefs ( $fielddefs , $layout ) ;
108 if ($loaded === null)
113 // Special handling for QuickCreates - if we don't have a QuickCreate definition in the usual places, then use an EditView
115 $loaded = $this->_loadFromFile ( $this->getFileName ( MB_EDITVIEW, $this->_moduleName, MB_BASEMETADATALOCATION ) ) ;
117 if ($loaded === null)
118 throw new Exception( get_class ( $this ) . ": cannot convert from EditView to QuickCreate for Module $this->_moduleName - definitions for EditView are missing" ) ;
120 // Now change the array index
121 $temp = $loaded [ GridLayoutMetaDataParser::$variableMap [ MB_EDITVIEW ] ] ;
122 unset ( $loaded [ GridLayoutMetaDataParser::$variableMap [ MB_EDITVIEW ] ] ) ;
123 $loaded [ GridLayoutMetaDataParser::$variableMap [ MB_QUICKCREATE ] ] = $temp ;
124 // finally, save out our new definition so that we have a base record for the history to work from
125 $this->_sourceFilename = self::getFileName ( MB_QUICKCREATE, $this->_moduleName, MB_CUSTOMMETADATALOCATION ) ;
126 $this->_saveToFile ( $this->_sourceFilename, $loaded ) ;
127 $this->_mergeFielddefs ( $fielddefs , $loaded ) ;
130 case MB_DASHLETSEARCH:
132 $type = $module->getType () ;
133 $this->_sourceFilename = self::getFileName ( $view, $moduleName, MB_CUSTOMMETADATALOCATION ) ;
135 if(file_exists( "custom/modules/{$moduleName}/metadata/".basename ( $this->_sourceFilename))){
136 $loaded = $this->_loadFromFile ( "custom/modules/{$moduleName}/metadata/".basename ( $this->_sourceFilename) ) ;
139 "modules/{$moduleName}/Dashlets/My{$moduleName}Dashlet/My{$moduleName}Dashlet.data.php")){
140 $loaded = $this->_loadFromFile ( "modules/{$moduleName}/Dashlets/My{$moduleName}Dashlet/My{$moduleName}Dashlet.data.php");
143 $loaded = $this->_loadFromFile ( "include/SugarObjects/templates/$type/metadata/".basename ( $this->_sourceFilename ) ) ;
146 if ($loaded === null)
147 throw new Exception( get_class ( $this ) . ": cannot create dashlet view for module $moduleName - definitions for $view are missing in the SugarObject template for type $type" ) ;
148 $loaded = $this->replaceVariables($loaded, $module);
149 $temp = $this->_moduleName;
151 $this->_moduleName = $this->_moduleName.'Dashlet';
152 $this->_saveToFile ( $this->_sourceFilename, $loaded,false) ; // write out without the placeholder module_name and object
153 $this->_moduleName = $temp;
156 $this->_mergeFielddefs ( $fielddefs , $loaded ) ;
160 $type = $module->getType () ;
161 $this->_sourceFilename = self::getFileName ( $view, $moduleName, MB_CUSTOMMETADATALOCATION ) ;
163 global $current_language;
164 $mod = return_module_language($current_language , $moduleName);
165 $loadedForWrite = $this->_loadFromPopupFile ( "include/SugarObjects/templates/$type/metadata/".basename ( $this->_sourceFilename ) , $mod, $view, true);
166 if ($loadedForWrite === null)
167 throw new Exception( get_class ( $this ) . ": cannot create popup view for module $moduleName - definitions for $view are missing in the SugarObject template for type $type" ) ;
168 $loadedForWrite = $this->replaceVariables($loadedForWrite, $module);
169 $this->_saveToFile ( $this->_sourceFilename, $loadedForWrite , false , true) ; // write out without the placeholder module_name and object
170 $loaded = $this->_loadFromPopupFile ( "include/SugarObjects/templates/$type/metadata/".basename ( $this->_sourceFilename ) , $mod, $view);
171 $this->_mergeFielddefs ( $fielddefs , $loaded ) ;
176 if ( $loaded === null )
177 throw new Exception( get_class ( $this ) . ": view definitions for View $this->_view and Module $this->_moduleName are missing" ) ;
180 $this->_viewdefs = $loaded ;
181 // Set the original Viewdefs - required to ensure we don't lose fields from the base layout
182 // Check the base location first, then if nothing is there (which for example, will be the case for some QuickCreates, and some mobile layouts - see above)
183 // we need to check the custom location where the derived layouts will be
184 foreach ( array ( MB_BASEMETADATALOCATION , MB_CUSTOMMETADATALOCATION ) as $type )
186 $sourceFilename = $this->getFileName ( $view, $moduleName, $type ) ;
187 if($view == MB_POPUPSEARCH || $view == MB_POPUPLIST){
188 global $current_language;
189 $mod = return_module_language($current_language , $moduleName);
190 $layout = $this->_loadFromPopupFile ( $sourceFilename , $mod, $view);
192 $layout = $this->_loadFromFile ( $sourceFilename );
194 if ( null !== ($layout ) )
196 $this->_originalViewdefs = $layout ;
200 //For quick create viewdefs, if there is no quickcreatedefs.php under MB_BASEMETADATALOCATION, the original defs is editview defs.
201 if ($view == MB_QUICKCREATE) {
202 foreach(array(MB_QUICKCREATE, MB_EDITVIEW) as $v){
203 $sourceFilename = $this->getFileName($v, $moduleName, MB_BASEMETADATALOCATION ) ;
204 if (file_exists($sourceFilename )) {
205 $layout = $this->_loadFromFile($sourceFilename );
206 if (null !== $layout && isset($layout[GridLayoutMetaDataParser::$variableMap[$v]])) {
207 $layout = array(GridLayoutMetaDataParser::$variableMap[MB_QUICKCREATE] => $layout[GridLayoutMetaDataParser::$variableMap[$v]]);
213 if (null === $layout) {
214 $sourceFilename = $this->getFileName($view, $moduleName, MB_CUSTOMMETADATALOCATION );
215 $layout = $this->_loadFromFile($sourceFilename );
218 if (null !== $layout ) {
219 $this->_originalViewdefs = $layout ;
223 $this->_fielddefs = $fielddefs ;
224 $this->_history = new History ( $this->getFileName ( $view, $moduleName, MB_HISTORYMETADATALOCATION ) ) ;
228 function getLanguage ()
230 return $this->_moduleName ;
233 function getOriginalViewdefs()
235 return $this->_originalViewdefs;
240 * Save a draft layout
241 * @param array defs Layout definition in the same format as received by the constructor
243 function save ($defs)
245 //If we are pulling from the History Location, that means we did a restore, and we need to save the history for the previous file.
246 if ($this->_sourceFilename == $this->getFileName ( $this->_view, $this->_moduleName, MB_HISTORYMETADATALOCATION )) {
247 foreach ( array ( MB_WORKINGMETADATALOCATION , MB_CUSTOMMETADATALOCATION , MB_BASEMETADATALOCATION ) as $type ) {
248 if (file_exists($this->getFileName ( $this->_view, $this->_moduleName, $type ))) {
249 $this->_history->append ( $this->getFileName ( $this->_view, $this->_moduleName, $type )) ;
254 $this->_history->append ( $this->_sourceFilename ) ;
257 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . "->save(): writing to " . $this->getFileName ( $this->_view, $this->_moduleName, MB_WORKINGMETADATALOCATION ) ) ;
258 $this->_saveToFile ( $this->getFileName ( $this->_view, $this->_moduleName, MB_WORKINGMETADATALOCATION ), $defs ) ;
263 * @param array defs Layout definition in the same format as received by the constructor
265 function deploy ($defs)
267 if ($this->_sourceFilename == $this->getFileName ( $this->_view, $this->_moduleName, MB_HISTORYMETADATALOCATION )) {
268 foreach ( array ( MB_WORKINGMETADATALOCATION , MB_CUSTOMMETADATALOCATION , MB_BASEMETADATALOCATION ) as $type ) {
269 if (file_exists($this->getFileName ( $this->_view, $this->_moduleName, $type ))) {
270 $this->_history->append ( $this->getFileName ( $this->_view, $this->_moduleName, $type )) ;
275 $this->_history->append ( $this->_sourceFilename ) ;
277 // when we deploy get rid of the working file; we have the changes in the MB_CUSTOMMETADATALOCATION so no need for a redundant copy in MB_WORKINGMETADATALOCATION
278 // this also simplifies manual editing of layouts. You can now switch back and forth between Studio and manual changes without having to keep these two locations in sync
279 $workingFilename = $this->getFileName ( $this->_view, $this->_moduleName, MB_WORKINGMETADATALOCATION ) ;
281 if (file_exists ( $workingFilename ))
282 unlink ( $this->getFileName ( $this->_view, $this->_moduleName, MB_WORKINGMETADATALOCATION ) ) ;
283 $filename = $this->getFileName ( $this->_view, $this->_moduleName, MB_CUSTOMMETADATALOCATION ) ;
284 $GLOBALS [ 'log' ]->debug ( get_class ( $this ) . "->deploy(): writing to " . $filename ) ;
285 $this->_saveToFile ( $filename, $defs ) ;
287 // now clear the cache so that the results are immediately visible
288 include_once ('include/TemplateHandler/TemplateHandler.php') ;
289 TemplateHandler::clearCache ( $this->_moduleName ) ;
293 * Construct a full pathname for the requested metadata
294 * Can be called statically
295 * @param string view The view type, that is, EditView, DetailView etc
296 * @param string modulename The name of the module that will use this layout
299 public static function getFileName ($view , $moduleName , $type = MB_CUSTOMMETADATALOCATION)
303 MB_BASEMETADATALOCATION => '' ,
304 MB_CUSTOMMETADATALOCATION => 'custom/' ,
305 MB_WORKINGMETADATALOCATION => 'custom/working/' ,
306 MB_HISTORYMETADATALOCATION => 'custom/history/'
308 $type = strtolower ( $type ) ;
311 MB_DASHLETSEARCH => 'dashletviewdefs',
312 MB_DASHLET => 'dashletviewdefs',
313 MB_POPUPSEARCH => 'popupdefs',
314 MB_POPUPLIST => 'popupdefs',
315 MB_LISTVIEW => 'listviewdefs' ,
316 MB_BASICSEARCH => 'searchdefs' ,
317 MB_ADVANCEDSEARCH => 'searchdefs' ,
318 MB_EDITVIEW => 'editviewdefs' ,
319 MB_DETAILVIEW => 'detailviewdefs' ,
320 MB_QUICKCREATE => 'quickcreatedefs',
323 //In a deployed module, we can check for a studio module with file name overrides.
324 $sm = StudioModuleFactory::getStudioModule($moduleName);
325 foreach($sm->sources as $file => $def)
327 if (!empty($def['view'])) {
328 $filenames[$def['view']] = substr($file, 0, strlen($file) - 4);
334 if (! isset ( $pathMap [ $type ] ))
336 sugar_die ( "DeployedMetaDataImplementation->getFileName(): Type $type is not recognized" ) ;
338 if (! isset ( $filenames [ $view ] ))
340 sugar_die ( "DeployedMetaDataImplementation->getFileName(): View $view is not recognized" ) ;
346 // Construct filename
347 return $pathMap [ $type ] . 'modules/' . $moduleName . '/metadata/' . $filenames [ $view ] . '.php' ;
350 private function replaceVariables($defs, $module) {
352 "<object_name>" => $module->seed->object_name,
353 "<_object_name>" => strtolower($module->seed->object_name),
354 "<OBJECT_NAME>" => strtoupper($module->seed->object_name),
355 "<module_name>" => $module->seed->module_dir,
356 '<_module_name>'=> strtolower ( $module->seed->module_dir )
358 return $this->recursiveVariableReplace($defs, $module, $var_values);
361 public function getModuleDir(){
362 return $this->module_dir;
365 private function recursiveVariableReplace($arr, $module, $replacements) {
367 foreach ($arr as $key => $val) {
368 if (is_array($val)) {
370 $val = $this->recursiveVariableReplace($val, $module, $replacements);
371 foreach ($replacements as $var => $rep) {
372 $newkey = str_replace($var, $rep, $newkey);
374 $ret[$newkey] = $val;
380 foreach ($replacements as $var => $rep) {
381 $newkey = str_replace($var, $rep, $newkey);
382 $newval = str_replace($var, $rep, $newval);
385 $ret[$newkey] = $newval;
392 * This is just a wrapper to the private method _saveToFile
393 * @param $file the file name to save to
394 * @param $defs the defs to save to the file
397 public function saveToFile($file, $defs)
399 $this->_saveToFile ( $file, $defs ) ;