CiviCRM Community Forums (archive)

*

News:

Have a question about CiviCRM?
Get it answered quickly at the new
CiviCRM Stack Exchange Q+A site

This forum was archived on 25 November 2017. Learn more.
How to get involved.
What to do if you think you've found a bug.



  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion (Moderator: Donald Lobo) »
  • Upgrade custom component from 1.7 to 2.1
Pages: [1]

Author Topic: Upgrade custom component from 1.7 to 2.1  (Read 3722 times)

wmostrey

  • I post occasionally
  • **
  • Posts: 36
  • Karma: 3
    • Wim Mostrey
Upgrade custom component from 1.7 to 2.1
January 06, 2009, 02:31:24 am
I'm having issues converting a custom component to 2.1. CiviCRM now doesn't make use anymore of Component.php but instead works with an Info.php and Task.php for each individual component. I've been trying to mimic the existing components but I'm unable to even get the component to show up at the "Settings - Enable Components" page (civicrm/admin/setting/component/?reset=1). I've been looking hard for any kind of documentation on creating a custom component for 2.1 or upgrading components to 2.1 but I haven't found any. Any help on this issue would be highly appreciated.

Deepak Srivastava

  • Ask me questions
  • ****
  • Posts: 677
  • Karma: 65
Re: Upgrade custom component from 1.7 to 2.1
January 06, 2009, 06:14:19 am
Quote
CiviCRM now doesn't make use anymore of Component.php but instead works with an Info.php and Task.php for each individual component.
The changes are in the direction of making components customizable/configurable, easy to add (in future via UI and/or an xml file, i think).
 
Quote
I've been trying to mimic the existing components but I'm unable to even get the component to show up at the "Settings - Enable Components" page (civicrm/admin/setting/component/?reset=1).
You look to be on right track. The thing you might be missing, is an entry in civicrm_component table (similar to other components).
Another major thing i remember is the url mappings which now is maintained in xml files in templates/Menu directory, which earlier used to be in Core/Menu.php file.

Would be great if could make notes of the fixes as you proceed and contribute/share in the form of document, which others can benefit from (since currently there is no such document).   
Found this reply helpful? Contribute NOW and help us improve CiviCRM with the Make it Happen! initiative.

Michał Mach

  • Ask me questions
  • ****
  • Posts: 748
  • Karma: 59
    • CiviCRM site
  • CiviCRM version: latest
  • CMS version: Drupal and Joomla latest
  • MySQL version: numerous
  • PHP version: 5.3 and 5.2
Re: Upgrade custom component from 1.7 to 2.1
January 06, 2009, 06:53:44 am
Hey,

There is no documentation on writing/upgrading custom components yet - we're still along the way in flashing out consistent component writing procedure. I'm not sure what model did you use for component writing in 1.7, but I'll gladly help you here, on the forum in upgrading it to 2.1/2.2, perhaps it will push our componentisation work forward. :-) Also, I will be most glad to hear your experience/remarks/ideas on writing external CiviCRM component, will be most happy to integrate the learning from this experience in upcoming version to make component writing easier.

As you noticed, the main element of external component is Info.php file. It contains PHP class which defines all the information about the component and implements a couple of methods needed by the system. Required elements in CRM_ComponentName_Info class are:

Class properties:
protected $keyword <= defines the unique keyword for the component, used in different places

Class methods:
function getInfo() <= provides information about the component to the system.
Needs to return an array defining values for the following keys:
'name' (string) <= unique name of the component, will be used
'translatedName' (string) <= the name of the component, enclosed in ts for translation purposes
'title' (string) <= short description of the component, enclosed in ts for translation purposes
'search' (1|0) <= whether this component uses search
'showActivitiesInCore' (1|0) <= whether this component uses built in activities functionality

example implementation (for CiviContrbute):
Code: [Select]
    public function getInfo()
    {
        return  array( 'name'                 => 'CiviContribute',
                       'translatedName'       => ts('CiviContribute'),
                       'title'                => ts('CiviCRM Contribution Engine'),
                       'search'               => 1,
                       'showActivitiesInCore' => 1
                       );
    }

