1 Ctools导入导出API

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Ctools自带了一个帮助文档,里面有导入导出的介绍,在ctools\help下面有export.htmlexport-ui.html两个介绍,这对我的开发非常有帮助。我至少读了两遍。除此以外,我还找到了几个实现了Ctools export的模块,作为例子,阅读了它们的相关代码。



Drupal版本:

1.1 Schema特有的定义

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看一下,Field validation的实现,首先是schema的变动:

function field_validation_schema() {

  $schema['field_validation_rule'] = array(

    'description' => 'Stores rule definitions',

    'export' => array(

      'key' => 'name',

      'key name' => 'Name',

      'primary key' => 'ruleid',

      'identifier' => 'rule', 

      'default hook' => 'default_field_validation_rule',

      'api' => array(

        'owner' => 'field_validation',

        'api' => 'default_field_validation_rules',

        'minimum_version' => 1,

        'current_version' => 1,

      ),

    ),

    'fields' => array(

      'ruleid' => array(

        'type' => 'serial',

        'description' => 'Unique identifier of the validation rule',

        'unsigned' => TRUE,

        'not null' => TRUE,

      ),

      'rulename' => array(

        'type' => 'varchar',

        'description' => 'Name of the validation rule',

        'not null' => TRUE,

        'default' => '',

        'length' => 255,

      ),

      'name' => array(

        'type' => 'varchar',

        'description' => 'Machine name of the validation rule',

        'not null' => TRUE,

        'default' => '',

        'length' => 32,

      ),

…..

    ),

  );

 

  return $schema;

}

    这里面有两处变化,一个就是为验证规则添加了一个机读名字,使用这个机读名字作为导入导出同步的唯一ID。这个大家都很好理解,对应代码如下:

      'name' => array(

        'type' => 'varchar',

        'description' => 'Machine name of the validation rule',

        'not null' => TRUE,

        'default' => '',

        'length' => 32,

      ),

另一处,就是添加了一个export键,它的值为一个数组:

'export' => array(

      'key' => 'name',

      'key name' => 'Name',

      'primary key' => 'ruleid',

      'identifier' => 'rule', 

      'default hook' => 'default_field_validation_rule',

      'api' => array(

        'owner' => 'field_validation',

        'api' => 'default_field_validation_rules',

        'minimum_version' => 1,

        'current_version' => 1,

      ),

    ),

   这个数组,所包含的键的含义,我们这里简单介绍一下用到的,其余没有用到的,可以参看Ctools的帮助文档,来看一下这里用到的:

 

key: 就是哪个字段是唯一键,在数据库中,导出的文件中,都是唯一的

key name:唯一键的用户可读名字

primary key: 就是主键,是一个数字ID,在数据库存储时用到。

identifier :标识符,导出的验证规则对象,它所在的变量

default hook :就是默认钩子,通过这个钩子,我们可以定义一个验证规则。

api:它下面包含4个键,owner,就是当前模块的名字;api,钩子,用来定义验证规则;minimum_version就是api的最小版本;current_versionapi的当前版本。


Drupal版本:

1.2 实现hook_ctools_plugin_directory

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

module文件中,我们添加了以下代码:

/**

 * Implementation of hook_ctools_plugin_directory().

 */

function field_validation_ctools_plugin_directory($module, $plugin) {

  if ($module == 'ctools' && $plugin == 'export_ui') {

    return 'plugins/' . $plugin;

  }

}

 

/**

 * Implementation of hook_ctools_plugin_api().

 *

 * Tell Ctools that we support the default_field_validation_rules API.

 */

function field_validation_ctools_plugin_api($owner, $api) {

  if ($owner == 'field_validation' && $api == 'default_field_validation_rules') {

    return array('version' => 1);

  }

}

hook_ctools_plugin_directory用来定义插件的位置,hook_ctools_plugin_api用来告诉Ctools,我们支持通过default_field_validation_rules定义验证规则。

这里我们需要区分一点,default_field_validation_rulesdefault_field_validation_rule的区别,前面写作的时候把两者都作为了钩子,这是不对的,正确的理解应该是这样的,default_field_validation_rule是一个钩子,可以用来定义规则,default_field_validation_rules,则对应于field_validation. default_field_validation_rules.inc文件,在这个文件中,我们可以实现钩子default_field_validation_rule。有点绕口,为了更好的理解两者之间的关系,建议大家打开RC1 版中的field_validation.default_field_validation_rules.inc文件,里面的代码如下:

/**

 * Implementation of hook_default_field_validation_rule().

 * 

 * Provide default validation rules.

 */

