sugarCrm翻译 及 文件内容排序后保存,避免冲突过多
Logic Hook
hook配置信息和触发器定义在以下目录中
./custom/Extension/modules/<module>/Ext/LogicHooks/<file>.php
./custom/modules/<module>/logic_hooks.php(避免使用)
./custom/Extension/application/Ext/LogicHooks/<file>.php
./custom/modules/logic_hooks.php(避免使用)
具体的hook代码在配置文件中制定, 但是建议放在与之相关的module目录中去
配置文件还定义了执行hook的类名和方法名
可以通过代码调用去移除hook: remove_logic_hook("<module>", "before_save", $my_hook);
为防止发生无限循环执行hook的情况, 可以在类里加一个属性, 来记录是否已经执行过hook, 从而避免不必要的hook执行
hook分类:
db保存前, 保存后
数据删除前, 后 ..
登录, 退出: 成功/失败
job执行失败
job执行失败后重试
数据展示到Listview或Editview前进行处理
Application Hooks
after_ui_frame
after_ui_footer
server_round_trip
after_delete
after_relationship_add
after_relationship_delete
after_restore
after_retrieve
after_save
before_delete
before_restore
before_save
process_record
Job Queue Hooks
job_failure
job_failure_retry
User Hooks
after_load_user
after_login
after_logout
before_logout
login_failed
Sugar Logic(逻辑运算/数学运算??)
方便自定义的业务逻辑代码的创建, 管理以及在服务器端和客户端重用
他由许多互相独立和可扩展的组件构成, 其中的一个重要组件就是 Sugar Formula 引擎, 它用来解析和求值那些人类读懂的数学公式
Formula 是一个代码表达式, 包含了嵌套的函数和变量, 符合 Sugar Formula 引擎语法, 用来实现比如 加法, 乘法, 逻辑运算: add(1, 2); not(equal($billing_state, "CA")); multiply(number($employees), $seat_cost, 0.0833); ...
Function 可以被调用的函数, 他的输出结果无论在服务端还是前段都是一样的
Trigger 一个只返回true或false的Formula, 他在这种情况下被执行: 当一个公式中的字段被更新或者一条记录被获取/保存, 此时他会通知关联的action去执行一些动作
Action 以某种方法去更改当前记录或者布局的函数, 大多数有两个参数, 一个目标, 一个formula; 比如说 style action(样式动作), 需要传递一个字段, 和一个字符串样式, 就可以用传入的样式来更改字段的样式; 列表
Dependencies 是由触发器和action组成的用来表达业务逻辑的单元, 比如: 当一个下拉选项中的值没有被选中的时候, 对应的面板要隐藏起来
Sugar Logic Types (类型)
number 数值类型, string 字符串类型, enum 枚举类型(列表类型) link 连接类型(描述两个表之间的关联关系)
Sugar Logic 基本特性(Sugar Logic Based Features)
计算字段的值, 如显示的时候, 将值乘以0.1而不是原值显示
字段关联, 比如当一个字段值在某一个范围的时候才在页面中显示出来
下拉列表关联, 如多级联动效果
自定义
自定义Function,
他将会存放在/custom/include/Expressions/Expression/{Type}/{Function_Name}.php
文件名必须是: {functionName}Expression.php, 类名跟文件名要一样, 例如继承了数字类型的function: class AbcExpression extends NumericExpression{}
必须要同时定义php和JavaScript两个相同的计算逻辑, 当在admin面板中操作"Rebuild Sugar Logic Functions"重新构建时, 相关的js代码(代码文件??)就会被编译出来
自定义Action
自定义的action就不用像function那样保持前后端一致了, 比如, 填写表单时用户输入错误了, 前段会有一个alert提醒, 但是后端会记录一条日志
他会存放在: custom/include/Expressions/Actions/{ActionName}.php ,
文件名必须以Action.php结尾, 类名跟文件名要一样, 同时得继承AbstractAction类: class WarningAction extends AbstractAction{}
必须定义一些基本的方法: "fire", "getDefinition", "getActionName", "getJavascriptClass", "getJavscriptFire"
Extensions 扩展
扩展的目的是为了提供一个修改Sugar metadata(vardefs and layouts)的渠道,
他的文件存放在: ./custom/Extension/application/Ext/ 和 ./custom/Extension/modules/<module_name>/Ext 目录下
这些文件会通过一个事先定义好的规则(例如: vardef.ext.php)被合并成一个单独的文件,
比如vardefs文件, ./custom/Extension/modules/Accounts/Ext/Vardefs/ 目录下的所有文件都会被合并, 里边的变量合并成一个数组放到./custom/modules/Accounts/Ext/Vardefs/vardefs.ext.php中, 其中:
如果你通过studio添加了一个test字段, 就会在./custom/Extension/modules/Accounts/Ext/Vardefs/ 目录下生成一个 sugarfield_test_c.php文件, 里边很简单就一行: $dictionary['Task']['fields']['test_c']['labelValue']='test';
./ModuleInstall/extensions.php 包含了所有的扩展映射关系
扩展的属性:
1. name: 扩展的内部名字, This is used in method names such as rebuild_layouts (???)
2. install definition: manifest文件的段的名字
3. ext directory : 扩展文件存放的目录
4. ext file: 合并后存放的文件名
5. useage: 扩展在哪里被用到了
扩展种类:
ActionFileMap | 如果你不想把视图文件放在./custom/modules/<module>/views/view.<name>.php, 这个文件定义了action到文件的映射关系 | |
ActionReMap | 将一个action映射到另一个已经存在的action | |
ActionViewMap | 给module添加一个action映射, 通常 | |
Administration | 在admin页面添加一个管理面板 (Used to add new administrative panels to the admin section) | |
Dependencies | 给字段或者表单添加一个依赖(关联)的行为去处理一些复杂的逻辑(studio界面暂时不能用) | |
EntryPointRegistry | 添加额外的入口映射 | |
Extensions | 允许用户在框架内自定义扩展, 他将跟./ModuleInstaller/extensions.php中的扩展一起使用 | |
FileAccessControlMap | 用于限制系统用户的特定视图操作 (Used to restrict specific view actions from users of the system. 限定系统用户可以访问哪些页面??) | |
GlobalLinks | 定义全局可用的超链接 | |
Include | 用来映射系统中额外的modules, 通常是构建工具利用他来生成一个module | |
JSGroupings | 添加js组(Used to add additional JavaScript groupings the system ??) | |
Language | 他可以添加或者覆盖已有的语言描述, 相关的application 和 module 目录都能用到 | |
Layoutdefs | 用来添加或者覆盖子面板的定义 | |
LogicHooks | 给某一个action添加一个特定的功能, 比如在保存数据之前 | |
Menus | 管理module的菜单, (会覆盖标准的菜单, 所以要全部重新定义) | |
ScheduledTasks | 自定义计划任务 | |
UserPage | 在用户管理详情页添加一个章节section | |
Utils | 给工全局的具包(util)添加功能函数 | |
vardefs | 添加或者覆盖vardefs | |
WirelessLayoutdefs | 给移动端视图(mobile)添加一个子面板 | |
WirelessModuleRegistry | 给移动端添加额外的modules |
注意:
通过 SugarCRM 工作室添加了模块或字段后, sugar 会重新生成并覆盖一大堆的配置文件, 而且是新生成的文件内容是无序的,
这就出现一个问题, 当小组多个成员一起开发时, 这种无序会导致冲突, 很多很大的冲突,
这就需要改动sugar的框架文件, 让重新生成配置文件时, 以字母有序的方式排列内容, (注意有些文件比如布局文件, 就不能进行排序了)
include/utils/array_utils.php
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 function var_export_helper($tempArray, $isSort=TRUE) { 2 if ($isSort && is_array($tempArray)) { 3 ksort($tempArray, SORT_NATURAL | SORT_FLAG_CASE); //按照字符顺序a-z排序, 不区分大小写 4 } 5 6 return var_export($tempArray, true); 7 }
include/utils/file_utils.php
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 function write_array_to_file( $the_name, $the_array, $the_file, $mode="w", $header='' ) 2 { 3 if(!empty($header) && ($mode != 'a' || !file_exists($the_file))){ 4 $the_string = $header; 5 }else{ 6 $the_string = "<?php\n" . 7 '// created: ' . date('Y-m-d H:i:s') . "\n"; 8 } 9 $tmp = array(); 10 if (strpos($the_file, 'subpanel') !== FALSE) { 11 $tmp = var_export_helper( $the_array, FALSE); 12 } else { 13 $tmp = var_export_helper( $the_array ); 14 } 15 $the_string .= "\$$the_name = " . $tmp . ";"; 16 return sugar_file_put_contents($the_file, $the_string, LOCK_EX) !== false; 17 }
modules/Administration/Common.php
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
function save_custom_app_list_strings_contents(&$contents, $language, $custom_dir_name = ''){ ...... if($dir_exists) { $filename = "$dirname/$language.lang.php"; $handle = @sugar_fopen($filename, 'w'); if($handle) { $contents = sortAppListStringContent($contents); //添加此行函数调用 if(fwrite($handle, $contents)) { $return_value = true; $GLOBALS['log']->info("Successful write to: $filename"); } fclose($handle); } else { $GLOBALS['log']->info("Unable to write edited language pak to file: $filename"); ....... } /** * 将下拉列表的内容排序后返回 * @param $contents * @return string */ function sortAppListStringContent($contents) { $rn = PHP_EOL; //匹配 $app_strings 导航栏 preg_match_all('#\$app_strings\[\'(.*)\'\]\s*=.*;#U', $contents, $matches); $list0 = array(); if (!empty($matches[0])) { foreach($matches[0] as $k => $content) { $key = $matches[1][$k]; $list0[$key] = $content; } ksort($list0, SORT_NATURAL | SORT_FLAG_CASE); //排序, 方便查看更改内容 } $strList0 = implode($rn, array_values($list0)); //匹配 $app_list_strings preg_match_all('#\$app_list_strings\[\'(.*)\'\]\[\'(.*)\'\]\s*=.*;#U', $contents, $matches); $list1 = array(); if (!empty($matches[0])) { foreach($matches[0] as $k => $content) { $key = $matches[1][$k].'_'.$matches[2][$k]; $list1[$key] = $content; } ksort($list1, SORT_NATURAL | SORT_FLAG_CASE); //排序, 方便查看更改内容 } $strList1 = implode($rn, array_values($list1)); //匹配 $GLOBALS['app_list_strings']['xxx'] preg_match_all('#\$GLOBALS\[\'app_list_strings\']\[\'(.*)\'\].*;#Us', $contents, $matches); $list2 = array(); if (!empty($matches[0])) { foreach($matches[0] as $k => $content) { $key = $matches[1][$k]; $content = preg_replace('#\s+#s', ' ', $content); $content = preg_replace('#array\s*\(\s*#', 'array ('.$rn.' ', $content); $content = preg_replace('#,\s*#', ','.$rn.' ', $content); $content = preg_replace('#\s*\);#', $rn.');', $content); $list2[$key] = $content; } ksort($list2, SORT_NATURAL | SORT_FLAG_CASE); //排序, 方便查看更改内容 } $strList2 = implode($rn, array_values($list2)); //$list2[$dropdown_name] = "\$GLOBALS['app_list_strings']['{$dropdown_name}']=".var_export_helper($dropdown).';'; $string = '<?php' . $rn. $strList0. $rn. $strList1. $rn. $strList2. $rn; return $string; }
modules/ModuleBuilder/parsers/parser.dropdown.php
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 function getNewCustomContents($dropdown_name, $dropdown, $lang) { 2 $contents = return_custom_app_list_strings_file_contents($lang); 3 $contents = str_replace("?>", '', $contents); 4 if(empty($contents))$contents = "<?php"; 5 $contents = preg_replace($this->getPatternMatch($dropdown_name), "\n", $contents); 6 $contents .= "\n\$GLOBALS['app_list_strings']['$dropdown_name']=" . var_export_helper($dropdown, FALSE) . ";"; 7 return $contents; 8 }
modules/ModuleBuilder/parsers/views/AbstractMetaDataImplementation.php
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 protected function _saveToFile ($filename , $defs , $useVariables = true, $forPopup = false ) 2 { 3 ..... 4 if($forPopup){ 5 $out .= "\$$viewVariable = \n" . var_export_helper ( $defs, FALSE) ; 6 }else{ 7 $out .= "\$$viewVariable [".(($useVariables) ? '$module_name' : "'$this->_moduleName'")."] = \n" . var_export_helper ( $defs , FALSE) ; 8 } 9 ..... 10 }
modules/ModuleBuilder/parsers/views/PopupMetaDataParser.php
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 function handleSave ($populate = true) 2 { 3 ...... 4 foreach( $allProperties as $p){ 5 if(isset($popupMeta[$p])){ 6 $out .= " '$p' => ". var_export_helper ($popupMeta[$p], FALSE) . ",\n"; 7 } 8 } 9 ...... 10 }