function getPermissions()
<= needs to return the array of permissions used by this component

example implementation (for CiviContrbute):
Code: [Select]
    public function getPermissions()
    {
        return array( 'access CiviContribute',
                      'edit contributions',
                      'make online contributions' );
    }

function getUserDashboardElement() <= registers a user dashboard (/civicrm/user) element, can return empty array if user dashboard not used.
Needs to return an array defining values for the following keys:
'name' (string) <= translatable name of the user dashboard element
'title' (string) <= translatable title of the user dashboard element
'perm' <= one of previously defined permissions needed to access dashboard element
'weight' (number) <= weight to define the position of the element on the dashboard

example implementation (for CiviContrbute):
Code: [Select]
    public function getUserDashboardElement()
    {
        return array( 'name'    => ts( 'Contributions' ),
                      'title'   => ts( 'Your Contribution(s)' ),
                      'perm'    => array( 'make online contributions' ),
                      'weight'  => 10 );
    }

function registerTab()
<= registers contact view (/civicrm/contact/view) tab, can return empty array if contact tab not used
Needs to return an array defining values for the following keys:
'title' (string) <= translatable title of the tab
'url' <= the last part of the url, can be the same as keyword (we should actually get rid of this one)
'weight' (number) <= weight to define position of the tab on contact view page

example implementation (for CiviContrbute):
Code: [Select]
    public function registerTab()
    {
        return array( 'title'   => ts( 'Contributions' ),
                      'url'     => 'contribution',
                      'weight'  => 20 );
    }

function registerAdvancedSearchPane() <= registers advanced search pane (/civicrm/contact/search/advanced) for this component, can return empty array if advanced search not used
Needs to return an array defining values for the following keys:
'title' (string) <= translatable title of the pane
'weight' (number) <= weight to define position of the pane on advanced search screen

example implementation (for CiviContrbute):
Code: [Select]
    public function registerAdvancedSearchPane()
    {
        return array( 'title'   => ts( 'Contributions' ),
                      'weight'  => 20 );
    }

The above is not very consistent, as it has been created alond a couple of versions, I see it deserves some unification for 2.3 or later (better sooner). :-)

Once you have you Info.php file, you need to add the entry to civicrm_component table. This tables has only three columns:

id => just let it be the next one in row
name => should be the same as 'name' property returned by getInfo() method
namespace => should state the namespace of your component's classes, file paths will be derived from it.

Example: if you're writing a component called YourComponent and put all the component files in $civicrm_root/CRM/YourComponentPath directory, you need to do the following:

Create a table entry:
insert into civicrm_component (name, namespace) values ('YourComponent', 'CRM_YourComponentPath');

Create an Info class in file $civicrm_root/CRM/YourComponentPath.php, the class name should be CRM_YourComponentPath_Info

After you have db entry and Info class, you need to create an xml file which will define all the URL paths that you're going to use in your component. This file needs to be placed in $civicrm_root/CRM/YourComponentPath/xml/Menu/AnyName.xml
To get the idea of how the file should be structured, take a look at the below example from civicontribute

Code: [Select]
<menu>
  <item>
     <path>civicrm/contribute</path>
     <title>CiviContribute</title>
     <page_callback>CRM_Contribute_Page_DashBoard</page_callback>
     <access_arguments>access CiviContribute</access_arguments>
     <page_type>1</page_type>
     <weight>500</weight>
     <component>CiviContribute</component>
  </item>
 <item>
     <path>civicrm/contribute/chart</path>
     <title>Contribution Chart</title>
     <page_callback>CRM_Contribute_Form_ContributionCharts</page_callback>
     <access_arguments>access CiviContribute</access_arguments>
     <component>CiviContribute</component>
  </item>
  <item>
     [...]
  </item>
</menu>

