Fixed few bugs in import/export of JCB packages. Resolved gh-204. Made some updates to the Dropbox class, more coming.

This commit is contained in:
Llewellyn van der Merwe 2017-12-25 14:46:35 +02:00
parent edafb6e688
commit cd5f1f2c87
No known key found for this signature in database
GPG Key ID: CAD7B16D27AF28C5
8 changed files with 229 additions and 186 deletions

View File

@ -111,11 +111,11 @@ Component Builder is mapped as a component in itself on my local development env
+ *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io) + *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io)
+ *Name*: [Component Builder](http://vdm.bz/component-builder) + *Name*: [Component Builder](http://vdm.bz/component-builder)
+ *First Build*: 30th April, 2015 + *First Build*: 30th April, 2015
+ *Last Build*: 22nd December, 2017 + *Last Build*: 25th December, 2017
+ *Version*: 2.6.7 + *Version*: 2.6.7
+ *Copyright*: Copyright (C) 2015. All Rights Reserved + *Copyright*: Copyright (C) 2015. All Rights Reserved
+ *License*: GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html + *License*: GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html
+ *Line count*: **178381** + *Line count*: **178415**
+ *File count*: **1145** + *File count*: **1145**
+ *Folder count*: **184** + *Folder count*: **184**

View File

@ -111,11 +111,11 @@ Component Builder is mapped as a component in itself on my local development env
+ *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io) + *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io)
+ *Name*: [Component Builder](http://vdm.bz/component-builder) + *Name*: [Component Builder](http://vdm.bz/component-builder)
+ *First Build*: 30th April, 2015 + *First Build*: 30th April, 2015
+ *Last Build*: 22nd December, 2017 + *Last Build*: 25th December, 2017
+ *Version*: 2.6.7 + *Version*: 2.6.7
+ *Copyright*: Copyright (C) 2015. All Rights Reserved + *Copyright*: Copyright (C) 2015. All Rights Reserved
+ *License*: GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html + *License*: GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html
+ *Line count*: **178381** + *Line count*: **178415**
+ *File count*: **1145** + *File count*: **1145**
+ *Folder count*: **184** + *Folder count*: **184**

View File

@ -1,4 +1,5 @@
<?php <?php
/*----------------------------------------------------------------------------------| www.vdm.io |----/ /*----------------------------------------------------------------------------------| www.vdm.io |----/
Vast Development Method Vast Development Method
/-------------------------------------------------------------------------------------------------------/ /-------------------------------------------------------------------------------------------------------/
@ -20,6 +21,7 @@ defined('_JEXEC') or die;
*/ */
class Dropbox class Dropbox
{ {
/** /**
* final url * final url
*/ */
@ -98,8 +100,8 @@ class Dropbox
public $error_summary = array(); public $error_summary = array();
/** /**
* force the update to reset * force the update to reset
**/ * */
public $forceReset = false; public $forceReset = false;
/** /**
@ -108,7 +110,7 @@ class Dropbox
public function __construct(JModelLegacy $model, $buildType) public function __construct(JModelLegacy $model, $buildType)
{ {
// set the url at this point for now // set the url at this point for now
$this->url = $this->postUrl["protocol"].$this->postUrl["suddomain"].$this->postUrl["domain"].$this->postUrl["path"]; $this->url = $this->postUrl["protocol"] . $this->postUrl["suddomain"] . $this->postUrl["domain"] . $this->postUrl["path"];
// set the local model // set the local model
$this->model = $model; $this->model = $model;
// set the build type // set the build type
@ -185,7 +187,7 @@ class Dropbox
if (2 == $this->dropboxOption) if (2 == $this->dropboxOption)
{ {
// simply set the path // simply set the path
$this->targetPath = '/'.trim(strtolower($this->dropboxTarget), '/'); $this->targetPath = '/' . trim(strtolower($this->dropboxTarget), '/');
return true; return true;
} }
@ -223,12 +225,11 @@ class Dropbox
protected function makeGetCall() protected function makeGetCall()
{ {
$options = array( $options = array(
'http' => array( 'http' => array(
'header' => "Content-Type: application/json\r\n". 'header' => "Content-Type: application/json\r\n" .
"Authorization: Bearer ".$this->oauthToken, "Authorization: Bearer " . $this->oauthToken,
'method' => "POST" 'method' => "POST"
), ),
); );
@ -239,28 +240,30 @@ class Dropbox
$options['http']['content'] = $this->query; $options['http']['content'] = $this->query;
$context = stream_context_create($options); $context = stream_context_create($options);
$response = file_get_contents($this->url.$this->domainpath[$this->type], false, $context); $response = file_get_contents($this->url . $this->domainpath[$this->type], false, $context);
// store the result
return $this->getCallResult($response);
}
protected function getCallResult($response)
{
if ($response === FALSE) if ($response === FALSE)
{ {
$this->error_summary[] = $this->type.'_error'; $this->error_summary[] = $this->type . '_error';
return false; return false;
} }
else // store the result
{ return $this->setTheResult(json_decode($response));
// store the result
return $this->setTheResult(json_decode($response));
}
} }
protected function makeCurlCall() protected function makeCurlCall()
{ {
$headers = array('Authorization: Bearer '. $this->oauthToken, $headers = array('Authorization: Bearer ' . $this->oauthToken,
'Content-Type: application/json' 'Content-Type: application/json'
); );
$ch = curl_init($this->url.$this->domainpath[$this->type]); $ch = curl_init($this->url . $this->domainpath[$this->type]);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// check if query is set // check if query is set
@ -277,6 +280,12 @@ class Dropbox
curl_close($ch); curl_close($ch);
// store the result
return $this->curlCallResult($response);
}
public function curlCallResult($response)
{
if ($this->checkJson($response)) if ($this->checkJson($response))
{ {
$response = json_decode($response); $response = json_decode($response);
@ -318,15 +327,15 @@ class Dropbox
return true; return true;
} }
} }
$this->error_summary[] = $this->type.'_error'; $this->error_summary[] = $this->type . '_error';
break; break;
case "create_shared_link": case "create_shared_link":
if (isset($data->url) && isset($data->path) && $this->storeSharedLink($this->fixPath($data->path), str_replace('?dl=0','?dl=1',$data->url))) if (isset($data->url) && isset($data->path) && $this->storeSharedLink($this->fixPath($data->path), str_replace('?dl=0', '?dl=1', $data->url)))
{ {
// we stored the link // we stored the link
return true; return true;
} }
$this->error_summary[] = $this->type.'_error'; $this->error_summary[] = $this->type . '_error';
break; break;
case "get_shared_link_metadata": case "get_shared_link_metadata":
if (isset($data->path_lower)) if (isset($data->path_lower))
@ -334,14 +343,14 @@ class Dropbox
$this->targetPath = $data->path_lower; $this->targetPath = $data->path_lower;
return true; return true;
} }
$this->error_summary[] = $this->type.'_error'; $this->error_summary[] = $this->type . '_error';
break; break;
case "revoke": case "revoke":
if (is_null($data)) if (is_null($data))
{ {
return true; return true;
} }
$this->error_summary[] = $this->type.'_error'; $this->error_summary[] = $this->type . '_error';
break; break;
} }
$this->forceReset = true; $this->forceReset = true;
@ -353,14 +362,14 @@ class Dropbox
// we need to store the url to DB // we need to store the url to DB
if (isset($this->mediaData[$path])) if (isset($this->mediaData[$path]))
{ {
$localListing = array(); $localListing = array();
$localListing['id'] = 0; $localListing['id'] = 0;
$localListing['name'] = $this->mediaData[$path]['name']; $localListing['name'] = $this->mediaData[$path]['name'];
$localListing['size'] = $this->mediaData[$path]['size']; $localListing['size'] = $this->mediaData[$path]['size'];
$localListing['key'] = $path; $localListing['key'] = $path;
$localListing['url'] = $url; $localListing['url'] = $url;
$localListing['build'] = $this->build; $localListing['build'] = $this->build;
$localListing['external_source'] = (int) $this->sourceID; $localListing['external_source'] = (int) $this->sourceID;
// free some memory // free some memory
unset($this->mediaData[$path]); unset($this->mediaData[$path]);
// check if item already set // check if item already set
@ -386,7 +395,7 @@ class Dropbox
{ {
foreach ($this->addTypes as $add) foreach ($this->addTypes as $add)
{ {
if (strpos($item->name,$add) !== false) if (strpos($item->name, $add) !== false)
{ {
$addLink = true; $addLink = true;
} }
@ -417,7 +426,7 @@ class Dropbox
} }
else else
{ {
$path = 'VDM_pLeK_h0uEr'.$path; $path = 'VDM_pLeK_h0uEr' . $path;
} }
return $path; return $path;
} }

View File

@ -41,12 +41,16 @@ abstract class ComponentbuilderHelper
self::loadSession(); self::loadSession();
} }
/**
* The global updater
**/
protected static $globalUpdater = array();
/* /*
* Convert repeatable field to subform * Convert repeatable field to subform
* *
* @param array $item The array to convert * @param array $item The array to convert
* @param string $name The main field name * @param string $name The main field name
* @param array $updater The updater (dynamic) option
* *
* @return array * @return array
*/ */
@ -72,11 +76,6 @@ abstract class ComponentbuilderHelper
return $item; return $item;
} }
/**
* The global updater
**/
protected static $globalUpdater = array();
/* /*
* Convert repeatable field to subform * Convert repeatable field to subform
* *
@ -91,66 +90,69 @@ abstract class ComponentbuilderHelper
// update the repeatable fields // update the repeatable fields
foreach ($searcher as $key => $sleutel) foreach ($searcher as $key => $sleutel)
{ {
$isJson = false; if (isset($object->{$key}))
if (isset($object->{$key}) && self::checkJson($object->{$key}))
{ {
$object->{$key} = json_decode($object->{$key}, true); $isJson = false;
$isJson = true; if (self::checkJson($object->{$key}))
}
// check if this is old values for repeatable fields
if (self::checkArray($object->{$key}) && isset($object->{$key}[$sleutel]))
{
// load it back
$object->{$key} = self::convertRepeatable($object->{$key}, $key);
// add to global updater
if (
self::checkArray($object->{$key}) && self::checkArray($updater) &&
(
( isset($updater['table']) && isset($updater['val']) && isset($updater['key']) ) ||
( isset($updater['unique']) && isset($updater['unique'][$key]) && isset($updater['unique'][$key]['table']) && isset($updater['unique'][$key]['val']) && isset($updater['unique'][$key]['key']) )
)
)
{ {
$_key = null; $object->{$key} = json_decode($object->{$key}, true);
$_value = null; $isJson = true;
$_table = null; }
// check if we have unique id table for this repeatable/subform field // check if this is old values for repeatable fields
if ( isset($updater['unique']) && isset($updater['unique'][$key]) && isset($updater['unique'][$key]['table']) && isset($updater['unique'][$key]['val']) && isset($updater['unique'][$key]['key']) ) if (self::checkArray($object->{$key}) && isset($object->{$key}[$sleutel]))
{
// load it back
$object->{$key} = self::convertRepeatable($object->{$key}, $key);
// add to global updater
if (
self::checkArray($object->{$key}) && self::checkArray($updater) &&
(
( isset($updater['table']) && isset($updater['val']) && isset($updater['key']) ) ||
( isset($updater['unique']) && isset($updater['unique'][$key]) && isset($updater['unique'][$key]['table']) && isset($updater['unique'][$key]['val']) && isset($updater['unique'][$key]['key']) )
)
)
{ {
$_key = $updater['unique'][$key]['key']; $_key = null;
$_value = $updater['unique'][$key]['val']; $_value = null;
$_table = $updater['unique'][$key]['table']; $_table = null;
} // check if we have unique id table for this repeatable/subform field
elseif ( isset($updater['table']) && isset($updater['val']) && isset($updater['key']) ) if ( isset($updater['unique']) && isset($updater['unique'][$key]) && isset($updater['unique'][$key]['table']) && isset($updater['unique'][$key]['val']) && isset($updater['unique'][$key]['key']) )
{
$_key = $updater['key'];
$_value = $updater['val'];
$_table = $updater['table'];
}
// continue only if values are valid
if (self::checkString($_table) && self::checkString($_key) && $_value > 0)
{
// set target table & item
$target = trim($_table) . '.' . trim($_key) . '.' . trim($_value);
if (!isset(self::$globalUpdater[$target]))
{ {
self::$globalUpdater[$target] = new stdClass; $_key = $updater['unique'][$key]['key'];
self::$globalUpdater[$target]->{$_key} = (int) $_value; $_value = $updater['unique'][$key]['val'];
$_table = $updater['unique'][$key]['table'];
}
elseif ( isset($updater['table']) && isset($updater['val']) && isset($updater['key']) )
{
$_key = $updater['key'];
$_value = $updater['val'];
$_table = $updater['table'];
}
// continue only if values are valid
if (self::checkString($_table) && self::checkString($_key) && $_value > 0)
{
// set target table & item
$target = trim($_table) . '.' . trim($_key) . '.' . trim($_value);
if (!isset(self::$globalUpdater[$target]))
{
self::$globalUpdater[$target] = new stdClass;
self::$globalUpdater[$target]->{$_key} = (int) $_value;
}
// load the new subform values to global updater
self::$globalUpdater[$target]->{$key} = json_encode($object->{$key});
} }
// load the new subform values to global updater
self::$globalUpdater[$target]->{$key} = json_encode($object->{$key});
} }
} }
} // no set back to json if came in as json
// no set back to json if came in as json if ($isJson && self::checkArray($object->{$key}))
if (isset($object->{$key}) && $isJson && self::checkArray($object->{$key})) {
{ $object->{$key} = json_encode($object->{$key});
$object->{$key} = json_encode($object->{$key}); }
} // remove if not json or array
// remove if not json or array elseif (!self::checkArray($object->{$key}) && !self::checkJson($object->{$key}))
elseif (isset($object->{$key}) && !self::checkArray($object->{$key}) && !self::checkJson($object->{$key})) {
{ unset($object->{$key});
unset($object->{$key}); }
} }
} }
return $object; return $object;

View File

@ -958,7 +958,7 @@ class ComponentbuilderModelImport_joomla_components extends JModelLegacy
// update the fields // update the fields
$object = new stdClass; $object = new stdClass;
$object->id = $adminview; $object->id = $adminview;
$object->addlinked_views = json_encode($addlinked_views); $object->addlinked_views = json_encode($addlinked_views, JSON_FORCE_OBJECT);
// update the admin view // update the admin view
$this->_db->updateObject('#__componentbuilder_admin_view', $object, 'id'); $this->_db->updateObject('#__componentbuilder_admin_view', $object, 'id');
} }
@ -1013,7 +1013,7 @@ class ComponentbuilderModelImport_joomla_components extends JModelLegacy
if (ComponentbuilderHelper::checkArray($updateArray)) if (ComponentbuilderHelper::checkArray($updateArray))
{ {
// load it back // load it back
$item->{$field} = json_encode($this->updateSubformIDs($updateArray, $table, $targetArray)); $item->{$field} = json_encode($this->updateSubformIDs($updateArray, $table, $targetArray), JSON_FORCE_OBJECT);
} }
} }
} }
@ -1052,7 +1052,7 @@ class ComponentbuilderModelImport_joomla_components extends JModelLegacy
} }
if ($isJson) if ($isJson)
{ {
return json_encode($values); return json_encode($values, JSON_FORCE_OBJECT);
} }
return $values; return $values;
} }
@ -1076,7 +1076,7 @@ class ComponentbuilderModelImport_joomla_components extends JModelLegacy
$item = json_decode($item, true); $item = json_decode($item, true);
$isJson = true; $isJson = true;
} }
if (ComponentbuilderHelper::checkArray($item)) if (ComponentbuilderHelper::checkArray($item) && isset($item[$target]))
{ {
// set item ID // set item ID
$itemId = (isset($item['id'])) ? $item['id'] : 'newItem'; $itemId = (isset($item['id'])) ? $item['id'] : 'newItem';
@ -1135,7 +1135,7 @@ class ComponentbuilderModelImport_joomla_components extends JModelLegacy
$item[$target] = json_encode($item[$target]); $item[$target] = json_encode($item[$target]);
} }
} }
elseif (ComponentbuilderHelper::checkObject($item)) elseif (ComponentbuilderHelper::checkObject($item) && isset($item->{$target}))
{ {
// set item ID // set item ID
$itemId = (isset($item->id)) ? $item->id : 'newItem'; $itemId = (isset($item->id)) ? $item->id : 'newItem';
@ -1550,7 +1550,7 @@ class ComponentbuilderModelImport_joomla_components extends JModelLegacy
// subform fields to target // subform fields to target
$updaterT = array( $updaterT = array(
// subformfield => array( field => type_value ) // subformfield => array( field => type_value )
'addcustom_admin_views' => array('customadminview' => 'custom_admin_view') 'addcustom_admin_views' => array('customadminview' => 'custom_admin_view', 'adminviews' => 'admin_view', 'before' => 'admin_view')
); );
// update the subform ids // update the subform ids
$this->updateSubformsIDs($item, 'component_custom_admin_views', $updaterT); $this->updateSubformsIDs($item, 'component_custom_admin_views', $updaterT);
@ -1743,7 +1743,7 @@ class ComponentbuilderModelImport_joomla_components extends JModelLegacy
// only update if we have translations // only update if we have translations
if (ComponentbuilderHelper::checkArray($translations)) if (ComponentbuilderHelper::checkArray($translations))
{ {
$item->translation = json_encode($translations); $item->translation = json_encode($translations, JSON_FORCE_OBJECT);
} }
} }
elseif (isset($item->localTranslation) && ComponentbuilderHelper::checkJson($item->localTranslation)) elseif (isset($item->localTranslation) && ComponentbuilderHelper::checkJson($item->localTranslation))

View File

@ -258,7 +258,7 @@ class ComponentbuilderModelJoomla_component extends JModelAdmin
} }
} }
$item->{$_value} = $bucket; $item->{$_value} = $bucket;
$objectUpdate->{$_value} = json_encode($bucket); $objectUpdate->{$_value} = json_encode($bucket, JSON_FORCE_OBJECT);
} }
} }
// be sure to update the table if we found repeatable fields that are still not converted // be sure to update the table if we found repeatable fields that are still not converted

View File

@ -57,34 +57,34 @@ class ComponentbuilderModelJoomla_components extends JModelList
} }
public $user; public $user;
public $packagePath = false; public $packagePath = false;
public $packageName = false; public $packageName = false;
public $zipPath = false; public $zipPath = false;
public $key = array(); public $key = array();
public $exportBuyLinks = array(); public $exportBuyLinks = array();
public $exportPackageLinks = array(); public $exportPackageLinks = array();
public $info = array( public $info = array(
'name' => array(), 'name' => array(),
'short_description' => array(), 'short_description' => array(),
'component_version' => array(), 'component_version' => array(),
'companyname' => array(), 'companyname' => array(),
'author' => array(), 'author' => array(),
'email' => array(), 'email' => array(),
'website' => array(), 'website' => array(),
'license' => array(), 'license' => array(),
'copyright' => array(), 'copyright' => array(),
'getKeyFrom' => null 'getKeyFrom' => null
); );
public $activeType = 'export'; public $activeType = 'export';
protected $params; protected $params;
protected $tempPath; protected $tempPath;
protected $customPath; protected $customPath;
protected $smartExport = array(); protected $smartExport = array();
protected $exportIDs = array(); protected $exportIDs = array();
protected $customCodeM = array(); protected $customCodeM = array();
protected $fieldTypes = array(); protected $fieldTypes = array();
protected $isMultiple = array(); protected $isMultiple = array();
/** /**
* Method to build the export package * Method to build the export package
@ -207,15 +207,15 @@ class ComponentbuilderModelJoomla_components extends JModelList
continue; continue;
} }
// build information data set // build information data set
$this->info['name'][$item->id] = $item->name; $this->info['name'][$item->id] = $item->name;
$this->info['short_description'][$item->id] = $item->short_description; $this->info['short_description'][$item->id] = $item->short_description;
$this->info['component_version'][$item->id] = $item->component_version; $this->info['component_version'][$item->id] = $item->component_version;
$this->info['companyname'][$item->id] = $item->companyname; $this->info['companyname'][$item->id] = $item->companyname;
$this->info['author'][$item->id] = $item->author; $this->info['author'][$item->id] = $item->author;
$this->info['email'][$item->id] = $item->email; $this->info['email'][$item->id] = $item->email;
$this->info['website'][$item->id] = $item->website; $this->info['website'][$item->id] = $item->website;
$this->info['license'][$item->id] = $item->license; $this->info['license'][$item->id] = $item->license;
$this->info['copyright'][$item->id] = $item->copyright; $this->info['copyright'][$item->id] = $item->copyright;
// set the keys // set the keys
if (isset($item->export_key) && ComponentbuilderHelper::checkString($item->export_key)) if (isset($item->export_key) && ComponentbuilderHelper::checkString($item->export_key))
{ {
@ -500,6 +500,38 @@ class ComponentbuilderModelJoomla_components extends JModelList
{ {
continue; continue;
} }
// actions to take before storing the item if table is template, layout, site_view, or custom_admin_view
if ('layout' === $table || 'template' === $table || 'site_view' === $table || 'custom_admin_view' === $table)
{
// unset snippets (we no longer export snippets)
if (isset($item->snippet))
{
unset($item->snippet);
}
}
// actions to take before storing the item if table is joomla_component
if ('joomla_component' === $table)
{
// make sure old fields are not exported any more
unset($item->addconfig);
unset($item->addadmin_views);
unset($item->addcustom_admin_views);
unset($item->addsite_views);
unset($item->version_update);
unset($item->sql_tweak);
unset($item->addcustommenus);
unset($item->dashboard_tab);
unset($item->php_dashboard_methods);
unset($item->addfiles);
unset($item->addfolders);
}
// actions to take before storing the item if table is admin_view
if ('admin_view' === $table)
{
// make sure old fields are not exported any more
unset($item->addfields);
unset($item->addconditions);
}
// load to global object // load to global object
$this->smartExport[$table][$item->id] = $item; $this->smartExport[$table][$item->id] = $item;
// set the custom code ID's // set the custom code ID's
@ -645,12 +677,12 @@ class ComponentbuilderModelJoomla_components extends JModelList
$data = $locker->encryptString($data); $data = $locker->encryptString($data);
// Set the key owner information // Set the key owner information
$this->info['getKeyFrom'] = array(); $this->info['getKeyFrom'] = array();
$this->info['getKeyFrom']['company'] = $this->params->get('export_company', null); $this->info['getKeyFrom']['company'] = $this->params->get('export_company', null);
$this->info['getKeyFrom']['owner'] = $this->params->get('export_owner', null); $this->info['getKeyFrom']['owner'] = $this->params->get('export_owner', null);
$this->info['getKeyFrom']['email'] = $this->params->get('export_email', null); $this->info['getKeyFrom']['email'] = $this->params->get('export_email', null);
$this->info['getKeyFrom']['website'] = $this->params->get('export_website', null); $this->info['getKeyFrom']['website'] = $this->params->get('export_website', null);
$this->info['getKeyFrom']['license'] = $this->params->get('export_license', null); $this->info['getKeyFrom']['license'] = $this->params->get('export_license', null);
$this->info['getKeyFrom']['copyright'] = $this->params->get('export_copyright', null); $this->info['getKeyFrom']['copyright'] = $this->params->get('export_copyright', null);
// making provision for future changes // making provision for future changes
if (count($this->exportBuyLinks) == 1) if (count($this->exportBuyLinks) == 1)
{ {

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="3.2" method="upgrade"> <extension type="component" version="3.2" method="upgrade">
<name>COM_COMPONENTBUILDER</name> <name>COM_COMPONENTBUILDER</name>
<creationDate>22nd December, 2017</creationDate> <creationDate>25th December, 2017</creationDate>
<author>Llewellyn van der Merwe</author> <author>Llewellyn van der Merwe</author>
<authorEmail>joomla@vdm.io</authorEmail> <authorEmail>joomla@vdm.io</authorEmail>
<authorUrl>http://vdm.bz/component-builder</authorUrl> <authorUrl>http://vdm.bz/component-builder</authorUrl>