2 /*********************************************************************************
3 * SugarCRM Community Edition is a customer relationship management program developed by
4 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
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 SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
11 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
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
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
23 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
24 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
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.
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 * SugarCRM" 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 SugarCRM".
35 ********************************************************************************/
40 * TemplateHandler builds templates using SugarFields and a generic view.
41 * Currently it handles EditViews and DetailViews. It creates a smarty template cached in
42 * cache/modules/moduleName/view
45 class TemplateHandler {
47 var $templateDir = 'modules/';
49 function TemplateHandler() {
50 $this->cacheDir = sugar_cached('');
53 function loadSmarty(){
55 $this->ss = new Sugar_Smarty();
62 * Helper function to remove all .tpl files in the cache directory
67 foreach($beanList as $module_dir =>$object_name){
68 TemplateHandler::clearCache($module_dir);
75 * Helper function to remove cached .tpl files for a particular module
77 * @param String $module The module directory to clear
78 * @param String $view Optional view value (DetailView, EditView, etc.)
80 function clearCache($module, $view=''){
81 $cacheDir = create_cache_directory('modules/'. $module . '/');
83 while($e = $d->read()){
84 if(!empty($view) && $e != $view )continue;
86 if(is_file($cacheDir . $e) && $end > 1 && substr($e, $end) == '.tpl'){
87 unlink($cacheDir . $e);
94 * This is a private function that should be called only from checkTemplate method
96 * @param module string module name
97 * @param view string view need (eg DetailView, EditView, etc)
98 * @param tpl string generic tpl to use
99 * @param ajaxSave boolean parameter indicating whether or not this is coming from an Ajax call
100 * @param metaDataDefs metadata definition as Array
102 function buildTemplate($module, $view, $tpl, $ajaxSave, $metaDataDefs) {
105 $cacheDir = create_cache_directory($this->templateDir. $module . '/');
106 $file = $cacheDir . $view . '.tpl';
107 $string = '{* Create Date: ' . date('Y-m-d H:i:s') . "*}\n";
108 $this->ss->left_delimiter = '{{';
109 $this->ss->right_delimiter = '}}';
110 $this->ss->assign('module', $module);
111 $this->ss->assign('built_in_buttons', array('CANCEL', 'DELETE', 'DUPLICATE', 'EDIT', 'FIND_DUPLICATES', 'SAVE', 'CONNECTOR'));
112 $contents = $this->ss->fetch($tpl);
113 //Insert validation and quicksearch stuff here
114 if($view == 'EditView' || strpos($view,'QuickCreate') || $ajaxSave || $view == "ConvertLead") {
116 global $dictionary, $beanList, $app_strings, $mod_strings;
117 $mod = $beanList[$module];
119 if($mod == 'aCase') {
123 $defs = $dictionary[$mod]['fields'];
125 //Retrieve all panel field definitions with displayParams Array field set
126 $panelFields = array();
128 foreach($metaDataDefs['panels'] as $panel) {
129 foreach($panel as $row) {
130 foreach($row as $entry) {
135 if(is_array($entry) &&
136 isset($entry['name']) &&
137 isset($entry['displayParams']) &&
138 isset($entry['displayParams']['required']) &&
139 $entry['displayParams']['required']) {
140 $panelFields[$entry['name']] = $entry;
143 if(is_array($entry)) {
144 $defs2[$entry['name']] = $entry;
146 $defs2[$entry] = array('name' => $entry);
152 foreach($panelFields as $field=>$value) {
154 if(!is_array($value['displayParams']['required'])) {
155 $nameList[] = $field;
157 foreach($value['displayParams']['required'] as $groupedField) {
158 $nameList[] = $groupedField;
162 foreach($nameList as $x) {
163 if(isset($defs[$x]) &&
164 isset($defs[$x]['type']) &&
165 !isset($defs[$x]['required'])) {
166 $defs[$x]['required'] = true;
171 //Create a base class with field_name_map property
172 $sugarbean = new stdClass;
173 $sugarbean->field_name_map = $defs;
174 $sugarbean->module_dir = $module;
176 $javascript = new javascript();
177 $view = $view == 'QuickCreate' ? "QuickCreate_{$module}" : $view;
178 $javascript->setFormName($view);
180 $javascript->setSugarBean($sugarbean);
181 if ($view != "ConvertLead")
182 $javascript->addAllFields('', null,true);
184 $validatedFields = array();
185 $javascript->addToValidateBinaryDependency('assigned_user_name', 'alpha', $javascript->buildStringToTranslateInSmarty('ERR_SQS_NO_MATCH_FIELD').': '.$javascript->buildStringToTranslateInSmarty('LBL_ASSIGNED_TO'), 'false', '', 'assigned_user_id');
186 $validatedFields[] = 'assigned_user_name';
187 //Add remaining validation dependency for related fields
188 //1) a relate type as defined in vardefs
189 //2) set in metadata layout
190 //3) not have validateDepedency set to false in metadata
191 //4) have id_name in vardef entry
192 //5) not already been added to Array
193 foreach($sugarbean->field_name_map as $name=>$def) {
195 if($def['type']=='relate' &&
196 isset($defs2[$name]) &&
197 (!isset($defs2[$name]['validateDependency']) || $defs2[$name]['validateDependency'] === true) &&
198 isset($def['id_name']) &&
199 !in_array($name, $validatedFields)) {
201 if(isset($mod_strings[$def['vname']])
202 || isset($app_strings[$def['vname']])
203 || translate($def['vname'],$sugarbean->module_dir) != $def['vname']) {
204 $vname = $def['vname'];
207 $vname = "undefined";
209 $javascript->addToValidateBinaryDependency($name, 'alpha', $javascript->buildStringToTranslateInSmarty('ERR_SQS_NO_MATCH_FIELD').': '.$javascript->buildStringToTranslateInSmarty($vname), (!empty($def['required']) ? 'true' : 'false'), '', $def['id_name']);
210 $validatedFields[] = $name;
214 $contents .= "{literal}\n";
215 $contents .= $javascript->getScript();
216 $contents .= $this->createQuickSearchCode($defs, $defs2, $view, $module);
217 $contents .= "{/literal}\n";
218 }else if(preg_match('/^SearchForm_.+/', $view)){
219 global $dictionary, $beanList, $app_strings, $mod_strings;
220 $mod = $beanList[$module];
222 if($mod == 'aCase') {
226 $defs = $dictionary[$mod]['fields'];
227 $contents .= '{literal}';
228 $contents .= $this->createQuickSearchCode($defs, array(), $view);
229 $contents .= '{/literal}';
232 //Remove all the copyright comments
233 $contents = preg_replace('/\{\*[^\}]*?\*\}/', '', $contents);
235 if($fh = @sugar_fopen($file, 'w')) {
236 fputs($fh, $contents);
241 $this->ss->left_delimiter = '{';
242 $this->ss->right_delimiter = '}';
246 * Checks if a template exists
248 * @param module string module name
249 * @param view string view need (eg DetailView, EditView, etc)
251 function checkTemplate($module, $view, $checkFormName = false, $formName='') {
252 if(inDeveloperMode() || !empty($_SESSION['developerMode'])){
255 $view = $checkFormName ? $formName : $view;
256 return file_exists($this->cacheDir . $this->templateDir . $module . '/' .$view . '.tpl');
260 * Retreives and displays a template
262 * @param module string module name
263 * @param view string view need (eg DetailView, EditView, etc)
264 * @param tpl string generic tpl to use
265 * @param ajaxSave boolean parameter indicating whether or not this is from an Ajax operation
266 * @param metaData Optional metadata definition Array
268 function displayTemplate($module, $view, $tpl, $ajaxSave = false, $metaDataDefs = null) {
270 if(!$this->checkTemplate($module, $view)) {
271 $this->buildTemplate($module, $view, $tpl, $ajaxSave, $metaDataDefs);
273 $file = $this->cacheDir . $this->templateDir . $module . '/' . $view . '.tpl';
274 if(file_exists($file)) {
275 return $this->ss->fetch($file);
278 $GLOBALS['log']->fatal($app_strings['ERR_NO_SUCH_FILE'] .": $file");
279 return $app_strings['ERR_NO_SUCH_FILE'] .": $file";
284 * Deletes an existing template
286 * @param module string module name
287 * @param view string view need (eg DetailView, EditView, etc)
289 function deleteTemplate($module, $view) {
290 if(is_file($this->cacheDir . $this->templateDir . $module . '/' .$view . '.tpl')) {
291 return unlink($this->cacheDir . $this->templateDir . $module . '/' .$view . '.tpl');
298 * createQuickSearchCode
299 * This function creates the $sqs_objects array that will be used by the quicksearch Javascript
300 * code. The $sqs_objects array is wrapped in a $json->encode call.
302 * @param array $def The vardefs.php definitions
303 * @param array $defs2 The Meta-Data file definitions
304 * @param string $view
305 * @param strign $module
308 public function createQuickSearchCode($defs, $defs2, $view = '', $module='')
310 $sqs_objects = array();
311 require_once('include/QuickSearchDefaults.php');
312 if(isset($this) && $this instanceof TemplateHandler) //If someone calls createQuickSearchCode as a static method (@see ImportViewStep3) $this becomes anoter object, not TemplateHandler
314 $qsd = QuickSearchDefaults::getQuickSearchDefaults($this->getQSDLookup());
317 $qsd = QuickSearchDefaults::getQuickSearchDefaults(array());
319 $qsd->setFormName($view);
320 if(preg_match('/^SearchForm_.+/', $view)){
321 if(strpos($view, 'popup_query_form')){
322 $qsd->setFormName('popup_query_form');
323 $parsedView = 'advanced';
325 $qsd->setFormName('search_form');
326 $parsedView = preg_replace("/^SearchForm_/", "", $view);
328 //Loop through the Meta-Data fields to see which ones need quick search support
329 foreach($defs as $f) {
331 $name = $qsd->form_name . '_' . $field['name'];
333 if($field['type'] == 'relate' && isset($field['module']) && preg_match('/_name$|_c$/si',$name)) {
334 if(preg_match('/^(Campaigns|Teams|Users|Contacts|Accounts)$/si', $field['module'], $matches)) {
336 if($matches[0] == 'Campaigns') {
337 $sqs_objects[$name.'_'.$parsedView] = $qsd->loadQSObject('Campaigns', 'Campaign', $field['name'], $field['id_name'], $field['id_name']);
338 } else if($matches[0] == 'Users'){
340 if(!empty($f['name']) && !empty($f['id_name'])) {
341 $sqs_objects[$name.'_'.$parsedView] = $qsd->getQSUser($f['name'],$f['id_name']);
344 $sqs_objects[$name.'_'.$parsedView] = $qsd->getQSUser();
346 } else if($matches[0] == 'Campaigns') {
347 $sqs_objects[$name.'_'.$parsedView] = $qsd->loadQSObject('Campaigns', 'Campaign', $field['name'], $field['id_name'], $field['id_name']);
348 } else if($matches[0] == 'Accounts') {
350 $idKey = isset($field['id_name']) ? $field['id_name'] : 'account_id';
352 //There are billingKey, shippingKey and additionalFields entries you can define in editviewdefs.php
353 //entry to allow quick search to autocomplete fields with a suffix value of the
354 //billing/shippingKey value (i.e. 'billingKey' => 'primary' in Contacts will populate
355 //primary_XXX fields with the Account's billing address values).
356 //addtionalFields are key/value pair of fields to fill from Accounts(key) to Contacts(value)
357 $billingKey = isset($f['displayParams']['billingKey']) ? $f['displayParams']['billingKey'] : null;
358 $shippingKey = isset($f['displayParams']['shippingKey']) ? $f['displayParams']['shippingKey'] : null;
359 $additionalFields = isset($f['displayParams']['additionalFields']) ? $f['displayParams']['additionalFields'] : null;
360 $sqs_objects[$name.'_'.$parsedView] = $qsd->getQSAccount($nameKey, $idKey, $billingKey, $shippingKey, $additionalFields);
361 } else if($matches[0] == 'Contacts'){
362 $sqs_objects[$name.'_'.$parsedView] = $qsd->getQSContact($field['name'], $field['id_name']);
365 $sqs_objects[$name.'_'.$parsedView] = $qsd->getQSParent($field['module']);
366 if(!isset($field['field_list']) && !isset($field['populate_list'])) {
367 $sqs_objects[$name.'_'.$parsedView]['populate_list'] = array($field['name'], $field['id_name']);
368 $sqs_objects[$name.'_'.$parsedView]['field_list'] = array('name', 'id');
370 $sqs_objects[$name.'_'.$parsedView]['populate_list'] = $field['field_list'];
371 $sqs_objects[$name.'_'.$parsedView]['field_list'] = $field['populate_list'];
374 } else if($field['type'] == 'parent') {
375 $sqs_objects[$name.'_'.$parsedView] = $qsd->getQSParent();
379 foreach ( $sqs_objects as $name => $field )
380 foreach ( $field['populate_list'] as $key => $fieldname )
381 $sqs_objects[$name]['populate_list'][$key] = $sqs_objects[$name]['populate_list'][$key] . '_'.$parsedView;
383 //Loop through the Meta-Data fields to see which ones need quick search support
384 foreach($defs2 as $f) {
385 if(!isset($defs[$f['name']])) continue;
387 $field = $defs[$f['name']];
388 if ($view == "ConvertLead")
390 $field['name'] = $module . $field['name'];
391 if (!empty($field['id_name']))
392 $field['id_name'] = $field['name'] . "_" . $field['id_name'];
394 $name = $qsd->form_name . '_' . $field['name'];
397 if($field['type'] == 'relate' && isset($field['module']) && (preg_match('/_name$|_c$/si',$name) || !empty($field['quicksearch']))) {
398 if (!preg_match('/_c$/si',$name)
399 && (!isset($field['id_name']) || !preg_match('/_c$/si',$field['id_name']))
400 && preg_match('/^(Campaigns|Teams|Users|Contacts|Accounts)$/si', $field['module'], $matches)
403 if($matches[0] == 'Campaigns') {
404 $sqs_objects[$name] = $qsd->loadQSObject('Campaigns', 'Campaign', $field['name'], $field['id_name'], $field['id_name']);
405 } else if($matches[0] == 'Users'){
406 if($field['name'] == 'reports_to_name')
407 $sqs_objects[$name] = $qsd->getQSUser('reports_to_name','reports_to_id');
409 if($view == "ConvertLead" || $field['name'] == 'created_by_name' || $field['name'] == 'modified_by_name')
410 $sqs_objects[$name] = $qsd->getQSUser($field['name'], $field['id_name']);
412 $sqs_objects[$name] = $qsd->getQSUser();
414 } else if($matches[0] == 'Campaigns') {
415 $sqs_objects[$name] = $qsd->loadQSObject('Campaigns', 'Campaign', $field['name'], $field['id_name'], $field['id_name']);
416 } else if($matches[0] == 'Accounts') {
418 $idKey = isset($field['id_name']) ? $field['id_name'] : 'account_id';
420 //There are billingKey, shippingKey and additionalFields entries you can define in editviewdefs.php
421 //entry to allow quick search to autocomplete fields with a suffix value of the
422 //billing/shippingKey value (i.e. 'billingKey' => 'primary' in Contacts will populate
423 //primary_XXX fields with the Account's billing address values).
424 //addtionalFields are key/value pair of fields to fill from Accounts(key) to Contacts(value)
425 $billingKey = SugarArray::staticGet($f, 'displayParams.billingKey');
426 $shippingKey = SugarArray::staticGet($f, 'displayParams.shippingKey');
427 $additionalFields = SugarArray::staticGet($f, 'displayParams.additionalFields');
428 $sqs_objects[$name] = $qsd->getQSAccount($nameKey, $idKey, $billingKey, $shippingKey, $additionalFields);
429 } else if($matches[0] == 'Contacts'){
430 $sqs_objects[$name] = $qsd->getQSContact($field['name'], $field['id_name']);
431 if(preg_match('/_c$/si',$name) || !empty($field['quicksearch'])){
432 $sqs_objects[$name]['field_list'] = array('salutation', 'first_name', 'last_name', 'id');
436 $sqs_objects[$name] = $qsd->getQSParent($field['module']);
437 if(!isset($field['field_list']) && !isset($field['populate_list'])) {
438 $sqs_objects[$name]['populate_list'] = array($field['name'], $field['id_name']);
439 // now handle quicksearches where the column to match is not 'name' but rather specified in 'rname'
440 if (!isset($field['rname']))
441 $sqs_objects[$name]['field_list'] = array('name', 'id');
444 $sqs_objects[$name]['field_list'] = array($field['rname'], 'id');
445 $sqs_objects[$name]['order'] = $field['rname'];
446 $sqs_objects[$name]['conditions'] = array(array('name'=>$field['rname'],'op'=>'like_custom','end'=>'%','value'=>''));
449 $sqs_objects[$name]['populate_list'] = $field['field_list'];
450 $sqs_objects[$name]['field_list'] = $field['populate_list'];
453 } else if($field['type'] == 'parent') {
454 $sqs_objects[$name] = $qsd->getQSParent();
459 //Implement QuickSearch for the field
460 if(!empty($sqs_objects) && count($sqs_objects) > 0) {
461 $quicksearch_js = '<script language="javascript">';
462 $quicksearch_js.= 'if(typeof sqs_objects == \'undefined\'){var sqs_objects = new Array;}';
463 $json = getJSONobj();
464 foreach($sqs_objects as $sqsfield=>$sqsfieldArray){
465 $quicksearch_js .= "sqs_objects['$sqsfield']={$json->encode($sqsfieldArray)};";
467 return $quicksearch_js . '</script>';
474 * Get lookup array for QuickSearchDefaults custom class
476 * @see QuickSearchDefaults::getQuickSearchDefaults()
478 protected function getQSDLookup()