Properties of each <item> mean:
path <= the path part of each URL, after base url, so for example: if your install is at http://localhost.localnetwork/drupal 
title <= title of the page that will be displayed under this address
page_callback <= php class that should be invoked once the URL is hit
access_arguments <= name of the permission that should be checked once the URL is hit
page_type <= 1 for elements that should be displayed in the menu, 0 for those which shouldn't
weight <= as everywhere, used for ordering
component <= name of the component this menu item belongs to (will be deprecated in one of following versions)

Once you get to this step, if anything is still wrong, you should get rather meaningful errors stating what's wrong and where to look for the answer. If there will be anything unclear, feel free to post question in this forum thread. Please also share your remarks, ideas, problems, as this thread might become very useful for people who want to write their own component, and might also become a seed for proper documentation page on components.

Thx,
m
Found this reply helpful? Contribute NOW and help us improve CiviCRM with the Make it Happen! initiative.

My absolute favourite: Wordpress Integration!.

Donate Now!

wmostrey

  • I post occasionally
  • **
  • Posts: 36
  • Karma: 3
    • Wim Mostrey
Re: Upgrade custom component from 1.7 to 2.1
January 09, 2009, 06:27:46 am
Thanks Michał, I was missing the database entry in civicrm_component. I was now able to enable the custom component. Do note that apart from the methods you mention the following is also required:
Code: [Select]
    public function getActivityTypes()
    {
        return null;
    }

What the component does is add an extra action to search results. In 1.7 this was done by adding a "task" attribute in the CRM_Core_Component class:

Code: [Select]
  'task'    => array( '36' => array(
    'title'  => 'My custom action',
    'class'  => 'CRM_CUST_Form_Task_SearchDist',
    'result' => false,
  ),),                                           
In the component I would have a SearchDist.php which extended CRM_Contact_Form_Task and adds a buildQuickForm function. I wonder how this works now with Task.php. I'm not sure what I should return in static function &tasks to enable this new search action. Could you help me with this or direct me to code in an existing component?

Thank you so much for your directions, your help is highly appreciated.

Michał Mach

  • Ask me questions
  • ****
  • Posts: 748
  • Karma: 59
    • CiviCRM site
  • CiviCRM version: latest
  • CMS version: Drupal and Joomla latest
  • MySQL version: numerous
  • PHP version: 5.3 and 5.2
Re: Upgrade custom component from 1.7 to 2.1
January 12, 2009, 06:11:12 am
Quote from: wmostrey on January 09, 2009, 06:27:46 am
Thanks Michał, I was missing the database entry in civicrm_component. I was now able to enable the custom component. Do note that apart from the methods you mention the following is also required:
Code: [Select]
    public function getActivityTypes()
    {
        return null;
    }


True - thanks for pointing to it. This function doesn't do anything at the moment, but I won't remove it from the interface at this stage, since it's very late in the release cycle (we published first alpha of 2.2 today) and I might make use of it in 2.3.

Quote from: wmostrey on January 09, 2009, 06:27:46 am
What the component does is add an extra action to search results. In 1.7 this was done by adding a "task" attribute in the CRM_Core_Component class:

Code: [Select]
  'task'    => array( '36' => array(
    'title'  => 'My custom action',
    'class'  => 'CRM_CUST_Form_Task_SearchDist',
    'result' => false,
  ),),                                           
In the component I would have a SearchDist.php which extended CRM_Contact_Form_Task and adds a buildQuickForm function. I wonder how this works now with Task.php. I'm not sure what I should return in static function &tasks to enable this new search action. Could you help me with this or direct me to code in an existing component?

Thank you so much for your directions, your help is highly appreciated.

Hm, tasks handling hasn't been tackled too much in recent "componentisation", but thanks for pointing our direction this way, it might make sense to take a closer look at it. I'm not sure I'm able to respond to your question with clear recipe right now, need a bit of time to take a look at it. In the meanwhile, there is a couple of resources that you might want to take a look at:
http://wiki.civicrm.org/confluence/display/CRMDOC/How+to+Add+Actions+to+the+Contact+Search+Dropdown

