vdespa.de

Developers

Content related to Developers
  • Creating a Simple Extension Module in Vtiger 6

    Technically what I am trying to do it to build an "Extension Module" as per vTiger definition, that can be installed via the Module Manager in the GUI.

    I must say from the start that I am not an expert in vTiger custom development. The reason for writing this is that I had lots of errors like:

    dUnzip2: File 'manifest.xml' is not compressed in the zip

    Invalid File provided for module import! Try Again.

    While I won't try to explain the cause of the errors above, I will try to explain how to build your module, sharing all the code necessary.

    I've tested the code below in vTiger 6.0.0 beta. All the code is freely available on github. Fell free to fork it and to send a PR is something is broken.

    What will this module do?

    This plugin will install, register an event handler and later when an Account item is saved, it will catch the data that was saved. If you don't need this functionality, just remove the relevant code from the SimpleExtension.php and SimpleExtensionHandler.php.

    Additionally it will be displayed under Tools, and it will display a basic view.

    Can I use it in Vtiger 5.4?

    NO, this works just under Vtiger 6.

    Step 0 - Make sure this is for you

    "Heads up!" First download the install kit (v.1.1) and test if this code works for you. Stop now if this is not working for you.

    If the module installed fine, than you are ready to go. I will try to give some basic steps on how to build this. Follow the steps but also the code showed on github.

    Step 1 - The manifest file

    Create an empty folder what will hold all the files. Generically I will refer to this folder as root.

    First thing we need is the manifest.xml file. This file is required. I will name my module SimpleExtension. Feel free to give it whatever name you like.

    Step 2 - The module class

    Create a folder named Modules in the root folder and inside it put the file SimpleExtension.php and if you need to handle Vtiger events, also add SimpleExtensionHandler.php.

    Step 3 - Adding the language file

    Next step is to add the language file. Create a folder named languages and put it inside the root folder. Inside languages put another folder named en_us and place inside it the file SimpleExtension.php. It is required to make the en_us folder. For other languages use the specific code, eg. de_de, en_gb, es_es, ro_ro etc.

    Step 4 - Creating a view

    In the module folder (/root/modules/SimpleExtension/), add a new folder called views. Inside you put the file List.php which will be our default view (triggered from top-level Menu). The view will be calling a module template, which we will define later.

    Creating the view / template (layout) is not mandatory; the implementation of the MVC architecture provides a fallback to a core class, when a module class has not been found. This means that if Vtiger won't find your view definition, it will just display a default view.

    Step 5 - Adding a module template (layout)

    You need to create a certain file structure, \root\layouts\vlayout\modules\SimpleExtension. Inside the last folder there will be to files: IndexViewPreProcess.tpl and Index.tpl.

    Note for Vtiger 6.0.0 beta - I am not sure that is the reason, but it seems that when installing the module, Vtiger is not copying the layouts folder into the system. I don't know why (if it's a bug or something I did wrong), but in order to get Step 5 to work, you need to manually merge the layout folder with the existing files in the system.

    Step 6 - Creating the zip package

    Now that we have all the files needed, let's just create the final package. When inside the root folder, select all the files and create an archive.

    When viewing the archive contents, manifest.xml and the other folders need to be directly visible (no other folders in between). Do NOT archive the root folder itself.

    Step 7 - See in in action

    Create a new organization (or change an existing one). When the event vtiger.entity.aftersave is triggered, the data will be passed to the event handler in the module.

    When this happens, the data will be dumped with var_dump() and the script will end. Ending the script here is just done for testing purposes. In "real life" you would do something quietly.

    Step 8 - Uninstalling and re-installing the module

    If you try to re-install the module, this will not be permitted. In Vtiger 6.0.0 beta I haven't figure out how to upgrade an module. Just changing the version and trying to install it does not work; but I haven't digged to much into this issue.

    Basically I run the script below to uninstall the module (put in inside a file in the root folder of the Vtiger installation):

    include_once('vtlib/Vtiger/Module.php');
    
    /*
     * Delete a module
     */
    $module = Vtiger_Module::getInstance('SimpleExtension');
    if($module)
    {
        // Delete from system
        $module->delete();
        echo "Module deleted!";
    } else {
        echo "Module was not found and could not be deleted!";
    }
    

    Run the file via CLI or browser.

    Why your installation may fail

    At the beginning of the install process there are several checks being made, inside Vtiger_PackageImport::checkZip() method, to see if the package uploaded is a valid package. Basically a couple of rules need to be respected:

    • manifest.xml needs to be present
    • Vtiger version in the manifest file must meet the minimum criteria defined by the Vtiger version you are running
    • language file needs to be present
    • files inside the package must have a certain structure in folders; not having the expected structure leads to confusing error messages
    • language file name must fit the module name

    Pro tip

    If you can still figure out what is going wrong with your module install, get into the vTiger code. Set your breakpoints for the debugger at the method Settings_ModuleManager_ModuleImport_View::importUserModuleStep2() inside the file /modules/Settings/ModuleManager/views/ModuleImport.php.

    Did I say or do something wrong?

    Feel free to leave a comment or correct the problem yourself by sending a PR.

    References and useful links:

  • Different pagination style in frontend vs. backend using JPagination

    If you use the method getListFooter() from the JPagination class, you may encounter a problem if you use this method also in the frontend of your website, most likely inside a component.

    Actually Joomla calls different HTML templates to generate the HTML do display the pagination. In the backend it calls the pagination.php file from the admin template (e.g. administrator/templates/bluestork/html/pagination.php) while in frontend it will call the /templates/CURRENT_TEMPLATE/html/pagination.php and if it does not find it, it will just display some html. If it cannot find it, it will use _list_footer() method, from the same class.

    To check which pagination.php file is calling, have a look inside:

    /libraries/joomla/html/pagination.php inside the method: getListFooter()

    and echo / var_dump the variable $chromePath to see the value of it.

    If you are looking to get the same style from backend, just copy the pagination.php file from the admin template and put it in your site template. Don’t forget to include the admin template css file (e.g. administrator/templates/bluestork/css/template.css).

  • Firebird - Converting BLOB / BINARY column in STRING

    This article will try to resolve two problems. Viewing a BLOB inside a database administration tool like FlameRobin and / or using it inside a server side script, like PHP.

    Viewing a BLOB inside a database administration tool

    You have a column which stores information as BLOB, but the information itself is text. With the following query you can display the contents of this field:

    SELECT      t1.*, 
        CAST(SUBSTRING(blob1 FROM 1 FOR 32000) AS VARCHAR(32000)) AS myblobfield 
    FROM        t1
    

    Please note that maximum length for VARCHAR is 32767 bytes and if the data gets truncated, the query may fail. Also if the BLOG is a multiline text, only the first line will be displayed.

    Now I am not saying that this is the best method or something, it is just a solution that worked for me. If you know another one, a better, simpler way to do it, drop a message in the comment section below.

    Reading a BLOB field in PHP

    PHP offers natively the possibility of getting the BLOB data as a string.

    For example ibase_fetch_assoc ( resource $result [, int $fetch_flag = 0 ] ) provides the $fetch_flag parameter which can be set to IBASE_TEXT, causing the function to return BLOB contents instead of BLOB ids.

    Practical example: // Connect to database $dbh = ibase_connect($host, $username, $password);

        // Build query
        $query = "SELECT * FROM t1";
    
        // Get result
        $result = ibase_query($dbh, $query);
    
        // Loop through results
        while ($row = ibase_fetch_object($result, IBASE_TEXT)) 
        {
            var_dump($row);
        }
    

    Resources

    How to convert BLOB to string? - http://www.firebirdfaq.org/faq250/

    Firebird CAST() - http://www.firebirdsql.org/refdocs/langrefupd21-intfunc-cast.html

    PHP ibasefetchassoc - http://www.php.net/manual/en/function.ibase-fetch-assoc.php

    Advantages/disadvantages of BLOBs vs. VARCHARs - http://www.volny.cz/iprenosil/interbase/ipibstrings.htm

  • Joomla! Library for PHPExcel

    I've used PHPExcel in a lot of projects and a felt the need to create a library to handle and update this library using the Joomla! build-in update mechanism.

     

  • Translating the default value in a field (JForm / JField)

    When creating forms with JForm, you already know that attributes such as label or description get automatically a translation from Joomla.

    But if you set for your form also the default value and it's nothing numerical and which is a language string, guess what will happen... Yes, it will not be translated automatically.

    The trick is adding another property to your field, called translate_default and setting it's value to 1 or true.

    Here is a quick test (snippet is a hacked version of client.xml from the Banners component:

    <field 
      name="name" 
      type="text" 
      class="inputbox"
            size="40" 
      label="COM_BANNERS_FIELD_CLIENT_NAME_LABEL"
            description="COM_BANNERS_FIELD_CLIENT_NAME_DESC"
            translate_default="true"
            default="JYES"
            required="true" />
    

    If you replace the code above in /administrator/components/com_banners/models/forms/client.xml and go to Banners > Clients, you will get the field Client name already populated with the translated value YES.

    This "core hack" is just a quick way to show you how this works. Now feel free to you this thing in your forms.

  • Vtiger 6 - Web Services for Products with VAT and Sales Tax

    In case you are interacting with Vtiger 6 via it's web services interface, you might have noticed that the entity Products not not expose it's VAT, service or sales tax (which are referenced from another table).

    Tax information is stored in the table vtiger_producttaxrel and there is also a new entity exposed via web services called ProductTaxes and also Taxes. Unfortunately, because this table is relational, web services cannot work with this table in a proper way. At least from the research I did, if you need to import / export this information via web services, there is no way to do this at this point, because existing classes cannot directly work with this table. Any efforts to retrieve or insert information will fail (largely because this table does not implement an id field as primary key as other entities do).

    Working solution for importing / exporting / insert / update / delete Product Tax via Web Services

    While ideally you wound want just to work just with Products, this solution overrides the core class that handles ProductTaxes. Any insert / update / retrieve will have to be done in two or more steps, and all will be using the create operation, even if semantically it does not make sense.

    All examples shown rely on the library vtwsclib and only relevant parts of the code are presented. Also the examples not not include error handling. You SHOULD add them to your code.

    Retrieving a new product with VAT or other taxes

    This needs to be done in two or more steps (if you require more than one tax).

    $recordId = '14x19';
    $recordInfo = $client->doRetrieve($recordId);
    
    $params = array(
        "operation" => "retrieve",
        "productid" => "14x23",
        "taxid" => "33x1"
    );
    $recordTaxesInfo = $client->doCreate("ProductTaxes", $params);
    

    A tax for a product will look like:

    Array
    (
        [productid] => 14x19
        [taxid] => 33x1
        [taxpercentage] => 17.000
        [id] => 
    )
    

    Inserting a new product with VAT

    // Create product
    $params = array(
        "productname" => "My product with VAT",
    );
    $createResult = $client->doCreate("Products", $params);
    
    // Add tax to created product
    $params = array(
        "operation" => "create",
        "productid" => $createResult['id'],
        "taxid" => "33x1",
        "taxpercentage" => "19"
    );
    $createResult = $client->doCreate("ProductTaxes", $params);
    

    Updating a product with VAT

    Updating is handled by doCreate as well.

    // Updating tax value for existing product
    $params = array(
        "operation" => "update",
        "productid" => "14x16",
        "taxid" => "33x1",
        "taxpercentage" => "16"
    );
    $createResult = $client->doCreate("ProductTaxes", $params);
    

    Deleting a product

    No matter how you created the taxes (manually or via webservices), it looks like invoking delete on Products will NOT delete it's references in the table vtiger_producttaxrel.

    // Delete product with id
    $params = array(
        "id" => "14x17",
    );
    
    $deleteResult = $client->doInvoke("delete", $params, "POST");
    

    The code above will move the product to the Recycle bin. Emptying it, will not delete data from vtiger_producttaxrel. This looks like a bug in Vtiger and I am not addressing this scenario with a fix.

    Deleting just the tax for a product

    While normally this should be done via invoking delete, to implement / get it to work that way, it would mean a couple more classes and methods that need to be overwritten. I opted for using the doCreate do handle the delete process as well.

    $params = array(
        "operation" => "delete",
        "productid" => "14x18",
        "taxid" => "33x1",
    );
    
    $deleteResult = $client->doCreate("ProductTaxes", $params);
    

    How to Install the Solution

    1. Download file VtigerProductTaxesOperation.php from github.
    2. Copy file VtigerProductTaxesOperation.php to VTIGER_ROOT/include/Webservices/Custom/VtigerProductTaxesOperation.php.
    3. Execute following MySQL query to replace the handler path for the ProductTaxes entity (assumption is made that the entry ProductTaxes already exists in the table). UPDATE vtiger_ws_entity SET handler_path = 'include/Webservices/Custom/VtigerProductTaxesOperation.php' WHERE vtiger_ws_entity.name = "ProductTaxes";
    4. All set!

    Technical Details of the Implementation

    Querying the Products entity does not show the product relationships. There is no join on related tables. From what I understand (https://discussions.vtiger.com/index.php?p=/discussion/171789) this is on the development roadmap, but not yet available in Vtiger 6.0.0. So if you need it now, bummer.

    After doing some research / testing, I started overriding methods from the class VtigerProductTaxesOperation which itself extends VtigerActorOperation.

    doCreate is the only operation for ProductTaxes that manages to bypass the Vtiger call logic. Any other methods will fail as they rely on you providing a valid id, which for ProductTaxes is simply not possible. So I am using doCreate as a controller in order to route all operations to the proper method. Semantically bad, but better as overriding tons of classes.

    This solution is just a workaround. It's not architecturally sound, but (at least for my needs) it does the job.

    Vtiger will need to implement the Products in a similar manner on how the Inventory is handled (https://wiki.vtiger.com/index.php/ServerAPIreferencemanual#InventoryRecordCreate). Meanwhile this is the only solution I have found for Vtiger 6.0.0.

    Use with caution and keep on eye on developments on this subject.

    Word of Caution

    • This is a non-standard implementation. I cannot guarantee that this will work on future versions. It's best to check in version 6.1 or later if this functionality does not already exist.
    • Test in advance if this fulfills your authorization needs. I only tested it against an administrator account.
    • Do not use this solution on production environments until you tested in thoroughly and are certain it works as you need it to work and no data corruption or any other issues occur.
    • I decline any responsibility for damages to your installation and database.
    • This may conflict with the Inventory creation over web services - test your inventory handling as well.

    Feedback & Contributions

    The code for this solution if freely available on github. Fell free to fork it and to send a PR is something is broken. If you find something wrong or you want to further improve it, raise a issue or leave a comment in the section below.



Developers

Generate Excel documents from your custom Joomla component in seconds. Library download and quick start guide is available.

View details »

Site builders

Generate Excel documents from your custom Joomla component in seconds. Library download and quick start guide is available.

View details »

Users

article.md brings to Joomla! the power of Github & co. combined with a lightweight markup language, Markdown.

View details »