function field_validation_default_field_validation_rule() {

  $export = array();

 

  $rule = new stdClass;

  $rule->api_version = 1;

  $rule->name = 'body_min_words';

  $rule->rulename = 'Body Min words';

  $rule->field_name = 'body';

  $rule->col = 'value';

  $rule->entity_type = 'node';

  $rule->bundle = 'page';

  $rule->validator = 'min_words';

  $rule->data = '2';

  $rule->error_message = t('You should enter at least two words.');

  $export['body_min_words'] = $rule;


  return $export;

}

这个函数里面的$rule变量,就对应于我们前面的identifier。现在,我们的验证规则,不仅仅存放在了数据库中,还可以通过代码来定义。


Drupal版本:

1.3 使用ctools_export_load_object加载对象

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们获取验证规则的代码也需要相应的调整。我们来看一个根据机读名字加载验证规则的API函数:

function field_validation_rule_load($name) {

  // Use Ctools export API to fetch this rule.

  ctools_include('export');

  $result = ctools_export_load_object('field_validation_rule', 'names', array($name));

  if (isset($result[$name])) {

    return $result[$name];

  }

}

这里面用到的,这两句代码,是Ctools加载可导出对象的标准用法。

  ctools_include('export');

  $result = ctools_export_load_object('field_validation_rule', 'names', array($name));

field_validation_field_attach_validate里面,我们用来加载验证规则的代码是这样的:

  ctools_include('export');

  $rules = ctools_export_load_object('field_validation_rule', 'conditions', array('entity_type'=>$entity_type, 'bundle' => $bundle));

通过这两段代码,我们可以看到ctools_export_load_object的第一个参数,是加载的对象,这里也就是field_validation_rule,我们在schema里面创建数据库表的时候,用的就是这个名字。第二个参数可以是'names',此时第三个参数为一个数组,里面包含了一组机读名字;第二个参数还可以是'conditions',此时第三个参数也是一个数组,里面包含了多个限制条件。有兴趣的可以读读ctools_export_load_object的源代码,看看Ctools的作者怎么把这些条件转化为对应的SQL的。

我们使用ctools_export_load_object加载的都是对象的形式,以前我们的验证规则,一直都采用数组的形式,所以在field_validation_field_attach_validate里面,我添加了一句从对象到数组转换的代码:

$rule = (array)$rule_obj;

之所以这样做,是因为前面的代码都是数组,如果改为对象的话,要改很多个小地方。为了兼容起见,将加载的验证规则对象,转换为了数组的形式。

field_validation.admin.inc里面,也有用到加载验证规则的地方,我们也做了相应的修改。由于我们使用了ctools_export_load_object,所以field_validation.rules.inc里面的代码,现在就有些多余了,我们在后面的版本中删除了这个文件。


Drupal版本:

1.4 实现导出UI插件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

再往下,就是在field_validation\plugins\export_ui里面创建field_validation_export_ui.inc文件,这里的代码有点长:

/**

 * Define this Export UI plugin.

 */

$plugin = array(

  'schema' => 'field_validation_rule',

  'access' => 'administer site configuration',

  'menu' => array(

    'menu item' => 'field_validation',

    'menu prefix' => 'admin/structure',

    'menu title' => 'Field Validation',

    'menu description' => 'Administer Field Validation rules.',

  ),

 

  'title singular' => t('rule'),

  'title plural' => t('rules'),

  'title singular proper' => t('Field Validation rule'),

  'title plural proper' => t('Field Validation rules'),

 

  'form' => array(

    'settings' => 'field_validation_ctools_export_ui_form',

    'validate' => 'field_validation_ctools_export_ui_form_validate',

    'submit' => 'field_validation_ctools_export_ui_form_submit',

  ),

);

 

/**

 * Define the add/edit form of validation rule.

 */