When it comes to looking at the code, please see following files for CiviContribute implementation:
Contribute/Form/Task.php
Contribute/Form/Search.php
Contribute/StateMachine/Search.php

However, I'm not sure if writing a component is the best way for extending the core advanced search form, unless your component does something else as well. It would be good if you could also describe your requirements in more detail so we know what you're trying to achieve.

Thx,
m

P.S. I've added the outcome from this thread as a seed documentation page on creating components:
http://wiki.civicrm.org/confluence/display/CRMDOC/Creating+custom+components
I'll be working on this document more as discussion evolves and we make progress on further components development in 2.3.
Found this reply helpful? Contribute NOW and help us improve CiviCRM with the Make it Happen! initiative.

My absolute favourite: Wordpress Integration!.

Donate Now!

fen

  • I post frequently
  • ***
  • Posts: 216
  • Karma: 13
    • CivicActions
  • CiviCRM version: 3.3-4.3
  • CMS version: Drupal 6/7
  • MySQL version: 5.1/5.5
  • PHP version: 5.3/5.4
Re: Upgrade custom component from 1.7 to 2.1
January 13, 2009, 07:34:09 am
Question from the peanut gallery: can all these code changes for adding a new component be collected under a single directory, say, CRM/local/, so that they can more easily be migrated to future releases?

Michał Mach

  • Ask me questions
  • ****
  • Posts: 748
  • Karma: 59
    • CiviCRM site
  • CiviCRM version: latest
  • CMS version: Drupal and Joomla latest
  • MySQL version: numerous
  • PHP version: 5.3 and 5.2
Re: Upgrade custom component from 1.7 to 2.1
January 13, 2009, 07:51:06 am
Fen,

If I get you right, "single directory" is basically the goal. The general concept of the whole componentisation rework that's going on since 1.8 is to make components pluggable and independent from the core from code perspective. So basically, if you're writing your own component called "HelloWorld", everything related to this component will be placed in CRM/HelloWorld directory and no changes elsewhere will be needed for the upgrade.

Or do you propose sth else? Glad to hear, all the ideas are welcome, this whole topic of formal CiviCRM components support is kind of emerging recently, so every feedback is extremely valuable.

Thx,
m
Found this reply helpful? Contribute NOW and help us improve CiviCRM with the Make it Happen! initiative.

My absolute favourite: Wordpress Integration!.

Donate Now!

fen

  • I post frequently
  • ***
  • Posts: 216
  • Karma: 13
    • CivicActions
  • CiviCRM version: 3.3-4.3
  • CMS version: Drupal 6/7
  • MySQL version: 5.1/5.5
  • PHP version: 5.3/5.4
Re: Upgrade custom component from 1.7 to 2.1
January 13, 2009, 08:49:48 am
You answered me perfectly: all files for a custom HelloWorld component in CRM/HelloWorld/ is excellent.

Thanks!

wmostrey

  • I post occasionally
  • **
  • Posts: 36
  • Karma: 3
    • Wim Mostrey
Re: Upgrade custom component from 1.7 to 2.1
January 14, 2009, 02:03:45 am
Hi Michał,

Thanks a lot for the clarification and for starting the documentation. I sent you a PM with the code of our custom component.

Michał Mach

  • Ask me questions
  • ****
  • Posts: 748
  • Karma: 59
    • CiviCRM site
  • CiviCRM version: latest
  • CMS version: Drupal and Joomla latest
  • MySQL version: numerous
  • PHP version: 5.3 and 5.2
Re: Upgrade custom component from 1.7 to 2.1
January 15, 2009, 03:46:08 am
Wim,

Technically, you've sent it to Lobo, but I got it. :-) Could you paste it on the forum, publically? Others could take a look and have other/better ideas that I do.

Otherwise, I'll take a look and post soon.

Thx,
m
Found this reply helpful? Contribute NOW and help us improve CiviCRM with the Make it Happen! initiative.

My absolute favourite: Wordpress Integration!.

Donate Now!

