Added download link for custom file fields.

This commit is contained in:
Jaap Jansma 2020-06-09 11:59:37 +02:00
parent 999345f4b5
commit 18c71c6520
8 changed files with 293 additions and 28 deletions

View File

@ -0,0 +1,26 @@
<?php
/**
* @author Jaap Jansma <jaap.jansma@civicoop.org>
* @license AGPL-3.0
*/
namespace Civi\CiviProxy;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
class CompilerPass implements CompilerPassInterface {
/**
* You can modify the container here before it is dumped to PHP code.
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('data_processor_factory')) {
return;
}
$factoryDefinition = $container->getDefinition('data_processor_factory');
$factoryDefinition->addMethodCall('addOutputHandler', array('civiproxy_file_field', 'Civi\CiviProxy\DataProcessor\FileFieldOutputHandler', ts('CiviProxy File download link')));
}
}

View File

@ -0,0 +1,184 @@
<?php
/**
* @author Jaap Jansma <jaap.jansma@civicoop.org>
* @license AGPL-3.0
*/
namespace Civi\CiviProxy\DataProcessor;
use Civi\DataProcessor\FieldOutputHandler\AbstractFieldOutputHandler;
use Civi\DataProcessor\Source\SourceInterface;
use Civi\DataProcessor\DataSpecification\FieldSpecification;
use Civi\DataProcessor\FieldOutputHandler\FieldOutput;
use Civi\DataProcessor\Exception\DataSourceNotFoundException;
use Civi\DataProcessor\Exception\FieldNotFoundException;
class FileFieldOutputHandler extends AbstractFieldOutputHandler {
/**
* Returns the data type of this field
*
* @return String
*/
protected function getType() {
return 'String';
}
/**
* Returns the formatted value
*
* @param $rawRecord
* @param $formattedRecord
*
* @return \Civi\DataProcessor\FieldOutputHandler\FieldOutput
*/
public function formatField($rawRecord, $formattedRecord) {
$rawValue = $rawRecord[$this->inputFieldSpec->alias];
$output = new FieldOutput($rawValue);
if ($rawValue) {
$proxy_base = \CRM_Core_BAO_Setting::getItem('CiviProxy Settings', 'proxy_url');
$attachment = civicrm_api3('Attachment', 'getsingle', array('id' => $rawValue));
if (!isset($attachment['is_error']) || $attachment['is_error'] == '0') {
$fcs = \CRM_Core_BAO_File::generateFileHash($attachment['entity_id'], $attachment['id']);
$output->formattedValue = $proxy_base.'/file.php?id='.$attachment['id'].'&eid='.$attachment['entity_id'].'&fcs='.$fcs;
}
}
return $output;
}
/**
* Callback function for determining whether this field could be handled by this output handler.
*
* @param \Civi\DataProcessor\DataSpecification\FieldSpecification $field
* @return bool
*/
public function isFieldValid(FieldSpecification $field) {
if ($field->type == 'File') {
return true;
}
return false;
}
/**
* @var \Civi\DataProcessor\DataSpecification\FieldSpecification
*/
protected $inputFieldSpec;
/**
* @var \Civi\DataProcessor\DataSpecification\FieldSpecification
*/
protected $outputFieldSpec;
/**
* @var SourceInterface
*/
protected $dataSource;
/**
* @return \Civi\DataProcessor\DataSpecification\FieldSpecification
*/
public function getOutputFieldSpecification() {
return $this->outputFieldSpec;
}
/**
* Initialize the processor
*
* @param String $alias
* @param String $title
* @param array $configuration
* @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $processorType
*/
public function initialize($alias, $title, $configuration) {
$this->dataSource = $this->dataProcessor->getDataSourceByName($configuration['datasource']);
if (!$this->dataSource) {
throw new DataSourceNotFoundException(ts("Field %1 requires data source '%2' which could not be found. Did you rename or deleted the data source?", array(1=>$title, 2=>$configuration['datasource'])));
}
$this->inputFieldSpec = $this->dataSource->getAvailableFields()->getFieldSpecificationByName($configuration['field']);
if (!$this->inputFieldSpec) {
throw new FieldNotFoundException(ts("Field %1 requires a field with the name '%2' in the data source '%3'. Did you change the data source type?", array(
1 => $title,
2 => $configuration['field'],
3 => $configuration['datasource']
)));
}
$this->dataSource->ensureFieldInSource($this->inputFieldSpec);
$this->outputFieldSpec = clone $this->inputFieldSpec;
$this->outputFieldSpec->alias = $alias;
$this->outputFieldSpec->title = $title;
}
/**
* Returns true when this handler has additional configuration.
*
* @return bool
*/
public function hasConfiguration() {
return true;
}
/**
* When this handler has additional configuration you can add
* the fields on the form with this function.
*
* @param \CRM_Core_Form $form
* @param array $field
*/
public function buildConfigurationForm(\CRM_Core_Form $form, $field=array()) {
$fieldSelect = $this->getFieldOptions($field['data_processor_id']);
$form->add('select', 'field', ts('Field'), $fieldSelect, true, array(
'style' => 'min-width:250px',
'class' => 'crm-select2 huge data-processor-field-for-name',
'placeholder' => ts('- select -'),
));
if (isset($field['configuration'])) {
$configuration = $field['configuration'];
$defaults = array();
if (isset($configuration['field']) && isset($configuration['datasource'])) {
$defaults['field'] = $configuration['datasource'] . '::' . $configuration['field'];
}
$form->setDefaults($defaults);
}
}
/**
* When this handler has configuration specify the template file name
* for the configuration form.
*
* @return false|string
*/
public function getConfigurationTemplateFileName() {
return "CRM/Dataprocessor/Form/Field/Configuration/RawFieldOutputHandler.tpl";
}
/**
* Process the submitted values and create a configuration array
*
* @param $submittedValues
* @return array
*/
public function processConfiguration($submittedValues) {
list($datasource, $field) = explode('::', $submittedValues['field'], 2);
$configuration['field'] = $field;
$configuration['datasource'] = $datasource;
return $configuration;
}
/**
* Returns all possible fields
*
* @param $data_processor_id
*
* @return array
* @throws \Exception
*/
protected function getFieldOptions($data_processor_id) {
$fieldSelect = \CRM_Dataprocessor_Utils_DataSourceFields::getAvailableFieldsInDataSources($data_processor_id, array($this, 'isFieldValid'));
return $fieldSelect;
}
}

View File

@ -10,6 +10,8 @@
require_once 'civiproxy.civix.php';
use \Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* We will provide our own Mailer (wrapping the original one).
* so we can mend all the URLs in outgoing emails
@ -18,6 +20,15 @@ function civiproxy_civicrm_alterMailer(&$mailer, $driver, $params) {
$mailer = new CRM_Civiproxy_Mailer($mailer);
}
/**
* Implements hook_civicrm_container()
*
* @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_container/
*/
function civiproxy_civicrm_container(ContainerBuilder $container) {
$container->addCompilerPass(new Civi\CiviProxy\CompilerPass());
}
/**
* Implementation of hook_civicrm_config
*/

View File

@ -19,4 +19,7 @@
<civix>
<namespace>CRM/Civiproxy</namespace>
</civix>
<classloader>
<psr4 prefix="Civi\" path="Civi" />
</classloader>
</extension>

View File

@ -50,7 +50,7 @@ First thing you need to configure is the base URL of your CiviProxy server using
```
!!! note
This guide assumes a Drupal7 target CiviCRM with clean URLs enabled. If this is not the case for you, you might have to adjust the URLs and/or encounter issues. If so, please report on GitHub!
This guide assumes a Drupal7 target CiviCRM with clean URLs enabled. If this is not the case for you, you might have to adjust the URLs and/or encounter issues. If so, please report on GitHub!
### Configuring the link to the secure target CiviCRM
@ -75,12 +75,16 @@ $target_open = $target_civicrm . '/sites/all/modules/civicrm/extern/open.ph
```
If you set it to the value NULL this functionality will not be available on your CiviProxy server.
### Setting for the location of images and included files in your mail(ing)
CiviCRM stores images and attachments you include in your (bulk) mail in a specific folder. In CiviProxy the name of this folder is stored in variable `$target_file` in the `config.php` file:
CiviCRM stores images and attachments you include in your (bulk) mail in a specific folder. In CiviProxy the name of this folder is stored in variable `$target_static_file` in the `config.php` file:
```php
$target_file = $target_civicrm . '/sites/default/files/civicrm/persist/';
$target_download_file = $target_civicrm . '/civicrm/file';
$target_static_file = $target_civicrm . '/sites/default/files/civicrm/persist/';
```
If you set it to the value NULL this functionality will not be available on your CiviProxy server.
The `$target_download_file` is used for downloading files from custom file fields (or the contact image).
The `$target_static_file` is used for downloading images in mailings.
!!! note
By default CiviProxy will cache the files so it does not have to file from CiviCRM for each individual mail that is part of a bulk mailing. The default settings can be found in the `config.php` file:
```php

View File

@ -35,7 +35,8 @@ $target_civicrm = 'https://your.civicrm.installation.org';
$target_rest = $target_civicrm . '/sites/all/modules/civicrm/extern/rest.php';
$target_url = $target_civicrm . '/sites/all/modules/civicrm/extern/url.php';
$target_open = $target_civicrm . '/sites/all/modules/civicrm/extern/open.php';
$target_file = $target_civicrm . '/sites/default/files/civicrm/persist/';
$target_download_file = $target_civicrm . '/civicrm/file';
$target_static_file = $target_civicrm . '/sites/default/files/civicrm/persist/';
$target_mosaico = NULL; // (disabled by default): $target_civicrm . '/civicrm/mosaico/img?src=';
$target_mail_view = $target_civicrm . '/civicrm/mailing/view';
@ -155,4 +156,4 @@ $_webhook2api = [
"parameter_sanitation" => [],
]
]
];
];

View File

@ -10,38 +10,51 @@
require_once "config.php";
require_once "proxy.php";
// see if file caching is enabled
if (!$target_file) civiproxy_http_error("Feature disabled", 405);
// basic check
civiproxy_security_check('file');
// basic restraints
$valid_parameters = array( 'id' => 'string' );
$valid_parameters = array(
'id' => 'string',
'eid' => 'int',
'fcs' => 'string'
);
$parameters = civiproxy_get_parameters($valid_parameters);
// check if id specified
if (empty($parameters['id'])) civiproxy_http_error("Resource not found");
// check restrictions
if (!empty($file_cache_exclude)) {
foreach ($file_cache_exclude as $pattern) {
if (preg_match($pattern, $parameters['id'])) {
$static_file = true;
if (isset($parameters['eid']) && isset($parameters['fcs'])) {
$static_file = false;
// see if file caching is enabled
if (!$target_download_file) civiproxy_http_error("Feature disabled", 405);
} else {
// see if file caching is enabled
if (!$target_static_file && isset($target_file)) {
$target_static_file = $target_file; // Backwards compatibility.
}
if (!$target_static_file) civiproxy_http_error("Feature disabled", 405);
// check restrictions
if (!empty($file_cache_exclude)) {
foreach ($file_cache_exclude as $pattern) {
if (preg_match($pattern, $parameters['id'])) {
civiproxy_http_error("Invalid Resource", 403);
}
}
}
if (!empty($file_cache_include)) {
$accept_id = FALSE;
foreach ($file_cache_include as $pattern) {
if (preg_match($pattern, $parameters['id'])) {
$accept_id = TRUE;
}
}
if (!$accept_id) {
civiproxy_http_error("Invalid Resource", 403);
}
}
}
if (!empty($file_cache_include)) {
$accept_id = FALSE;
foreach ($file_cache_include as $pattern) {
if (preg_match($pattern, $parameters['id'])) {
$accept_id = TRUE;
}
}
if (!$accept_id) {
civiproxy_http_error("Invalid Resource", 403);
}
}
// load PEAR file cache
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . 'libs');
@ -52,6 +65,10 @@ $file_cache = new Cache_Lite($file_cache_options);
// look up the required resource
$header_key = 'header&' . $parameters['id'];
$data_key = 'data&' . $parameters['id'];
if (!$static_file) {
$header_key .= '&eid='.$parameters['eid'];
$data_key .= '&eid='.$parameters['eid'];
}
$header = $file_cache->get($header_key);
$data = $file_cache->get($data_key);
@ -68,7 +85,10 @@ if ($header && $data) {
}
// if we get here, we have a cache miss => load
$url = $target_file . $parameters['id'];
$url = $target_static_file . $parameters['id'];
if (!$static_file) {
$url = $target_download_file .'?reset=1&id='.$parameters['id'].'&eid='.$parameters['eid'].'&fcs='.$parameters['fcs'];
}
// error_log("CACHE MISS. LOADING $url");
$curlSession = curl_init();
@ -100,12 +120,28 @@ $body = $content[1];
// extract headers
$header_lines = explode(chr(10), $header);
// Check whether the Content-Disposition header is available but only when it is
// a dynamic file.
$content_disposition_header_present = FALSE;
foreach ($header_lines as $header_line) {
if (stripos($header_line, 'Content-Disposition')===0) {
$content_disposition_header_present = TRUE;
}
}
if (!$static_file && !$content_disposition_header_present) {
// check whether the content disposition header is available.
// If not we are dealing with an invalid file.
// And CiviCRM does a redirect to the login page however we
// dont want to expose that through CiviProxy so we will return an error message instead.
civiproxy_http_error("Invalid Resource", 403);
}
// store the information in the cache
$file_cache->save(json_encode($header_lines), $header_key);
$file_cache->save($body, $data_key);
// and reply
$content_disposition_header_present = FALSE;
foreach ($header_lines as $header_line) {
header($header_line);
}

View File

@ -96,7 +96,7 @@ function civiproxy_redirect($url_requested, $parameters) {
* so they will point to this proxy instead
*/
function civiproxy_mend_URLs(&$string) {
global $target_rest, $target_url, $target_open, $target_file, $target_mail, $proxy_base, $target_mosaico, $target_civicrm;
global $target_rest, $target_url, $target_open, $target_static_file, $target_mail, $proxy_base, $target_mosaico, $target_civicrm;
if ($target_rest) {
$string = preg_replace("#{$target_rest}#", $proxy_base . '/rest.php', $string);
@ -110,8 +110,8 @@ function civiproxy_mend_URLs(&$string) {
if ($target_mail) {
$string = preg_replace("#{$target_mail}#", $proxy_base . '/mail.php', $string);
}
if ($target_file) {
$string = preg_replace("#{$target_file}#", $proxy_base . '/file.php?id=', $string);
if ($target_static_file) {
$string = preg_replace("#{$target_static_file}#", $proxy_base . '/file.php?id=', $string);
// https://github.com/systopia/CiviProxy/issues/38
// fix for relative
if ($target_mosaico) {