diff --git a/Makefile b/Makefile index 6bf7f4420ce53ae40a56d5eaf736b0001af14357..6fe9424be6b5efd89b11366aedf941f5eda6e06b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BASE=downloads_for_sale PLUGINTYPE=vmcustom -VERSION=1.8 +VERSION=1.8.1 PLUGINFILES=$(BASE).php $(BASE).script.php $(BASE).xml index.html diff --git a/downloads_for_sale.php b/downloads_for_sale.php index c5313cd65448936036903e20f4f3614a3feb52ad..cf68f01960b82ecf44e68ed185c444502d55584a 100644 --- a/downloads_for_sale.php +++ b/downloads_for_sale.php @@ -252,6 +252,7 @@ class plgVmCustomDownloads_for_Sale extends vmCustomPlugin { $handled = false; $field_id = vRequest::getInt('customfield_id',0); $customModel = VmModel::getModel('customfields'); + if ($field_id) { if (!defined('VM_VERSION') or VM_VERSION < 3) { @@ -259,10 +260,19 @@ class plgVmCustomDownloads_for_Sale extends vmCustomPlugin { } else { $field = $customModel->getCustomEmbeddedProductCustomField($field_id); } + if ($field) { $orderitems = $this->getCustomfieldOrderitems ($field, JFactory::getUser()->get('id'), true); $orderitem = empty($orderitems)?null:$orderitems[0]; - if ($this->checkDownloadable ($field, $orderitem, true)) { + $downloadable = $this->checkDownloadable ($field, $orderitem, true); + if (vRequest::getInt('check_access', 0)==1) { + if ($downloadable) { + header("HTTP/1.1 200 ".JText::_('VMCUSTOM_DLSALE_ACCESS_CHECK_SUCCESS')); + } else { + header("HTTP/1.1 403 ".JText::_('VMCUSTOM_DLSALE_ACCESS_CHECK_ERROR')); + } + JExit(); + } elseif ($downloadable) { $handled = true; if ($this->downloadFile ($field->media_id, $render)) { // Successful download, so set order status to shipped, then exit to prevent the normal joomla page formatting (we sent a potentially binary file already!) @@ -276,6 +286,10 @@ class plgVmCustomDownloads_for_Sale extends vmCustomPlugin { JFactory::getApplication()->enqueueMessage(JText::_('VMCUSTOM_DLSALE_ERROR_NOT_AUTHORIZED_UNKNOWN'), 'error'); } } else { + if (vRequest::getInt('check_access', 0)==1) { + header("HTTP/1.1 403 ".JText::_('VMCUSTOM_DLSALE_ACCESS_CHECK_ERROR')); + JExit(); + } JFactory::getApplication()->enqueueMessage(JText::_('VMCUSTOM_DLSALE_ERROR_LOAD_FAILURE'), 'error'); } } @@ -355,17 +369,6 @@ class plgVmCustomDownloads_for_Sale extends vmCustomPlugin { } } - /** - * plgVmOnSelfCallBE ... Called to execute some plugin action in the backend (e.g. set/reset dl counter, show statistics etc.) - */ -/* function plgVmOnSelfCallBE($type, $name, &$output) { - if ($name != $this->_name || $type != 'vmcustom') return false; - vmDebug('plgVmOnSelfCallBE'); - - $db = JFactory::getDBO(); - $nullDate = $db->getNullDate(); - }*/ - /** * @see Form displayed in the product edit page in the BE, configure the download file @@ -667,6 +670,203 @@ class plgVmCustomDownloads_for_Sale extends vmCustomPlugin { return $SQLfields; } + /** + * plgVmOnSelfCallBE ... Called to execute some plugin action in the backend (e.g. set/reset dl counter, show statistics etc.) + */ + function plgVmOnSelfCallBE($type, $name, &$output) { + if ($name != $this->_name || $type != $this->_type) return false; + vmDebug('plgVmOnSelfCallBE'); + $user = JFactory::getUser(); + $authorized = ($user->authorise('core.admin','com_virtuemart') or + $user->authorise('core.manage','com_virtuemart')); + $json = array(); + $json['authorized'] = $authorized; + if (!$authorized) return FALSE; + + $action = vRequest::getCmd('action'); + $json['action'] = $action; + $json['success'] = 0; // default: unsuccessfull + switch ($action) { + case "check_update_access": + $order_number = vRequest::getString('order_number'); + $order_pass = vRequest::getString('order_pass'); + $json = $this->checkUpdateAccess($order_number, $order_pass, $json); + break; + } + + // Also return all messages (in HTML format!): + // Since we are in a JSON document, we have to temporarily switch the type + // to HTML to make sure the html renderer is actually used + $document = JFactory::getDocument (); + $previoustype = $document->getType(); + $document->setType('html'); + $msgrenderer = $document->loadRenderer('message'); + $json['messages'] = $msgrenderer->render('Message'); + $document->setType($previoustype); + + // WORKAROUND for broken (i.e. duplicate) content-disposition headers in Joomla 2.x: + // We request everything in raw and here send the headers for JSON and return + // the raw output in json format + $document =JFactory::getDocument(); + $document->setMimeEncoding('application/json'); + JResponse::setHeader('Content-Disposition','attachment;filename="opentools_update_access.json"'); + $output = json_encode($json); + } + + protected function checkUpdateAccess($order_number, $order_pass, $json = array()) { + // First, extract the update server URL from the manifest, then load + // the update XML from the update server, extract the download URL, + // append the order number and password and check whether access is + // possible. + $json['success'] = FALSE; + $xml = simplexml_load_file($this->_xmlFile); + if (!$xml || !isset($xml->updateservers)) { + JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_XMLMANIFEST_ERROR', $this->_xmlFile), 'error'); + return $json; + } + $updateservers = $xml->updateservers; + foreach ($updateservers->children() as $server) { + if ($server->getName()!='server') { + JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_XMLMANIFEST_ERROR', $this->_xmlFile), 'error'); + continue; + } + $updateurl = html_entity_decode((string)$server); + $updatescript = simplexml_load_file($updateurl); + if (!$updatescript) { + JFactory::getApplication()->enqueueMessage(JText::sprintf('OPENTOOLS_UPDATESCRIPT_ERROR', $updateurl), 'error'); + continue; + } + $urls = $updatescript->xpath('/updates/update/downloads/downloadurl'); + while (list( , $node) = each($urls)) { + $downloadurl = (string)($node); + if ($order_number) { + $downloadurl .= (parse_url($downloadurl, PHP_URL_QUERY) ? '&' : '?') . 'order_number=' . urlencode($order_number); + } + if ($order_pass) { + $downloadurl .= (parse_url($downloadurl, PHP_URL_QUERY) ? '&' : '?') . 'order_pass=' . urlencode($order_pass); + } + $downloadurl .= (parse_url($downloadurl, PHP_URL_QUERY) ? '&' : '?') . 'check_access=1'; + + $headers = get_headers($downloadurl); + list($version, $status_code, $msg) = explode(' ',$headers[0], 3); + + // Check the HTTP Status code + switch($status_code) { + case 200: + $json['success'] = TRUE; + JFactory::getApplication()->enqueueMessage($msg, 'message'); + $this->setupUpdateCredentials($order_number, $order_pass); + break; + default: + JFactory::getApplication()->enqueueMessage($msg, 'error'); + // Clear the credentials... + $this->setupUpdateCredentials("", ""); + break; + } + $this->setAndSaveParams(array( + 'update_credentials_checked'=>$json['success'], + 'order_number' => $order_number, + 'order_pass' => $order_pass, + )); + } + } + return $json; + } + + protected function setAndSaveParams($params) { + $db = JFactory::getDbo(); + $query = $db->getQuery(true) + ->select('extension_id') + ->from('#__extensions') + ->where('folder = '.$db->quote($this->_type)) + ->where('element = '.$db->quote($this->_name)) + ->where('type =' . $db->quote('plugin')) + ->order('ordering'); + + $plugin = $db->setQuery($query)->loadObject(); + if (!$plugin) + return; + $pluginId=$plugin->extension_id; + + foreach ($params as $param=>$parvalue) { + $this->params->set($param, $parvalue); + } + + $extensions = JTable::getInstance('extension'); + $extensions->load($pluginId); + $extensions->bind(array('params' => $this->params->toString())); + + // check and store + if (!$extensions->check()) { + $this->setError($extensions->getError()); + return false; + } + if (!$extensions->store()) { + $this->setError($extensions->getError()); + return false; + } + } + + + protected function setupUpdateCredentials($ordernumber, $orderpass) { + $db = JFactory::getDbo(); + $query = $db->getQuery(true) + ->select('extension_id AS id') + ->from('#__extensions') + ->where('folder = '.$db->quote($this->_type)) + ->where('element = '.$db->quote($this->_name)) + ->where('type =' . $db->quote('plugin')) + ->order('ordering'); + + $plugin = $db->setQuery($query)->loadObject(); + if (empty($plugin)) + return; + + $ordernumber = preg_replace("/[^-A-Za-z0-9_]/", '', $ordernumber); + $orderpass = preg_replace("/[^-A-Za-z0-9_]/", '', $orderpass); + + $extra_query = array(); + if ($ordernumber!='') { + $extra_query[] = 'order_number='.preg_replace("/[^-A-Za-z0-9_]/", '', $ordernumber); + } + if ($orderpass!='') { + $extra_query[] = 'order_pass='.preg_replace("/[^-A-Za-z0-9_]/", '', $orderpass); + } + $extra_query = implode('&', $extra_query); + + // The following code is based on Nicholas K. Dionysopoulos' Joomla Pull request: + // https://github.com/joomla/joomla-cms/pull/2508 + + // Load the update site record, if it exists + $db = JFactory::getDbo(); + $query = $db->getQuery(true) + ->select('update_site_id AS id') + ->from($db->qn('#__update_sites_extensions')) + ->where($db->qn('extension_id').' = '.$db->q($plugin->id)); + $db->setQuery($query); + $updateSites = $db->loadObjectList(); + + foreach ($updateSites as $updateSite) { + // Update the update site record + $query = $db->getQuery(true) + ->update($db->qn('#__update_sites')) + ->set('extra_query = '.$db->q($extra_query)) + ->set('last_check_timestamp = 0') + ->where($db->qn('update_site_id').' = '.$db->q($updateSite->id)); + $db->setQuery($query); + $db->execute(); + + // Delete any existing updates (essentially flushes the updates cache for this update site) + $query = $db->getQuery(true) + ->delete($db->qn('#__updates')) + ->where($db->qn('update_site_id').' = '.$db->q($updateSite->id)); + $db->setQuery($query); + $db->execute(); + } + + } + + } // No closing tag \ No newline at end of file diff --git a/downloads_for_sale.xml b/downloads_for_sale.xml index 35418b1da91acdca15feeb20895da2ade1672f4b..467f0cb7bc134718d9e9dc69f2863f9b36cb8c00 100644 --- a/downloads_for_sale.xml +++ b/downloads_for_sale.xml @@ -6,7 +6,7 @@ <authorUrl>http://www.open-tools.net/</authorUrl> <copyright>Copyright (C) 2013 Reinhold Kainhofer. All rights reserved.</copyright> <license>http://www.gnu.org/licenses/gpl.html GNU/GPL v3+</license> - <version>1.8</version> + <version>1.8.1</version> <description>VMCUSTOM_DLSALE_DESC</description> <files> <filename plugin="downloads_for_sale">downloads_for_sale.php</filename> @@ -72,4 +72,19 @@ <param name="paid_status" type="vmorderstates" scope="com_virtuemart" default="C, S" multiple="multiple" label="VMCUSTOM_DLSALE_STATUS_PAID" description="VMCUSTOM_DLSALE_STATUS_PAID_EXPLAIN" /> <param name="downloaded_status" type="vmdlorderstate" scope="com_virtuemart" default="" label="VMCUSTOM_DLSALE_STATUS_DOWNLOADED" description="VMCUSTOM_DLSALE_STATUS_DOWNLOADED_EXPLAIN" /> </params> + + <config> + <fields name="params"> + <fieldset name="update_credentials" label="OPENTOOLS_FIELDSET_CREDENTIALS" addfieldpath="/plugins/vmcustom/downloads_for_sale/fields"> + <field name="credentials_desc" type="spacer" label="OPENTOOLS_CREDENTIALS_DESC" /> + <field name="order_number" type="text" default="" label="OPENTOOLS_ORDERNUMBER" description="OPENTOOLS_ORDERNUMBER_DESC"/> + <field name="order_pass" type="text" default="" label="OPENTOOLS_ORDERPASS" description="OPENTOOLS_ORDERPASS_DESC"/> + <field name="update_credentials_checked" type="vmUpdateCredentialsCheck" label="" ajaxurl="index.php?option=com_virtuemart&view=plugin&type=vmcustom&name=downloads_for_sale&format=raw" /> + </fieldset> + </fields> + </config> + <updateservers> + <server type="extension" name="VM Downloads for Sale Updates"><![CDATA[http://open-tools.net/UpdateServer/index.php?package=Joomla&extension=DownloadsForSale&file=extension.xml]]></server> + </updateservers> + </extension> diff --git a/fields/vmupdatecredentialscheck.php b/fields/vmupdatecredentialscheck.php new file mode 100644 index 0000000000000000000000000000000000000000..d08397b1a162d02b78ba2551a79aa2e43957a3e4 --- /dev/null +++ b/fields/vmupdatecredentialscheck.php @@ -0,0 +1,161 @@ +<?php +defined('_JEXEC') or die(); +/** + * + * @package VirtueMart + * @subpackage Plugins - Fields + * @author Reinhold Kainhofer, Open Tools + * @link http://www.open-tools.net + * @copyright Copyright (c) 2016 Reinhold Kainhofer. All rights reserved. + * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.txt + * VirtueMart is free software. This version may have been modified pursuant + * to the GNU General Public License, and as distributed it includes or + * is derivative of works licensed under the GNU General Public License or + * other free or open source software licenses. + */ + +defined('DS') or define('DS', DIRECTORY_SEPARATOR); +if (!class_exists( 'VmConfig' )) + require(JPATH_ROOT.DS.'administrator'.DS.'components'.DS.'com_virtuemart'.DS.'helpers'.DS.'config.php'); +VmConfig::loadConfig(); + +class JFormFieldVmUpdateCredentialsCheck extends JFormField { + var $_name = 'vmUpdateCredentialsCheck'; + + // VM2 on J2 works, VM3 on J3 works out of the box, but + // VM3 on J2 does NOT work by simply calling vmJsApi::jQuery, because + // the JS is never added to the page header, so we have to add this manually + public function loadjQuery() { + vmJsApi::jQuery(); + // TODO: jquery::ui available only in J3: + if (version_compare(JVERSION, '3.0', 'lt')) { + } else { + JHtml::_('jquery.ui', array('core', 'sortable')); + } + // If we are on Joomla 2.5 and VM 3, manually add the script declarations + // cached in vmJsApi to the document header: + if (version_compare(JVERSION, '3.0', 'lt') && defined('VM_VERSION') && VM_VERSION>=3) { + $document = JFactory::getDocument(); + $scripts = vmJsApi::getJScripts(); + foreach ($scripts as $name => $jsToAdd) { + if($jsToAdd['written']) continue; + $file = $jsToAdd['script'] ? $jsToAdd['script'] : $name; + + if(strpos($file,'/')!==0){ + $file = vmJsApi::setPath($file,false,''); + } else if(strpos($file,'//')!==0){ + $file = JURI::root(true).$file; + } + + $ver = ''; + if(!empty($jsToAdd['ver'])) $ver = '?vmver='.$jsToAdd['ver']; + $document->addScript( $file .$ver,"text/javascript",$jsToAdd['defer'],$jsToAdd['async'] ); + vmJsApi::removeJScript($name); + } + } + } + + protected function getJavaScript() { + return " +var credentials_ajaxurl = \"".$this->element["ajaxurl"]."\"; +var credentials_updateMessages = function(messages, area) { + jQuery( \"#system-message-container #system-message .\"+area+\"-message\").remove(); + var newmessages = jQuery( messages ).find(\"div.alert, .message\").addClass(area+\"-message\"); + if (!jQuery( \"#system-message-container #system-message\").length && newmessages.length) { + if (jQuery(newmessages).first().prop(\"tagName\")==\"dt\") { // Joomla 2.x: + jQuery( \"#system-message-container\" ).append( \"<dl id=\'system-message\'></div>\" ); + } else { + jQuery( \"#system-message-container\" ).append( \"<div id=\'system-message\'></div>\" ); + } + } + newmessages.appendTo( \"#system-message-container #system-message\"); +}; + +var checkUpdateCredentialsError = function() { + alert (\"".JText::_('OPENTOOLS_CHECK_CREDENTIALS_ERROR')."\"); +} + +var checkUpdateCredentials = function () { + var ordernumber = jQuery('#jform_params_order_number').val(); + var orderpass = jQuery('#jform_params_order_pass').val(); + + var ajaxargs = { + type: \"POST\", + dataType: \"text\", + url: credentials_ajaxurl, + data: { + action: \"check_update_access\", + order_number: ordernumber, + order_pass: orderpass + }, + + success: function ( json ) { + try { + json = jQuery.parseJSON(json); + credentials_updateMessages(json['messages'], 'ordernumber'); + } catch (e) { + checkUpdateCredentialsError(); + return; + } + var success=0; + if (json.success>0) { + success=1; + } + jQuery('#update_credentials_hidden_checked').val(success); + jQuery('.credentials_checked') + .removeClass('credentials_checked_0') + .removeClass('credentials_checked_1') + .addClass('credentials_checked_'+success); + }, + error: function() { checkUpdateCredentialsError(); }, + complete: function() { }, + }; + jQuery.ajax(ajaxargs); +}; +jQuery(document).ready (function () { + jQuery('#jform_params_order_number').focusout(checkUpdateCredentials); + jQuery('#jform_params_order_pass').focusout(checkUpdateCredentials); +}); + +"; + } + + protected function getCSS() { + return " +div.credentials_checked { + padding: 10px 5px; + float: left; + clear: left; + display: block; + width: 100%; +} +div.credentials_checked_0 { + background-color: #FFD0D0; +} +div.credentials_checked_1 { + background-color: #D0FFD0; +} +a#credentials_check { +} + +"; + } + protected function getInput() { + // Tell the user that automatic updates are not available in Joomla 2.5: + if (version_compare(JVERSION, '3.0', 'lt')) { + JFactory::getApplication()->enqueueMessage(JText::_('OPENTOOLS_COMMERCIAL_UPDATES_J25'), 'warning'); + } + + $this->loadjQuery(); + + $doc = JFactory::getDocument(); + $doc->addScriptDeclaration($this->getJavaScript()); + $doc->addStyleDeclaration($this->getCSS()); + + if ($this->value!=1) { + $this->value=0; + } +// if ($this->value==1) { + return "<input type='hidden' id=\"update_credentials_hidden_checked\" name='".$this->name."' value='".$this->value."' /><div class='credentials_checked credentials_checked_".$this->value."'><a href=\"#\" class=\"btn btn-info credentials_check\" id=\"credentials_check\" onclick=\"checkUpdateCredentials()\" >".JText::_('OPENTOOLS_CHECK_CREDENTIALS')."</a></div>"; + } +} diff --git a/language/de-DE/de-DE.plg_vmcustom_downloads_for_sale.ini b/language/de-DE/de-DE.plg_vmcustom_downloads_for_sale.ini index 8f4416ba5fbbc5ce56d20879db30a62d1c6fcc1f..993819b7eb0a7529d464c60f4778987a401bf35f 100644 --- a/language/de-DE/de-DE.plg_vmcustom_downloads_for_sale.ini +++ b/language/de-DE/de-DE.plg_vmcustom_downloads_for_sale.ini @@ -52,3 +52,18 @@ VMCUSTOM_DLSALE_MIMETYPE="MIME-Typ" VMCUSTOM_DLSALE_FILESIZE="Dateigröße" VMCUSTOM_DLSALE_FILESIZE_BYTES="Bytes" + +VMCUSTOM_DLSALE_ACCESS_CHECK_SUCCESS="Download-Zugang möglich." +VMCUSTOM_DLSALE_ACCESS_CHECK_ERROR="Download nicht möglich. Bitte überprüfen Sie Ihre Zugangsdaten." + +OPENTOOLS_FIELDSET_CREDENTIALS="Automatische Aktualisierung" +OPENTOOLS_CREDENTIALS_DESC="Bitte geben Sie hier die Bestellungsnummer und -passwort ein, die Sie in der Bestellbestätigung (per Mail) von <a href="https://www.open-tools.net/">open-tools.net</a> erhalten haben. Wird keine gültige Kombination eingegeben, ist die Funktionalität der Erweiterung in keiner Weise eingeschränkt, jedoch stehen keine automatischen Aktualisierungen zur Verfügung." +OPENTOOLS_ORDERNUMBER="Bestellungsnummer:" +OPENTOOLS_ORDERNUMBER_DESC="Bitte geben Sie hier die Bestellungsnummer Ihres Kaufs der Erweiterung auf open-tools.net an. Wenn keine gültige Kombination aus Bestellungsnummer und -passwort angegeben wird, funktioniert die Erweiterung dennoch, jedoch sind keine automatischen Aktualisierungen möglich." +OPENTOOLS_ORDERPASS="Bestellungspassword:" +OPENTOOLS_ORDERPASS_DESC="Bitte geben Sie hier das Bestellungspasswort Ihres Kaufs der Erweiterung auf open-tools.net an. Wenn keine gültige Kombination aus Bestellungsnummer und -passwort angegeben wird, funktioniert die Erweiterung dennoch, jedoch sind keine automatischen Aktualisierungen möglich." +OPENTOOLS_CHECK_CREDENTIALS="Zugangsdaten überprüfen und speichern" +OPENTOOLS_CHECK_CREDENTIALS_ERROR="Konnte Zugangsdaten nicht überprüfen. Bitte stellen Sie sicher, dass die Erweiterung in Joomla freigegeben ist!" +OPENTOOLS_XMLMANIFEST_ERROR="Konnte die XML-Manifest-Datei der Erweiterung nicht laden (%s)" +OPENTOOLS_UPDATESCRIPT_ERROR="Konnte die Aktualisierungsinformationen nicht laden (%s)" +OPENTOOLS_COMMERCIAL_UPDATES_J25="Automatische Aktualisierungen von kommerziellen Erweiterungen sind in Joomla 2.5 leider nicht möglich (erste ab Joomla 3.x). Bitte informieren Sie sich auf der Homepage des Entwicklers über mögliche Aktualisierungen und installieren Sie diese manuell." diff --git a/language/en-GB/en-GB.plg_vmcustom_downloads_for_sale.ini b/language/en-GB/en-GB.plg_vmcustom_downloads_for_sale.ini index 960ae15a83a71560ef2abaa580ba6ebc0d076daa..1faf68e20e68adba73139f95954fe901ba026b16 100644 --- a/language/en-GB/en-GB.plg_vmcustom_downloads_for_sale.ini +++ b/language/en-GB/en-GB.plg_vmcustom_downloads_for_sale.ini @@ -53,3 +53,18 @@ VMCUSTOM_DLSALE_MIMETYPE="MIME-Type" VMCUSTOM_DLSALE_FILESIZE="File size" VMCUSTOM_DLSALE_FILESIZE_BYTES="Bytes" + +VMCUSTOM_DLSALE_ACCESS_CHECK_SUCCESS="Access granted." +VMCUSTOM_DLSALE_ACCESS_CHECK_ERROR="Download not possible. Please check your access credentials." + +OPENTOOLS_FIELDSET_CREDENTIALS="Update credentials" +OPENTOOLS_CREDENTIALS_DESC="Please enter your order number and the order password, which can be found in the order confirmation mail you received after your purchase at <a href="https://www.open-tools.net/">open-tools.net</a>. If you do not enter a valid combination, the plugin will continue to work, but automatic updates will only be provided with a valid order number and password." +OPENTOOLS_ORDERNUMBER="Order number:" +OPENTOOLS_ORDERNUMBER_DESC="Please enter the order number of your purchase at open-tools.net to get access to automatic updates. If no valid order number and password is entered, the plugin will still work, but automatic updates will not be available." +OPENTOOLS_ORDERPASS="Order password:" +OPENTOOLS_ORDERPASS_DESC="Please enter the order password of your purchase at open-tools.net to get access to automatic updates. If no valid order number and password is entered, the plugin will still work, but automatic updates will not be available." +OPENTOOLS_CHECK_CREDENTIALS="Check and save update credentials" +OPENTOOLS_CHECK_CREDENTIALS_ERROR="Unable to check the download credentials. Please make sure that the plugin is enabled in your Joomla installation!" +OPENTOOLS_XMLMANIFEST_ERROR="Unable to load the plugin manifest file (%s)" +OPENTOOLS_UPDATESCRIPT_ERROR="Unable to load the update information from the update server (%s)" +OPENTOOLS_COMMERCIAL_UPDATES_J25="Automatic updates of commercial Joomla extensions are not available in Joomla 2.5 (only in Joomla 3.x). Please check the software developer's homepage and manually install updates." diff --git a/releases/plg_vmcustom_downloads_for_sale_v1.8.1.zip b/releases/plg_vmcustom_downloads_for_sale_v1.8.1.zip new file mode 100644 index 0000000000000000000000000000000000000000..5a2d2d3dd04a31217b339e9754560ac168ed055b Binary files /dev/null and b/releases/plg_vmcustom_downloads_for_sale_v1.8.1.zip differ