wmostrey

  • I post occasionally
  • **
  • Posts: 36
  • Karma: 3
    • Wim Mostrey
Re: Upgrade custom component from 1.7 to 2.1
January 26, 2009, 12:39:55 am
Here is the task code, saved in EJUSA/Form/Task/SearchDist.php, for CiviCRM 1.7:
Code: [Select]
<?
require_once 'CRM/Contact/Form/Task.php';

/**
 * This class helps to print the labels for contacts
 *
 */
class CRM_EJUSA_Form_Task_SearchDist extends CRM_Contact_Form_Task
{

  /**
     * build all the data structures needed to build the form
     *
     * @return void
     * @access public
     */
  function preProcess()
  {
    $this->set( 'contactIds', $this->_contactIds );
    parent::preProcess( );
  }

  /**
     * Build the form
     *   
     * @access public
     * @return void
     */
  function buildQuickForm()
  {

    CRM_Utils_System::setTitle( ts('Get Constituent Report') );

    /**Get an array of the custom fields which
    * specify a district the legislator represents matched
    * with the contact fields if they live in a district
    */

    $districtsRepresented = _ejusa_getDistrictFields();

   
    foreach($districtsRepresented as $match) {     
      $customFields[$match->represents->label] = 'custom_' . $match->represents->id;
    }

    $returnProperties = array("display_name" => 1);
    $returnProperties = array_merge($returnProperties,array_combine($customFields,array_fill(0,count($customFields),1)));
   
    $returnProperties['gender'] = 1;

    $values = array(); //used to maintain unique values
    foreach ($this->_contactIds as $value) {
      $params  = array( 'contact_id'=> $value );
      $contact[$value] =& crm_contact_search($params, $returnProperties );
     
      foreach($customFields as $label => $fieldName) {
        if($contact[$value][0][$value][$fieldName]) {
            if(isset($values[$fieldName][$contact[$value][0][$value][$fieldName]])) {
                continue;
            }
            $values[$fieldName][$contact[$value][0][$value][$fieldName]] = 1;
          $choices[$fieldName] .= isset($choices[$fieldName]) ?  ";" . $contact[$value][0][$value][$fieldName] : $contact[$value][0][$value][$fieldName];
        }
      }
      if ( is_a( $contact, 'CRM_Core_Error' ) ) {
        return null;
      }
    }
   
    if(!$choices) {
      $e = CRM_Core_Error::statusBounce("None of the selected contacts represent districts");
      exit;
    }
   


    foreach($choices as $field => $districts) {
      if ($districts == 'unassigned') {     
        continue;       
      }
      $queryDisplay .= isset($queryDisplay) ? "\n<br/>and all contacts in:<br/>\n " : '';
      foreach($districtsRepresented as $newMatch) {
        if ("custom_" . $newMatch->represents->id == $field) {
          //This is the one we are matching
          $represendedField = "custom_" . $newMatch->represented->id;
          $represendedFieldDisplay = $newMatch->represented->label;
          $_arrDistricts = explode(';',$districts);
          if(count($_arrDistricts) > 1) {
            $districtText = implode(", ", array_slice($_arrDistricts,0,count($_arrDistricts) -1 )) . ' and ' . $_arrDistricts[count($_arrDistricts) - 1]; 
            $queryDisplay .= $represendedFieldDisplay . "s " . $districtText;
          } else {
            $districtText = $_arrDistricts[0];
            $queryDisplay .= $represendedFieldDisplay . " " . $districtText;
          }
          $queryDisplay  .= "\n";
        }
      }
     
      $this->add('hidden',"choices[$represendedField]", $districts);
     
    }
   
    $queryDisplay = "This will create a smart group containing all contacts in:<br/>\n" . $queryDisplay;
    CRM_Core_Session::setStatus($queryDisplay);
   
    $this->add('text', 'title', ts('Smart Group Name:'),null,true);
    $this->add('text', 'description', ts('Smart Group Description:'),null,true);
    $this->addDefaultButtons( ts('Create Smart Group'));

  }

  /**
     * process the form after the input has been submitted and validated
     *
     * @access public
     * @return void
     */
  public function postProcess()
  {
    $formValues = $fv = $this->controller->exportValues($this->_name);
    $config =& new CRM_Core_Config;
   
    $statusField = _ejusa_getCustomFieldName(EJUSA_CONTACT_STATUS_FIELD_LABEL);

    foreach($formValues['choices'] as $selectedField => $_selected) {
      $selected = explode(";",$_selected);
      foreach($selected as $value) {
        $i++; //starts at one
        $fakeFormValues['mapper'][$i][] = array('Individual',"$selectedField");
        $fakeFormValues['operator'][$i][] = '=';
        $fakeFormValues['value'][$i][] = "$value";               
       
        //Also Add the Status Flag
        $fakeFormValues['mapper'][$i][] = array('Individual',"$statusField");
        $fakeFormValues['operator'][$i][] = '!=';
        $fakeFormValues['value'][$i][] = "1";
       
        //Only From Constuents!
        $fakeFormValues['mapper'][$i][] = array('Individual',"groups");
        $fakeFormValues['operator'][$i][] = 'IN';
        $fakeFormValues['value'][$i][] = EJUSA_CONSTITUENT_GID;
      }
     
    }
   
   
    //save the mapping for search builder
    require_once "CRM/Core/BAO/Mapping.php";


    //save record in mapping table
    $mappingParams = array('mapping_type' => 'Search Builder');
    $temp      = array();
    $mapping   = CRM_Core_BAO_Mapping::add($mappingParams, $temp) ;
    $mappingId = $mapping->id;


    //save mapping fields
    CRM_Core_BAO_Mapping::saveMappingFields($fakeFormValues , $mappingId);

    //save the search
    $savedSearch =& new CRM_Contact_BAO_SavedSearch();
    $savedSearch->id          = $this->_id;
    $savedSearch->form_values = serialize($fakeFormValues);
    $savedSearch->mapping_id  = $mappingId;
    $savedSearch->save();
    $this->set('ssID', $savedSearch->id);
   
   
    // also create a group that is associated with this saved search only if new saved search
    $params = array( );
    $params['domain_id'  ]     = CRM_Core_Config::domainID( );
    $params['title'      ]     = $formValues['title'];
    $params['description']     = $formValues['description'];
    $params['visibility' ]     = 'User and User Admin Only';
    $params['saved_search_id'] = $savedSearch->id;
    $params['is_active']       = 1;
   
    $group =& CRM_Contact_BAO_Group::create( $params );
    CRM_Core_Session::setStatus( ts('Your smart group has been saved as "%1". Go the <a href="%2">member list </a>',
      array(
        1 => $formValues['title'],
        2 => url("civicrm/group/search","?reset=1&force=1&context=smog&gid=$group->id")
        )));

    return;
  }
}


?>

Michał Mach

  • Ask me questions
  • ****
  • Posts: 748
  • Karma: 59
    • CiviCRM site
  • CiviCRM version: latest
  • CMS version: Drupal and Joomla latest
  • MySQL version: numerous
  • PHP version: 5.3 and 5.2
Re: Upgrade custom component from 1.7 to 2.1
January 27, 2009, 04:04:10 am
Hey,

Sorry for late reply, I've been busy with some upcoming documentation changes.

Thanks for posting your code. In general, if adding new task is everything that you require, I wouldn't bother with adding new component, especially that component model doesn't support plugging in tasks to the core (note to self: take a look and consider this on nearest refactoring iteration). My advise would be to just follow the instructions from docs linked above on how to add new action to search dropdown.

Thx,
m
Found this reply helpful? Contribute NOW and help us improve CiviCRM with the Make it Happen! initiative.

My absolute favourite: Wordpress Integration!.

Donate Now!

Pages: [1]
  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion (Moderator: Donald Lobo) »
  • Upgrade custom component from 1.7 to 2.1

This forum was archived on 2017-11-26.