function field_validation_ctools_export_ui_form(&$form, &$form_state) {

  ctools_include('export');

  $rule = $form_state['item'];

  $default_rulename = isset($rule->rulename) ? $rule->rulename : '';

  $default_entity_type = isset($rule->entity_type) ? $rule->entity_type : '';

  $default_bundle = isset($rule->bundle) ? $rule->bundle : '';

  $default_field_name = isset($rule->field_name) ? $rule->field_name : '';

  $default_col = isset($rule->col) ? $rule->col : '';

  $default_validator = isset($rule->validator) ? $rule->validator : '';

  $default_data = isset($rule->data) ? $rule->data : '';

  $default_error_message = isset($rule->error_message) ? $rule->error_message : '';

  //print debug($form_state);

  

  $default_rulename = isset($form_state['values']['rulename']) ? $form_state['values']['rulename'] : $default_rulename;

  $default_entity_type = isset($form_state['values']['entity_type']) ? $form_state['values']['entity_type'] : $default_entity_type;

  $default_bundle = isset($form_state['values']['bundle']) ? $form_state['values']['bundle'] : $default_bundle;

  $default_field_name = isset($form_state['values']['field_name']) ? $form_state['values']['field_name'] : $default_field_name;

  $default_col = isset($form_state['values']['col']) ? $form_state['values']['col'] : $default_col;

  $default_validator = isset($form_state['values']['validator']) ? $form_state['values']['validator'] : $default_validator;

  $default_data = isset($form_state['values']['data']) ? $form_state['values']['data'] : $default_data;

  $default_error_message = isset($form_state['values']['error_message']) ? $form_state['values']['error_message'] : $default_error_message;

  //print debug($rule);

  

  $form['rulename'] = array(

    '#type' => 'textfield',

    '#title' => t('Rule name'),

    '#default_value' => $default_rulename,

    '#required' => TRUE,

    '#size' => 60,

    '#maxlength' => 255,

   // '#weight' => 1,

  );

 

  $entity_type_options = array(

    '' => 'Choose an entity type',

  );

  $entity_types = entity_get_info();

  foreach ($entity_types as $key => $entity_type) {

    $entity_type_options[$key] = $entity_type['label'];  

  }

  $form['entity_type'] = array(

    '#type' => 'select', 

'#options' => $entity_type_options,

'#title' => t('Entity type'),

    '#default_value' => $default_entity_type,

    '#required' => TRUE,

    '#ajax' => array(      

      'callback' => 'field_validation_entity_type_callback',      

      'wrapper' => 'validation-rule-wrapper-div',      

      'method' => 'replace',      

      'effect' => 'fade',    

    ),

  );

 

 

  $bundle_options = array(

    '' => 'Choose a bundle',

  );

 // print debug($entity_types['node']);

  $bundles = !empty($entity_types[$default_entity_type]['bundles']) ? $entity_types[$default_entity_type]['bundles'] : array();

 // $bundles = !empty($entity_types['node']['bundles']) ? $entity_types['node']['bundles'] : array();

 // print debug($default_entity_type);

  foreach ($bundles as $key => $bundle) {

    $bundle_options[$key] = $bundle['label'];  

  }

  $form['bundle'] = array(

    '#type' => 'select',

'#options' => $bundle_options,

'#title' => t('Bundle name'),

    '#default_value' => $default_bundle,

    '#required' => TRUE,

    '#prefix' => '<div id="bundle-wrapper-div">',

    '#suffix' => '</div>',

    '#ajax' => array(      

      'callback' => 'field_validation_bundle_callback',      

      'wrapper' => 'validation-rule-wrapper-div',      

      'method' => 'replace',      

      'effect' => 'fade',    

    ),

  );

  

  $field_name_options = array(

    '' => 'Choose a field',

  );

  $instances = array();

  if(!empty($default_entity_type) && !empty($default_bundle)){

    $instances = field_info_instances($default_entity_type, $default_bundle);

  }

  //$instances = field_info_instances('node', 'article');

 

  foreach ($instances as $key => $instance) {

    $field_name_options[$key] = $instance['label'];  

  }

  if(!in_array($default_field_name, array_keys($field_name_options))){

    $default_field_name = '';

  }

  $form['field_name'] = array(

    '#type' => 'select',

'#options' => $field_name_options,

    '#title' => t('Field name'),

    '#default_value' => $default_field_name,

    '#required' => TRUE,

    '#prefix' => '<div id="field-name-wrapper-div">',

    '#suffix' => '</div>',

    '#ajax' => array(      

      'callback' => 'field_validation_field_name_callback',      

      'wrapper' => 'col-wrapper-div',      

      'method' => 'replace',      

      'effect' => 'fade',    

    ),

  ); 

  $field = field_info_field($default_field_name);

  //print debug($field);

  $col_options = array(

    '' => t('Choose a column'),

  );

  $columns = !empty($field['columns']) ? $field['columns'] : array();

  foreach ($columns as $key => $column) {

    $col_options[$key] = $key;  

  }

  if(!in_array($default_col, array_keys($col_options))){

    $default_col = '';

  }  

  $form['col'] = array(

    '#type' => 'select',

'#options' => $col_options,

    '#title' => t('Column'),

    '#description' => t('A column defined in the hook_field_schema() of this field.'),

    '#default_value' => $default_col,

    '#required' => TRUE,

    '#weight' => 2,

    '#prefix' => '<div id="col-wrapper-div">',

    '#suffix' => '</div>',

  );

 

  $validator_options = array(

    '' => 'Choose a validator',

  );

  $validators = field_validation_get_validators();

  foreach ($validators as $validator_key => $validator_info) {

    $validator_options[$validator_key] = $validator_info['name'];

  }

  $form['validator'] = array(

    '#type' => 'select',

'#options' => $validator_options,

    '#title' => t('Validator'),

    '#description' => t('A column defined in the hook_field_schema() of this field.'),

    '#default_value' => $default_validator,

    '#required' => TRUE,

    '#weight' => 3,

    '#ajax' => array(      

      'callback' => 'field_validation_validator_callback',      

      'wrapper' => 'data-wrapper-div',      

      'method' => 'replace',      

      'effect' => 'fade',    

    ),

  );

  

  $form['data'] = array(

    '#type' => 'textfield',

    '#title' => t('Config Data'),

    '#required' => FALSE,

    '#size' => 60,

    '#maxlength' => 255,

    '#default_value' => $default_data,

    '#weight' => 4,

    '#prefix' => '<div id="data-wrapper-div">',

    '#suffix' => '</div>',

  );

  //$rule_validator = $validators[$default_validator];

  if (isset($validators[$default_validator]['custom_data']) && is_array($validators[$default_validator]['custom_data'])) {

    $required = isset($validators[$default_validator]['custom_data']['required']) ? $validators[$default_validator]['custom_data']['required'] : TRUE;

$form['data']['#title'] = isset($validators[$default_validator]['custom_data']['label']) ? $validators[$default_validator]['custom_data']['label'] : t('Config Data');

$form['data']['#description'] = isset($validators[$default_validator]['custom_data']['description']) ? $validators[$default_validator]['custom_data']['description'] : t('Config Data');

$form['data']['#required'] = ($required !== FALSE) ? TRUE : FALSE;

  }

  

  $form['error_message'] = array(

    '#type' => 'textfield',

    '#title' => t('Custom error message'),

    '#description' => t("Specify an error message that should be displayed when user input doesn't pass validation"),

    '#required' => TRUE,

    '#size' => 60,

    '#maxlength' => 255,

    '#default_value' => $default_error_message,

    '#weight' => 5,

  );

   $form['#prefix'] = '<div id="validation-rule-wrapper-div">';

   $form['#suffix'] = '</div>';

 

}

 

/**

 * Validation handler for the validation rule add/edit form.

 */

function field_validation_ctools_export_ui_form_validate($form, &$form_state) {

$values = $form_state['values'];

}

 

/**

 * Submit handler for the preset edit form.

 */

function field_validation_ctools_export_ui_form_submit($form, &$form_state) {

$values = $form_state['values'];

}

 

function field_validation_entity_type_callback($form,&$form_state){

  return $form;

}

 

function field_validation_bundle_callback($form,&$form_state){

  return $form;

}

 

function field_validation_field_name_callback($form,&$form_state){

  return $form['col'];

}

 

function field_validation_validator_callback($form,&$form_state){

  return $form['data'];

}

最上面的plugin,这是Ctools插件的常用定义方法,代码是从别的地方复制过来的,我只是改了一下名字而已,把对应的改为Field validation

field_validation_ctools_export_ui_form,就是一个普通的表单,我在里面使用了AJAX技术。这个表单是用来在admin/structure/field_validation这个管理界面,添加/编辑验证规则使用的。这样在RC1版里面,我们提供了两种方式,来添加验证规则,一种是原来编写的admin.inc里面的,一种就是这种基于Ctools的方式。在这个表单里面使用Drupal自带的AJAXCtools早期的版本是不支持的,我写这个功能的时候,恰好支持了这个特性。

这里面,使用了函数entity_get_info来获取所有的实体信息,返回的实体信息数组里面,也包含每个实体里面的bundle(包)信息。而获取某类实体下面的bundles信息,则采用下面的代码:

$bundles = !empty($entity_types[$default_entity_type]['bundles']) ? $entity_types[$default_entity_type]['bundles'] : array();

还有一段代码,也值得学习一下,这段代码的作用是,我们获取一个字段上面包含多少个columns,让用户选择column,而不是输入,这在RC1 里面是一个改进,我以前是不知道这种方式的:

  $field = field_info_field($default_field_name);

  $col_options = array(

    '' => t('Choose a column'),

  );

  $columns = !empty($field['columns']) ? $field['columns'] : array();

  foreach ($columns as $key => $column) {

    $col_options[$key] = $key;  

  }

有关这里的AJAX效果实现,这里就不介绍了,参看第一集的省市县三级联动。验证规则的导出功能实现以后,让我欣喜不已,不过很快发现一个小问题,就是导出验证规则的时候,将ruleid也导出来了。在Ctools的作者的帮助下,很快就修正了这个问题:

     'ruleid' => array(

       'type' => 'serial',

       'description' => 'Unique identifier of the validation rule',

       'unsigned' => TRUE,

       'not null' => TRUE,

       'no export' => TRUE,

     ),

这里面,加了一个键'no export',并将它设置为TRUE,这样就可以避免导出ruleid了。


Drupal版本: