contrib modules security updates
This commit is contained in:
@@ -20,15 +20,20 @@ class FeedsCSVParser extends FeedsParser {
|
||||
// Load and configure parser.
|
||||
feeds_include_library('ParserCSV.inc', 'ParserCSV');
|
||||
$parser = new ParserCSV();
|
||||
$delimiter = $source_config['delimiter'] == 'TAB' ? "\t" : $source_config['delimiter'];
|
||||
$delimiter = $this->getDelimiterChar($source_config);
|
||||
$parser->setDelimiter($delimiter);
|
||||
if (isset($source_config['encoding'])) {
|
||||
// Encoding can only be set when the mbstring extension is loaded.
|
||||
$parser->setEncoding($source_config['encoding']);
|
||||
}
|
||||
|
||||
$iterator = new ParserCSVIterator($fetcher_result->getFilePath());
|
||||
if (empty($source_config['no_headers'])) {
|
||||
// Get first line and use it for column names, convert them to lower case.
|
||||
$header = $this->parseHeader($parser, $iterator);
|
||||
if (!$header) {
|
||||
return;
|
||||
drupal_set_message(t('The CSV file is empty.'), 'warning', FALSE);
|
||||
return new FeedsParserResult();
|
||||
}
|
||||
$parser->setColumnNames($header);
|
||||
}
|
||||
@@ -100,12 +105,20 @@ class FeedsCSVParser extends FeedsParser {
|
||||
return parent::getSourceElement($source, $result, drupal_strtolower($element_key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::getMappingSourceList() to use only lower keys.
|
||||
*/
|
||||
public function getMappingSourceList() {
|
||||
return array_map('drupal_strtolower', parent::getMappingSourceList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Define defaults.
|
||||
*/
|
||||
public function sourceDefaults() {
|
||||
return array(
|
||||
'delimiter' => $this->config['delimiter'],
|
||||
'encoding' => $this->config['encoding'],
|
||||
'no_headers' => $this->config['no_headers'],
|
||||
);
|
||||
}
|
||||
@@ -122,26 +135,40 @@ class FeedsCSVParser extends FeedsParser {
|
||||
$mappings = feeds_importer($this->id)->processor->config['mappings'];
|
||||
$sources = $uniques = array();
|
||||
foreach ($mappings as $mapping) {
|
||||
$sources[] = check_plain($mapping['source']);
|
||||
if ($mapping['unique']) {
|
||||
$uniques[] = check_plain($mapping['source']);
|
||||
if (strpos($mapping['source'], ',') !== FALSE) {
|
||||
$sources[] = '"' . $mapping['source'] . '"';
|
||||
}
|
||||
else {
|
||||
$sources[] = $mapping['source'];
|
||||
}
|
||||
if (!empty($mapping['unique'])) {
|
||||
$uniques[] = $mapping['source'];
|
||||
}
|
||||
}
|
||||
$sources = array_unique($sources);
|
||||
|
||||
$output = t('Import !csv_files with one or more of these columns: !columns.', array('!csv_files' => l(t('CSV files'), 'http://en.wikipedia.org/wiki/Comma-separated_values'), '!columns' => implode(', ', $sources)));
|
||||
$output = t('Import !csv_files with one or more of these columns: @columns.', array('!csv_files' => l(t('CSV files'), 'http://en.wikipedia.org/wiki/Comma-separated_values'), '@columns' => implode(', ', $sources)));
|
||||
$items = array();
|
||||
$items[] = format_plural(count($uniques), t('Column <strong>!column</strong> is mandatory and considered unique: only one item per !column value will be created.', array('!column' => implode(', ', $uniques))), t('Columns <strong>!columns</strong> are mandatory and values in these columns are considered unique: only one entry per value in one of these column will be created.', array('!columns' => implode(', ', $uniques))));
|
||||
$items[] = format_plural(count($uniques), 'Column <strong>@columns</strong> is mandatory and considered unique: only one item per @columns value will be created.', 'Columns <strong>@columns</strong> are mandatory and values in these columns are considered unique: only one entry per value in one of these column will be created.', array('@columns' => implode(', ', $uniques)));
|
||||
$items[] = l(t('Download a template'), 'import/' . $this->id . '/template');
|
||||
$form['help']['#markup'] = '<div class="help"><p>' . $output . '</p>' . theme('item_list', array('items' => $items)) . '</div>';
|
||||
$form['help'] = array(
|
||||
'#prefix' => '<div class="help">',
|
||||
'#suffix' => '</div>',
|
||||
'description' => array(
|
||||
'#prefix' => '<p>',
|
||||
'#markup' => $output,
|
||||
'#suffix' => '</p>',
|
||||
),
|
||||
'list' => array(
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
),
|
||||
);
|
||||
$form['delimiter'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Delimiter'),
|
||||
'#description' => t('The character that delimits fields in the CSV file.'),
|
||||
'#options' => array(
|
||||
',' => ',',
|
||||
';' => ';',
|
||||
'TAB' => 'TAB',
|
||||
),
|
||||
'#options' => $this->getAllDelimiterTypes(),
|
||||
'#default_value' => isset($source_config['delimiter']) ? $source_config['delimiter'] : ',',
|
||||
);
|
||||
$form['no_headers'] = array(
|
||||
@@ -150,6 +177,10 @@ class FeedsCSVParser extends FeedsParser {
|
||||
'#description' => t('Check if the imported CSV file does not start with a header row. If checked, mapping sources must be named \'0\', \'1\', \'2\' etc.'),
|
||||
'#default_value' => isset($source_config['no_headers']) ? $source_config['no_headers'] : 0,
|
||||
);
|
||||
$form['encoding'] = $this->configEncodingForm();
|
||||
if (isset($source_config['encoding'])) {
|
||||
$form['encoding']['#default_value'] = $source_config['encoding'];
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -159,6 +190,7 @@ class FeedsCSVParser extends FeedsParser {
|
||||
public function configDefaults() {
|
||||
return array(
|
||||
'delimiter' => ',',
|
||||
'encoding' => 'UTF-8',
|
||||
'no_headers' => 0,
|
||||
);
|
||||
}
|
||||
@@ -172,11 +204,7 @@ class FeedsCSVParser extends FeedsParser {
|
||||
'#type' => 'select',
|
||||
'#title' => t('Default delimiter'),
|
||||
'#description' => t('Default field delimiter.'),
|
||||
'#options' => array(
|
||||
',' => ',',
|
||||
';' => ';',
|
||||
'TAB' => 'TAB',
|
||||
),
|
||||
'#options' => $this->getAllDelimiterTypes(),
|
||||
'#default_value' => $this->config['delimiter'],
|
||||
);
|
||||
$form['no_headers'] = array(
|
||||
@@ -185,32 +213,162 @@ class FeedsCSVParser extends FeedsParser {
|
||||
'#description' => t('Check if the imported CSV file does not start with a header row. If checked, mapping sources must be named \'0\', \'1\', \'2\' etc.'),
|
||||
'#default_value' => $this->config['no_headers'],
|
||||
);
|
||||
$form['encoding'] = $this->configEncodingForm();
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds configuration field for setting file encoding.
|
||||
*
|
||||
* If the mbstring extension is not available a markup render array
|
||||
* will be returned instead.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array.
|
||||
*/
|
||||
public function configEncodingForm() {
|
||||
if (extension_loaded('mbstring') && variable_get('feeds_use_mbstring', TRUE)) {
|
||||
// Get the system's list of available encodings.
|
||||
$options = mb_list_encodings();
|
||||
// Make the key/values the same in the array.
|
||||
$options = array_combine($options, $options);
|
||||
// Sort alphabetically not-case sensitive.
|
||||
natcasesort($options);
|
||||
return array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('File encoding'),
|
||||
'#description' => t('Performs character encoding conversion from selected option to UTF-8.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->config['encoding'],
|
||||
);
|
||||
}
|
||||
else {
|
||||
return array(
|
||||
'#markup' => '<em>' . t('PHP mbstring extension must be available for character encoding conversion.') . '</em>',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getTemplate() {
|
||||
$mappings = feeds_importer($this->id)->processor->config['mappings'];
|
||||
$sources = $uniques = array();
|
||||
|
||||
foreach ($mappings as $mapping) {
|
||||
if ($mapping['unique']) {
|
||||
$uniques[] = check_plain($mapping['source']);
|
||||
if (in_array($mapping['source'], $uniques) || in_array($mapping['source'], $sources)) {
|
||||
// Skip columns we've already seen.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($mapping['unique'])) {
|
||||
$uniques[] = $mapping['source'];
|
||||
}
|
||||
else {
|
||||
$sources[] = check_plain($mapping['source']);
|
||||
$sources[] = $mapping['source'];
|
||||
}
|
||||
}
|
||||
$sep = ',';
|
||||
|
||||
$sep = $this->getDelimiterChar($this->config);
|
||||
$columns = array();
|
||||
|
||||
foreach (array_merge($uniques, $sources) as $col) {
|
||||
if (strpos($col, $sep) !== FALSE) {
|
||||
$col = '"' . str_replace('"', '""', $col) . '"';
|
||||
}
|
||||
|
||||
$columns[] = $col;
|
||||
}
|
||||
drupal_add_http_header('Cache-Control', 'max-age=60, must-revalidate');
|
||||
drupal_add_http_header('Content-Disposition', 'attachment; filename="' . $this->id . '_template.csv"');
|
||||
drupal_add_http_header('Content-type', 'text/csv; charset=utf-8');
|
||||
|
||||
$template_file_details = $this->getTemplateFileDetails($this->config);
|
||||
|
||||
$filename = "{$this->id}_template.{$template_file_details['extension']}";
|
||||
$cache_control = 'max-age=60, must-revalidate';
|
||||
$content_disposition = 'attachment; filename="' . $filename . '"';
|
||||
$content_type = "{$template_file_details['mime_type']}; charset=utf-8";
|
||||
|
||||
drupal_add_http_header('Cache-Control', $cache_control);
|
||||
drupal_add_http_header('Content-Disposition', $content_disposition);
|
||||
drupal_add_http_header('Content-type', $content_type);
|
||||
|
||||
print implode($sep, $columns);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an associative array of the delimiters supported by this parser.
|
||||
*
|
||||
* The keys represent the value that is persisted into the database, and the
|
||||
* value represents the text that is shown in the admins UI.
|
||||
*
|
||||
* @return array
|
||||
* The associative array of delimiter types to display name.
|
||||
*/
|
||||
protected function getAllDelimiterTypes() {
|
||||
$delimiters = array(
|
||||
',',
|
||||
';',
|
||||
'TAB',
|
||||
'|',
|
||||
'+',
|
||||
);
|
||||
|
||||
return array_combine($delimiters, $delimiters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate delimiter character for the delimiter in the config.
|
||||
*
|
||||
* @param array $config
|
||||
* The configuration for the parser.
|
||||
*
|
||||
* @return string
|
||||
* The delimiter character.
|
||||
*/
|
||||
protected function getDelimiterChar(array $config) {
|
||||
$config_delimiter = $config['delimiter'];
|
||||
|
||||
switch ($config_delimiter) {
|
||||
case 'TAB':
|
||||
$delimiter = "\t";
|
||||
break;
|
||||
|
||||
default:
|
||||
$delimiter = $config_delimiter;
|
||||
break;
|
||||
}
|
||||
|
||||
return $delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets details about the template file, for the delimiter in the config.
|
||||
*
|
||||
* The resulting details indicate the file extension and mime type for the
|
||||
* delimiter type.
|
||||
*
|
||||
* @param array $config
|
||||
* The configuration for the parser.
|
||||
*
|
||||
* @return array
|
||||
* An array with the following information:
|
||||
* - 'extension': The file extension for the template ('tsv', 'csv', etc).
|
||||
* - 'mime-type': The mime type for the template
|
||||
* ('text/tab-separated-values', 'text/csv', etc).
|
||||
*/
|
||||
protected function getTemplateFileDetails(array $config) {
|
||||
switch ($config['delimiter']) {
|
||||
case 'TAB':
|
||||
$extension = 'tsv';
|
||||
$mime_type = 'text/tab-separated-values';
|
||||
break;
|
||||
|
||||
default:
|
||||
$extension = 'csv';
|
||||
$mime_type = 'text/csv';
|
||||
break;
|
||||
}
|
||||
|
||||
return array(
|
||||
'extension' => $extension,
|
||||
'mime_type' => $mime_type,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -113,6 +113,13 @@ class FeedsFetcherResult extends FeedsResult {
|
||||
*/
|
||||
abstract class FeedsFetcher extends FeedsPlugin {
|
||||
|
||||
/**
|
||||
* Implements FeedsPlugin::pluginType().
|
||||
*/
|
||||
public function pluginType() {
|
||||
return 'fetcher';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch content from a source and return it.
|
||||
*
|
||||
|
@@ -19,7 +19,7 @@ class FeedsFileFetcherResult extends FeedsFetcherResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides parent::getRaw();
|
||||
* Overrides parent::getRaw().
|
||||
*/
|
||||
public function getRaw() {
|
||||
return $this->sanitizeRaw(file_get_contents($this->file_path));
|
||||
@@ -29,7 +29,7 @@ class FeedsFileFetcherResult extends FeedsFetcherResult {
|
||||
* Overrides parent::getFilePath().
|
||||
*/
|
||||
public function getFilePath() {
|
||||
if (!file_exists($this->file_path)) {
|
||||
if (!is_readable($this->file_path)) {
|
||||
throw new Exception(t('File @filepath is not accessible.', array('@filepath' => $this->file_path)));
|
||||
}
|
||||
return $this->sanitizeFile($this->file_path);
|
||||
@@ -69,25 +69,25 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of files in a directory.
|
||||
* Returns an array of files in a directory.
|
||||
*
|
||||
* @param $dir
|
||||
* @param string $dir
|
||||
* A stream wreapper URI that is a directory.
|
||||
*
|
||||
* @return
|
||||
* An array of stream wrapper URIs pointing to files. The array is empty
|
||||
* if no files could be found. Never contains directories.
|
||||
* @return array
|
||||
* An array of stream wrapper URIs pointing to files. The array is empty if
|
||||
* no files could be found. Never contains directories.
|
||||
*/
|
||||
protected function listFiles($dir) {
|
||||
$dir = file_stream_wrapper_uri_normalize($dir);
|
||||
// Seperate out string into array of extensions. Make sure its regex safe.
|
||||
$config = $this->getConfig();
|
||||
$extensions = array_filter(array_map('preg_quote', explode(' ', $config['allowed_extensions'])));
|
||||
$regex = '/\.(' . implode('|', $extensions) . ')$/';
|
||||
$files = array();
|
||||
if ($items = @scandir($dir)) {
|
||||
foreach ($items as $item) {
|
||||
if (is_file("$dir/$item") && strpos($item, '.') !== 0) {
|
||||
$files[] = "$dir/$item";
|
||||
}
|
||||
}
|
||||
foreach (file_scan_directory($dir, $regex) as $file) {
|
||||
$files[] = $file->uri;
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
'#type' => 'file',
|
||||
'#title' => empty($this->config['direct']) ? t('File') : NULL,
|
||||
'#description' => empty($source_config['source']) ? t('Select a file from your local system.') : t('Select a different file from your local system.'),
|
||||
'#theme' => 'feeds_upload',
|
||||
'#theme_wrappers' => array('feeds_upload'),
|
||||
'#file_info' => empty($source_config['fid']) ? NULL : file_load($source_config['fid']),
|
||||
'#size' => 10,
|
||||
);
|
||||
@@ -118,7 +118,7 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
$form['source'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('File'),
|
||||
'#description' => t('Specify a path to a file or a directory. Path must start with @scheme://', array('@scheme' => file_default_scheme())),
|
||||
'#description' => t('Specify a path to a file or a directory. Prefix the path with a scheme. Available schemes: @schemes.', array('@schemes' => implode(', ', $this->config['allowed_schemes']))),
|
||||
'#default_value' => empty($source_config['source']) ? '' : $source_config['source'],
|
||||
);
|
||||
}
|
||||
@@ -126,34 +126,53 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::sourceFormValidate().
|
||||
* Overrides parent::sourceFormValidate().
|
||||
*/
|
||||
public function sourceFormValidate(&$values) {
|
||||
$values['source'] = trim($values['source']);
|
||||
|
||||
$feed_dir = 'public://feeds';
|
||||
file_prepare_directory($feed_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
|
||||
if (empty($this->config['direct'])) {
|
||||
|
||||
// If there is a file uploaded, save it, otherwise validate input on
|
||||
// file.
|
||||
// @todo: Track usage of file, remove file when removing source.
|
||||
if ($file = file_save_upload('feeds', array('file_validate_extensions' => array(0 => $this->config['allowed_extensions'])), $feed_dir)) {
|
||||
$values['source'] = $file->uri;
|
||||
$values['file'] = $file;
|
||||
$feed_dir = $this->config['directory'];
|
||||
|
||||
if (!file_prepare_directory($feed_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
|
||||
if (user_access('administer feeds')) {
|
||||
$plugin_key = feeds_importer($this->id)->config[$this->pluginType()]['plugin_key'];
|
||||
$link = url('admin/structure/feeds/' . $this->id . '/settings/' . $plugin_key);
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t('Upload failed. Please check the upload <a href="@link">settings.</a>', array('@link' => $link)));
|
||||
}
|
||||
else {
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t('Upload failed. Please contact your site administrator.'));
|
||||
}
|
||||
watchdog('feeds', 'The upload directory %directory required by a feed could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $feed_dir));
|
||||
}
|
||||
// Validate and save uploaded file.
|
||||
elseif ($file = file_save_upload('feeds', array('file_validate_extensions' => array(0 => $this->config['allowed_extensions'])), $feed_dir)) {
|
||||
$values['source'] = $file->uri;
|
||||
$values['file'] = $file;
|
||||
}
|
||||
elseif (empty($values['source'])) {
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t('Please upload a file.'));
|
||||
}
|
||||
else {
|
||||
// File present from previous upload. Nothing to validate.
|
||||
}
|
||||
}
|
||||
elseif (empty($values['source'])) {
|
||||
form_set_error('feeds][source', t('Upload a file first.'));
|
||||
}
|
||||
// If a file has not been uploaded and $values['source'] is not empty, make
|
||||
// sure that this file is within Drupal's files directory as otherwise
|
||||
// potentially any file that the web server has access to could be exposed.
|
||||
elseif (strpos($values['source'], file_default_scheme()) !== 0) {
|
||||
form_set_error('feeds][source', t('File needs to reside within the site\'s file directory, its path needs to start with @scheme://.', array('@scheme' => file_default_scheme())));
|
||||
else {
|
||||
// Check if chosen url scheme is allowed.
|
||||
$scheme = file_uri_scheme($values['source']);
|
||||
if (!$scheme || !in_array($scheme, $this->config['allowed_schemes'])) {
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t("The file needs to reside within the site's files directory, its path needs to start with scheme://. Available schemes: @schemes.", array('@schemes' => implode(', ', $this->config['allowed_schemes']))));
|
||||
}
|
||||
// Check whether the given path is readable.
|
||||
elseif (!is_readable($values['source'])) {
|
||||
form_set_error('feeds][FeedsFileFetcher][source', t('The specified file or directory does not exist.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::sourceSave().
|
||||
* Overrides parent::sourceSave().
|
||||
*/
|
||||
public function sourceSave(FeedsSource $source) {
|
||||
$source_config = $source->getConfigFor($this);
|
||||
@@ -176,7 +195,7 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::sourceDelete().
|
||||
* Overrides parent::sourceDelete().
|
||||
*/
|
||||
public function sourceDelete(FeedsSource $source) {
|
||||
$source_config = $source->getConfigFor($this);
|
||||
@@ -186,17 +205,22 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configDefaults().
|
||||
* Overrides parent::configDefaults().
|
||||
*/
|
||||
public function configDefaults() {
|
||||
$schemes = $this->getSchemes();
|
||||
$scheme = in_array('private', $schemes) ? 'private' : 'public';
|
||||
|
||||
return array(
|
||||
'allowed_extensions' => 'txt csv tsv xml opml',
|
||||
'direct' => FALSE,
|
||||
'directory' => $scheme . '://feeds',
|
||||
'allowed_schemes' => $schemes,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configForm().
|
||||
* Overrides parent::configForm().
|
||||
*/
|
||||
public function configForm(&$form_state) {
|
||||
$form = array();
|
||||
@@ -214,16 +238,112 @@ class FeedsFileFetcher extends FeedsFetcher {
|
||||
are already on the server.'),
|
||||
'#default_value' => $this->config['direct'],
|
||||
);
|
||||
$form['directory'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Upload directory'),
|
||||
'#description' => t('Directory where uploaded files get stored. Prefix the path with a scheme. Available schemes: @schemes.', array('@schemes' => implode(', ', $this->getSchemes()))),
|
||||
'#default_value' => $this->config['directory'],
|
||||
'#states' => array(
|
||||
'visible' => array(':input[name="direct"]' => array('checked' => FALSE)),
|
||||
'required' => array(':input[name="direct"]' => array('checked' => FALSE)),
|
||||
),
|
||||
);
|
||||
if ($options = $this->getSchemeOptions()) {
|
||||
$form['allowed_schemes'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Allowed schemes'),
|
||||
'#default_value' => $this->config['allowed_schemes'],
|
||||
'#options' => $options,
|
||||
'#description' => t('Select the schemes you want to allow for direct upload.'),
|
||||
'#states' => array(
|
||||
'visible' => array(':input[name="direct"]' => array('checked' => TRUE)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper. Deletes a file.
|
||||
* Overrides parent::configFormValidate().
|
||||
*
|
||||
* Ensure that the chosen directory is accessible.
|
||||
*/
|
||||
public function configFormValidate(&$values) {
|
||||
|
||||
$values['directory'] = trim($values['directory']);
|
||||
$values['allowed_schemes'] = array_filter($values['allowed_schemes']);
|
||||
|
||||
if (!$values['direct']) {
|
||||
// Ensure that the upload directory field is not empty when not in
|
||||
// direct-mode.
|
||||
if (!$values['directory']) {
|
||||
form_set_error('directory', t('Please specify an upload directory.'));
|
||||
// Do not continue validating the directory if none was specified.
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the URI scheme of the upload directory.
|
||||
$scheme = file_uri_scheme($values['directory']);
|
||||
if (!$scheme || !in_array($scheme, $this->getSchemes())) {
|
||||
form_set_error('directory', t('Please enter a valid scheme into the directory location.'));
|
||||
|
||||
// Return here so that attempts to create the directory below don't
|
||||
// throw warnings.
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the upload directory exists.
|
||||
if (!file_prepare_directory($values['directory'], FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
|
||||
form_set_error('directory', t('The chosen directory does not exist and attempts to create it failed.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a file.
|
||||
*
|
||||
* @param int $fid
|
||||
* The file id.
|
||||
* @param int $feed_nid
|
||||
* The feed node's id, or 0 if a standalone feed.
|
||||
*
|
||||
* @return bool|array
|
||||
* TRUE for success, FALSE in the event of an error, or an array if the file
|
||||
* is being used by any modules.
|
||||
*
|
||||
* @see file_delete()
|
||||
*/
|
||||
protected function deleteFile($fid, $feed_nid) {
|
||||
if ($file = file_load($fid)) {
|
||||
file_usage_delete($file, 'feeds', get_class($this), $feed_nid);
|
||||
file_delete($file);
|
||||
return file_delete($file);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns available schemes.
|
||||
*
|
||||
* @return array
|
||||
* The available schemes.
|
||||
*/
|
||||
protected function getSchemes() {
|
||||
return array_keys(file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns available scheme options for use in checkboxes or select list.
|
||||
*
|
||||
* @return array
|
||||
* The available scheme array keyed scheme => description
|
||||
*/
|
||||
protected function getSchemeOptions() {
|
||||
$options = array();
|
||||
foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $info) {
|
||||
$options[$scheme] = check_plain($scheme . ': ' . $info['description']);
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -11,28 +11,70 @@ feeds_include_library('PuSHSubscriber.inc', 'PuSHSubscriber');
|
||||
* Result of FeedsHTTPFetcher::fetch().
|
||||
*/
|
||||
class FeedsHTTPFetcherResult extends FeedsFetcherResult {
|
||||
|
||||
/**
|
||||
* The URL of the feed being fetched.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
protected $file_path;
|
||||
|
||||
/**
|
||||
* The timeout in seconds to wait for a download.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timeout;
|
||||
|
||||
/**
|
||||
*
|
||||
* Whether to ignore SSL validation errors.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $acceptInvalidCert;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct($url = NULL) {
|
||||
$this->url = $url;
|
||||
parent::__construct('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FeedsFetcherResult::getRaw();
|
||||
*/
|
||||
public function getRaw() {
|
||||
feeds_include_library('http_request.inc', 'http_request');
|
||||
$result = http_request_get($this->url);
|
||||
if (!in_array($result->code, array(200, 201, 202, 203, 204, 205, 206))) {
|
||||
throw new Exception(t('Download of @url failed with code !code.', array('@url' => $this->url, '!code' => $result->code)));
|
||||
if (!isset($this->raw)) {
|
||||
feeds_include_library('http_request.inc', 'http_request');
|
||||
$result = http_request_get($this->url, NULL, NULL, $this->acceptInvalidCert, $this->timeout);
|
||||
if (!in_array($result->code, array(200, 201, 202, 203, 204, 205, 206))) {
|
||||
throw new Exception(t('Download of @url failed with code !code.', array('@url' => $this->url, '!code' => $result->code)));
|
||||
}
|
||||
$this->raw = $result->data;
|
||||
}
|
||||
return $this->sanitizeRaw($result->data);
|
||||
|
||||
return $this->sanitizeRaw($this->raw);
|
||||
}
|
||||
|
||||
public function getTimeout() {
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
public function setTimeout($timeout) {
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accept invalid certificates option.
|
||||
*
|
||||
* @param bool $accept_invalid_cert
|
||||
* Whether to accept invalid certificates.
|
||||
*/
|
||||
public function setAcceptInvalidCert($accept_invalid_cert) {
|
||||
$this->acceptInvalidCert = (bool) $accept_invalid_cert;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +90,11 @@ class FeedsHTTPFetcher extends FeedsFetcher {
|
||||
if ($this->config['use_pubsubhubbub'] && ($raw = $this->subscriber($source->feed_nid)->receive())) {
|
||||
return new FeedsFetcherResult($raw);
|
||||
}
|
||||
return new FeedsHTTPFetcherResult($source_config['source']);
|
||||
$fetcher_result = new FeedsHTTPFetcherResult($source_config['source']);
|
||||
// When request_timeout is empty, the global value is used.
|
||||
$fetcher_result->setTimeout($this->config['request_timeout']);
|
||||
$fetcher_result->setAcceptInvalidCert($this->config['accept_invalid_cert']);
|
||||
return $fetcher_result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,6 +141,9 @@ class FeedsHTTPFetcher extends FeedsFetcher {
|
||||
'auto_detect_feeds' => FALSE,
|
||||
'use_pubsubhubbub' => FALSE,
|
||||
'designated_hub' => '',
|
||||
'request_timeout' => NULL,
|
||||
'auto_scheme' => 'http',
|
||||
'accept_invalid_cert' => FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,15 +164,46 @@ class FeedsHTTPFetcher extends FeedsFetcher {
|
||||
'#description' => t('Attempt to use a <a href="http://en.wikipedia.org/wiki/PubSubHubbub">PubSubHubbub</a> subscription if available.'),
|
||||
'#default_value' => $this->config['use_pubsubhubbub'],
|
||||
);
|
||||
$form['designated_hub'] = array(
|
||||
$form['advanced'] = array(
|
||||
'#title' => t('Advanced settings'),
|
||||
'#type' => 'fieldset',
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
$form['advanced']['auto_scheme'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Automatically add scheme'),
|
||||
'#description' => t('If the supplied URL does not contain the scheme, use this one automatically. Keep empty to force the user to input the scheme.'),
|
||||
'#default_value' => $this->config['auto_scheme'],
|
||||
);
|
||||
$form['advanced']['designated_hub'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Designated hub'),
|
||||
'#description' => t('Enter the URL of a designated PubSubHubbub hub (e. g. superfeedr.com). If given, this hub will be used instead of the hub specified in the actual feed.'),
|
||||
'#default_value' => $this->config['designated_hub'],
|
||||
'#dependency' => array(
|
||||
'edit-use-pubsubhubbub' => array(1),
|
||||
'#states' => array(
|
||||
'visible' => array(':input[name="use_pubsubhubbub"]' => array('checked' => TRUE)),
|
||||
),
|
||||
);
|
||||
// Per importer override of global http request timeout setting.
|
||||
$form['advanced']['request_timeout'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Request timeout'),
|
||||
'#description' => t('Timeout in seconds to wait for an HTTP get request to finish.</br>' .
|
||||
'<b>Note:</b> this setting will override the global setting.</br>' .
|
||||
'When left empty, the global value is used.'),
|
||||
'#default_value' => $this->config['request_timeout'],
|
||||
'#element_validate' => array('element_validate_integer_positive'),
|
||||
'#maxlength' => 3,
|
||||
'#size'=> 30,
|
||||
);
|
||||
$form['advanced']['accept_invalid_cert'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Accept invalid SSL certificates'),
|
||||
'#description' => t('<strong>IMPORTANT:</strong> This setting will force cURL to completely ignore all SSL errors. This is a <strong>major security risk</strong> and should only be used during development.'),
|
||||
'#default_value' => $this->config['accept_invalid_cert'],
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -149,13 +229,24 @@ class FeedsHTTPFetcher extends FeedsFetcher {
|
||||
public function sourceFormValidate(&$values) {
|
||||
$values['source'] = trim($values['source']);
|
||||
|
||||
// Keep a copy for error messages.
|
||||
$original_url = $values['source'];
|
||||
|
||||
$parts = parse_url($values['source']);
|
||||
if (empty($parts['scheme']) && $this->config['auto_scheme']) {
|
||||
$values['source'] = $this->config['auto_scheme'] . '://' . $values['source'];
|
||||
}
|
||||
|
||||
if (!feeds_valid_url($values['source'], TRUE)) {
|
||||
$form_key = 'feeds][' . get_class($this) . '][source';
|
||||
form_set_error($form_key, t('The URL %source is invalid.', array('%source' => $values['source'])));
|
||||
form_set_error($form_key, t('The URL %source is invalid.', array('%source' => $original_url)));
|
||||
}
|
||||
elseif ($this->config['auto_detect_feeds']) {
|
||||
feeds_include_library('http_request.inc', 'http_request');
|
||||
if ($url = http_request_get_common_syndication($values['source'])) {
|
||||
$url = http_request_get_common_syndication($values['source'], array(
|
||||
'accept_invalid_cert' => $this->config['accept_invalid_cert'],
|
||||
));
|
||||
if ($url) {
|
||||
$values['source'] = $url;
|
||||
}
|
||||
}
|
||||
|
@@ -5,10 +5,17 @@
|
||||
* Class definition of FeedsNodeProcessor.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Option for handling content in Drupal but not in source data (unpublish
|
||||
* instead of skip/delete).
|
||||
*/
|
||||
define('FEEDS_UNPUBLISH_NON_EXISTENT', 'unpublish');
|
||||
|
||||
/**
|
||||
* Creates nodes from feed items.
|
||||
*/
|
||||
class FeedsNodeProcessor extends FeedsProcessor {
|
||||
|
||||
/**
|
||||
* Define entity type.
|
||||
*/
|
||||
@@ -29,11 +36,11 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* Creates a new node in memory and returns it.
|
||||
*/
|
||||
protected function newEntity(FeedsSource $source) {
|
||||
$node = new stdClass();
|
||||
$node->type = $this->config['content_type'];
|
||||
$node = parent::newEntity($source);
|
||||
$node->type = $this->bundle();
|
||||
$node->changed = REQUEST_TIME;
|
||||
$node->created = REQUEST_TIME;
|
||||
$node->language = LANGUAGE_NONE;
|
||||
$node->is_new = TRUE;
|
||||
node_object_prepare($node);
|
||||
// Populate properties that are set by node_object_prepare().
|
||||
$node->log = 'Created by FeedsNodeProcessor';
|
||||
@@ -50,14 +57,12 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* @todo Reevaluate the use of node_object_prepare().
|
||||
*/
|
||||
protected function entityLoad(FeedsSource $source, $nid) {
|
||||
if ($this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
|
||||
$node = node_load($nid, NULL, TRUE);
|
||||
}
|
||||
else {
|
||||
// We're replacing the existing node. Only save the absolutely necessary.
|
||||
$node = db_query("SELECT created, nid, vid, type, status FROM {node} WHERE nid = :nid", array(':nid' => $nid))->fetchObject();
|
||||
$node = parent::entityLoad($source, $nid);
|
||||
|
||||
if ($this->config['update_existing'] != FEEDS_UPDATE_EXISTING) {
|
||||
$node->uid = $this->config['author'];
|
||||
}
|
||||
|
||||
node_object_prepare($node);
|
||||
|
||||
// Workaround for issue #1247506. See #1245094 for backstory.
|
||||
@@ -87,7 +92,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
|
||||
$author = user_load($entity->uid);
|
||||
|
||||
// If the uid was mapped directly, rather than by email or username, it
|
||||
// If the uid was mapped directly, rather than by email or username, it
|
||||
// could be invalid.
|
||||
if (!$author) {
|
||||
$message = 'User %uid is not a valid user.';
|
||||
@@ -104,20 +109,34 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
}
|
||||
|
||||
if (!$access) {
|
||||
$message = 'User %name is not authorized to %op content type %content_type.';
|
||||
throw new FeedsAccessException(t($message, array('%name' => $author->name, '%op' => $op, '%content_type' => $entity->type)));
|
||||
$message = t('The user %name is not authorized to %op content of type %content_type. To import this item, either the user "@name" (author of the item) must be given the permission to @op content of type @content_type, or the option "Authorize" on the Node processor settings must be turned off.', array(
|
||||
'%name' => $author->name,
|
||||
'%op' => $op,
|
||||
'%content_type' => $entity->type,
|
||||
'@name' => $author->name,
|
||||
'@op' => $op,
|
||||
'@content_type' => $entity->type,
|
||||
));
|
||||
throw new FeedsAccessException($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a node.
|
||||
*/
|
||||
protected function entityValidate($entity) {
|
||||
parent::entityValidate($entity);
|
||||
|
||||
if (!isset($entity->uid) || !is_numeric($entity->uid)) {
|
||||
$entity->uid = $this->config['author'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a node.
|
||||
*/
|
||||
public function entitySave($entity) {
|
||||
// If nid is set and a node with that id doesn't exist, flag as new.
|
||||
if (!empty($entity->nid) && !node_load($entity->nid)) {
|
||||
$entity->is_new = TRUE;
|
||||
}
|
||||
node_save($entity);
|
||||
}
|
||||
|
||||
@@ -129,28 +148,12 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement expire().
|
||||
*
|
||||
* @todo: move to processor stage?
|
||||
* Overrides parent::expiryQuery().
|
||||
*/
|
||||
public function expire($time = NULL) {
|
||||
if ($time === NULL) {
|
||||
$time = $this->expiryTime();
|
||||
}
|
||||
if ($time == FEEDS_EXPIRE_NEVER) {
|
||||
return;
|
||||
}
|
||||
$count = $this->getLimit();
|
||||
$nodes = db_query_range("SELECT n.nid FROM {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE fi.id = :id AND n.created < :created", 0, $count, array(':id' => $this->id, ':created' => REQUEST_TIME - $time));
|
||||
$nids = array();
|
||||
foreach ($nodes as $node) {
|
||||
$nids[$node->nid] = $node->nid;
|
||||
}
|
||||
$this->entityDeleteMultiple($nids);
|
||||
if (db_query_range("SELECT 1 FROM {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE fi.id = :id AND n.created < :created", 0, 1, array(':id' => $this->id, ':created' => REQUEST_TIME - $time))->fetchField()) {
|
||||
return FEEDS_BATCH_ACTIVE;
|
||||
}
|
||||
return FEEDS_BATCH_COMPLETE;
|
||||
protected function expiryQuery(FeedsSource $source, $time) {
|
||||
$select = parent::expiryQuery($source, $time);
|
||||
$select->condition('e.created', REQUEST_TIME - $time, '<');
|
||||
return $select;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,10 +167,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* Override parent::configDefaults().
|
||||
*/
|
||||
public function configDefaults() {
|
||||
$types = node_type_get_names();
|
||||
$type = isset($types['article']) ? 'article' : key($types);
|
||||
return array(
|
||||
'content_type' => $type,
|
||||
'expire' => FEEDS_EXPIRE_NEVER,
|
||||
'author' => 0,
|
||||
'authorize' => TRUE,
|
||||
@@ -178,16 +178,8 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* Override parent::configForm().
|
||||
*/
|
||||
public function configForm(&$form_state) {
|
||||
$types = node_type_get_names();
|
||||
array_walk($types, 'check_plain');
|
||||
$form = parent::configForm($form_state);
|
||||
$form['content_type'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Content type'),
|
||||
'#description' => t('Select the content type for the nodes to be created. <strong>Note:</strong> Users with "import !feed_id feeds" permissions will be able to <strong>import</strong> nodes of the content type selected here regardless of the node level permissions. Further, users with "clear !feed_id permissions" will be able to <strong>delete</strong> imported nodes regardless of their node level permissions.', array('!feed_id' => $this->id)),
|
||||
'#options' => $types,
|
||||
'#default_value' => $this->config['content_type'],
|
||||
);
|
||||
|
||||
$author = user_load($this->config['author']);
|
||||
$form['author'] = array(
|
||||
'#type' => 'textfield',
|
||||
@@ -207,14 +199,13 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
'#type' => 'select',
|
||||
'#title' => t('Expire nodes'),
|
||||
'#options' => $period,
|
||||
'#description' => t('Select after how much time nodes should be deleted. The node\'s published date will be used for determining the node\'s age, see Mapping settings.'),
|
||||
'#description' => t("Select after how much time nodes should be deleted. The node's published date will be used for determining the node's age, see Mapping settings."),
|
||||
'#default_value' => $this->config['expire'],
|
||||
);
|
||||
$form['update_existing']['#options'] = array(
|
||||
FEEDS_SKIP_EXISTING => 'Do not update existing nodes',
|
||||
FEEDS_REPLACE_EXISTING => 'Replace existing nodes',
|
||||
FEEDS_UPDATE_EXISTING => 'Update existing nodes (slower than replacing them)',
|
||||
);
|
||||
// Add on the "Unpublish" option for nodes, update wording.
|
||||
if (isset($form['update_non_existent'])) {
|
||||
$form['update_non_existent']['#options'][FEEDS_UNPUBLISH_NON_EXISTENT] = t('Unpublish non-existent nodes');
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -248,10 +239,16 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
case 'created':
|
||||
$target_node->created = feeds_to_unixtime($value, REQUEST_TIME);
|
||||
break;
|
||||
case 'changed':
|
||||
// The 'changed' value will be set on the node in feeds_node_presave().
|
||||
// This is because node_save() always overwrites this value (though
|
||||
// before invoking hook_node_presave()).
|
||||
$target_node->feeds_item->node_changed = feeds_to_unixtime($value, REQUEST_TIME);
|
||||
break;
|
||||
case 'feeds_source':
|
||||
// Get the class of the feed node importer's fetcher and set the source
|
||||
// property. See feeds_node_update() how $node->feeds gets stored.
|
||||
if ($id = feeds_get_importer_id($this->config['content_type'])) {
|
||||
if ($id = feeds_get_importer_id($this->bundle())) {
|
||||
$class = get_class(feeds_importer($id)->fetcher);
|
||||
$target_node->feeds[$class]['source'] = $value;
|
||||
// This effectively suppresses 'import on submission' feature.
|
||||
@@ -279,9 +276,10 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
* Return available mapping targets.
|
||||
*/
|
||||
public function getMappingTargets() {
|
||||
$type = node_type_get_type($this->config['content_type']);
|
||||
$type = node_type_get_type($this->bundle());
|
||||
|
||||
$targets = parent::getMappingTargets();
|
||||
if ($type->has_title) {
|
||||
if ($type && $type->has_title) {
|
||||
$targets['title'] = array(
|
||||
'name' => t('Title'),
|
||||
'description' => t('The title of the node.'),
|
||||
@@ -313,6 +311,10 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
'name' => t('Published date'),
|
||||
'description' => t('The UNIX time when a node has been published.'),
|
||||
);
|
||||
$targets['changed'] = array(
|
||||
'name' => t('Updated date'),
|
||||
'description' => t('The Unix timestamp when a node has been last updated.'),
|
||||
);
|
||||
$targets['promote'] = array(
|
||||
'name' => t('Promoted to front page'),
|
||||
'description' => t('Boolean value, whether or not node is promoted to front page. (1 = promoted, 0 = not promoted)'),
|
||||
@@ -339,7 +341,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
}
|
||||
|
||||
// If the target content type is a Feed node, expose its source field.
|
||||
if ($id = feeds_get_importer_id($this->config['content_type'])) {
|
||||
if ($id = feeds_get_importer_id($this->bundle())) {
|
||||
$name = feeds_importer($id)->config['name'];
|
||||
$targets['feeds_source'] = array(
|
||||
'name' => t('Feed source'),
|
||||
@@ -348,11 +350,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
);
|
||||
}
|
||||
|
||||
// Let other modules expose mapping targets.
|
||||
self::loadMappers();
|
||||
$entity_type = $this->entityType();
|
||||
$bundle = $this->config['content_type'];
|
||||
drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
|
||||
$this->getHookTargets($targets);
|
||||
|
||||
return $targets;
|
||||
}
|
||||
@@ -373,10 +371,10 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
$nid = db_query("SELECT nid FROM {node} WHERE nid = :nid", array(':nid' => $value))->fetchField();
|
||||
break;
|
||||
case 'title':
|
||||
$nid = db_query("SELECT nid FROM {node} WHERE title = :title AND type = :type", array(':title' => $value, ':type' => $this->config['content_type']))->fetchField();
|
||||
$nid = db_query("SELECT nid FROM {node} WHERE title = :title AND type = :type", array(':title' => $value, ':type' => $this->bundle()))->fetchField();
|
||||
break;
|
||||
case 'feeds_source':
|
||||
if ($id = feeds_get_importer_id($this->config['content_type'])) {
|
||||
if ($id = feeds_get_importer_id($this->bundle())) {
|
||||
$nid = db_query("SELECT fs.feed_nid FROM {node} n JOIN {feeds_source} fs ON n.nid = fs.feed_nid WHERE fs.id = :id AND fs.source = :source", array(':id' => $id, ':source' => $value))->fetchField();
|
||||
}
|
||||
break;
|
||||
@@ -388,4 +386,34 @@ class FeedsNodeProcessor extends FeedsProcessor {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FeedsProcessor::clean().
|
||||
*
|
||||
* Allow unpublish instead of delete.
|
||||
*
|
||||
* @param FeedsState $state
|
||||
* The FeedsState object for the given stage.
|
||||
*/
|
||||
protected function clean(FeedsState $state) {
|
||||
// Delegate to parent if not unpublishing or option not set.
|
||||
if (!isset($this->config['update_non_existent']) || $this->config['update_non_existent'] != FEEDS_UNPUBLISH_NON_EXISTENT) {
|
||||
return parent::clean($state);
|
||||
}
|
||||
|
||||
$total = count($state->removeList);
|
||||
if ($total) {
|
||||
$nodes = node_load_multiple($state->removeList);
|
||||
foreach ($nodes as &$node) {
|
||||
$this->loadItemInfo($node);
|
||||
// Update the hash value of the feed item to ensure that the item gets
|
||||
// updated in case it reappears in the feed.
|
||||
$node->feeds_item->hash = $this->config['update_non_existent'];
|
||||
node_unpublish_action($node);
|
||||
node_save($node);
|
||||
$state->unpublished++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -53,6 +53,13 @@ class FeedsParserResult extends FeedsResult {
|
||||
*/
|
||||
abstract class FeedsParser extends FeedsPlugin {
|
||||
|
||||
/**
|
||||
* Implements FeedsPlugin::pluginType().
|
||||
*/
|
||||
public function pluginType() {
|
||||
return 'parser';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse content fetched by fetcher.
|
||||
*
|
||||
@@ -112,6 +119,21 @@ abstract class FeedsParser extends FeedsPlugin {
|
||||
return $sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of mapped sources.
|
||||
*
|
||||
* @return array
|
||||
* List of mapped source names in an array.
|
||||
*/
|
||||
public function getMappingSourceList() {
|
||||
$mappings = feeds_importer($this->id)->processor->config['mappings'];
|
||||
$sources = array();
|
||||
foreach ($mappings as $mapping) {
|
||||
$sources[] = $mapping['source'];
|
||||
}
|
||||
return $sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element identified by $element_key of the given item.
|
||||
* The element key corresponds to the values in the array returned by
|
||||
@@ -257,7 +279,27 @@ class FeedsGeoTermElement extends FeedsTermElement {
|
||||
* Enclosure element, can be part of the result array.
|
||||
*/
|
||||
class FeedsEnclosure extends FeedsElement {
|
||||
protected $mime_type;
|
||||
|
||||
/**
|
||||
* The mime type of the enclosure.
|
||||
*
|
||||
* @param string
|
||||
*/
|
||||
protected $mime_type;
|
||||
|
||||
/**
|
||||
* The default list of allowed extensions.
|
||||
*
|
||||
* @param string
|
||||
*/
|
||||
protected $allowedExtensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
|
||||
|
||||
/**
|
||||
* The sanitized local file name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $safeFilename;
|
||||
|
||||
/**
|
||||
* Constructor, requires MIME type.
|
||||
@@ -280,6 +322,17 @@ class FeedsEnclosure extends FeedsElement {
|
||||
return $this->mime_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of allowed extensions.
|
||||
*
|
||||
* @param string $extensions
|
||||
* The list of allowed extensions separated by a space.
|
||||
*/
|
||||
public function setAllowedExtensions($extensions) {
|
||||
// Normalize whitespace so that empty extensions are not allowed.
|
||||
$this->allowedExtensions = drupal_strtolower(trim(preg_replace('/\s+/', ' ', $extensions)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method instead of FeedsElement::getValue() when fetching the file
|
||||
* from the URL.
|
||||
@@ -294,20 +347,74 @@ class FeedsEnclosure extends FeedsElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method instead of FeedsElement::getValue() to get the file name
|
||||
* transformed for better local saving (underscores instead of spaces)
|
||||
* Returns the full path to the file URI with a safe file name.
|
||||
*
|
||||
* @return
|
||||
* Value with space characters changed to underscores.
|
||||
* @return string
|
||||
* The safe file URI.
|
||||
*
|
||||
* @see FeedsElement::getValue()
|
||||
* @throws RuntimeException
|
||||
* Thrown if the file extension is invalid.
|
||||
*/
|
||||
public function getLocalValue() {
|
||||
return str_replace(' ', '_', $this->getValue());
|
||||
public function getSanitizedUri() {
|
||||
return drupal_dirname($this->getValue()) . '/' . $this->getSafeFilename();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* Returns the file name transformed for better local saving.
|
||||
*
|
||||
* @return string
|
||||
* Value with space characters changed to underscores.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* Thrown if the file extension is invalid.
|
||||
*/
|
||||
public function getLocalValue() {
|
||||
return str_replace(' ', '_', $this->getSafeFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the safe file name.
|
||||
*
|
||||
* @return string
|
||||
* A filename that is safe to save to the filesystem.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* Thrown if the file extension is invalid.
|
||||
*/
|
||||
protected function getSafeFilename() {
|
||||
if (isset($this->safeFilename)) {
|
||||
return $this->safeFilename;
|
||||
}
|
||||
|
||||
// Strip any query string or fragment from file name.
|
||||
list($filename) = explode('?', $this->getValue());
|
||||
list($filename) = explode('#', $filename);
|
||||
|
||||
$filename = rawurldecode(drupal_basename($filename));
|
||||
|
||||
// Remove leading and trailing whitespace and periods.
|
||||
$filename = trim($filename, " \t\n\r\0\x0B.");
|
||||
|
||||
if (strpos($filename, '.') === FALSE) {
|
||||
$extension = FALSE;
|
||||
}
|
||||
else {
|
||||
$extension = drupal_strtolower(substr($filename, strrpos($filename, '.') + 1));
|
||||
}
|
||||
|
||||
if (!$extension || !in_array($extension, explode(' ', $this->allowedExtensions), TRUE)) {
|
||||
throw new RuntimeException(t('The file @file has an invalid extension.', array('@file' => $filename)));
|
||||
}
|
||||
|
||||
$this->safeFilename = file_munge_filename($filename, $this->allowedExtensions, FALSE);
|
||||
|
||||
return $this->safeFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the content from the file URL.
|
||||
*
|
||||
* @return string
|
||||
* The content of the referenced resource.
|
||||
*/
|
||||
public function getContent() {
|
||||
@@ -333,18 +440,19 @@ class FeedsEnclosure extends FeedsElement {
|
||||
* If file object could not be created.
|
||||
*/
|
||||
public function getFile($destination) {
|
||||
|
||||
$file = NULL;
|
||||
if ($this->getValue()) {
|
||||
// Prepare destination directory.
|
||||
file_prepare_directory($destination, FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY);
|
||||
// Copy or save file depending on whether it is remote or local.
|
||||
if (drupal_realpath($this->getValue())) {
|
||||
if (drupal_realpath($this->getSanitizedUri())) {
|
||||
$file = new stdClass();
|
||||
$file->uid = 0;
|
||||
$file->uri = $this->getValue();
|
||||
$file->filemime = $this->mime_type;
|
||||
$file->filename = basename($file->uri);
|
||||
if (dirname($file->uri) != $destination) {
|
||||
$file->uri = $this->getSanitizedUri();
|
||||
$file->filemime = $this->getMIMEType();
|
||||
$file->filename = $this->getSafeFilename();
|
||||
|
||||
if (drupal_dirname($file->uri) !== $destination) {
|
||||
$file = file_copy($file, $destination);
|
||||
}
|
||||
else {
|
||||
@@ -361,15 +469,17 @@ class FeedsEnclosure extends FeedsElement {
|
||||
}
|
||||
}
|
||||
else {
|
||||
$filename = basename($this->getLocalValue());
|
||||
if (module_exists('transliteration')) {
|
||||
require_once drupal_get_path('module', 'transliteration') . '/transliteration.inc';
|
||||
$filename = transliteration_clean_filename($filename);
|
||||
}
|
||||
if (file_uri_target($destination)) {
|
||||
$destination = trim($destination, '/') . '/';
|
||||
}
|
||||
try {
|
||||
$filename = $this->getLocalValue();
|
||||
|
||||
if (module_exists('transliteration')) {
|
||||
require_once drupal_get_path('module', 'transliteration') . '/transliteration.inc';
|
||||
$filename = transliteration_clean_filename($filename);
|
||||
}
|
||||
|
||||
$file = file_save_data($this->getContent(), $destination . $filename);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
@@ -381,8 +491,9 @@ class FeedsEnclosure extends FeedsElement {
|
||||
if (!$file) {
|
||||
throw new Exception(t('Invalid enclosure %enclosure', array('%enclosure' => $this->getValue())));
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,13 +569,13 @@ class FeedsDateTimeElement extends FeedsElement {
|
||||
* Helper method for buildDateField(). Build a FeedsDateTimeElement object
|
||||
* from a standard formatted node.
|
||||
*/
|
||||
protected static function readDateField($entity, $field_name) {
|
||||
protected static function readDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) {
|
||||
$ret = new FeedsDateTimeElement();
|
||||
if (isset($entity->{$field_name}['und'][0]['date']) && $entity->{$field_name}['und'][0]['date'] instanceof FeedsDateTime) {
|
||||
$ret->start = $entity->{$field_name}['und'][0]['date'];
|
||||
if (isset($entity->{$field_name}[$language][$delta]['date']) && $entity->{$field_name}[$language][$delta]['date'] instanceof FeedsDateTime) {
|
||||
$ret->start = $entity->{$field_name}[$language][$delta]['date'];
|
||||
}
|
||||
if (isset($entity->{$field_name}['und'][0]['date2']) && $entity->{$field_name}['und'][0]['date2'] instanceof FeedsDateTime) {
|
||||
$ret->end = $entity->{$field_name}['und'][0]['date2'];
|
||||
if (isset($entity->{$field_name}[$language][$delta]['date2']) && $entity->{$field_name}[$language][$delta]['date2'] instanceof FeedsDateTime) {
|
||||
$ret->end = $entity->{$field_name}[$language][$delta]['date2'];
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
@@ -472,15 +583,17 @@ class FeedsDateTimeElement extends FeedsElement {
|
||||
/**
|
||||
* Build a entity's date field from our object.
|
||||
*
|
||||
* @param $entity
|
||||
* @param object $entity
|
||||
* The entity to build the date field on.
|
||||
* @param $field_name
|
||||
* @param str $field_name
|
||||
* The name of the field to build.
|
||||
* @param int $delta
|
||||
* The delta in the field.
|
||||
*/
|
||||
public function buildDateField($entity, $field_name) {
|
||||
public function buildDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) {
|
||||
$info = field_info_field($field_name);
|
||||
|
||||
$oldfield = FeedsDateTimeElement::readDateField($entity, $field_name);
|
||||
$oldfield = FeedsDateTimeElement::readDateField($entity, $field_name, $delta, $language);
|
||||
// Merge with any preexisting objects on the field; we take precedence.
|
||||
$oldfield = $this->merge($oldfield);
|
||||
$use_start = $oldfield->start;
|
||||
@@ -513,27 +626,27 @@ class FeedsDateTimeElement extends FeedsElement {
|
||||
|
||||
$db_tz = new DateTimeZone($db_tz);
|
||||
if (!isset($entity->{$field_name})) {
|
||||
$entity->{$field_name} = array('und' => array());
|
||||
$entity->{$field_name} = array($language => array());
|
||||
}
|
||||
if ($use_start) {
|
||||
$entity->{$field_name}['und'][0]['timezone'] = $use_start->getTimezone()->getName();
|
||||
$entity->{$field_name}['und'][0]['offset'] = $use_start->getOffset();
|
||||
$entity->{$field_name}[$language][$delta]['timezone'] = $use_start->getTimezone()->getName();
|
||||
$entity->{$field_name}[$language][$delta]['offset'] = $use_start->getOffset();
|
||||
$use_start->setTimezone($db_tz);
|
||||
$entity->{$field_name}['und'][0]['date'] = $use_start;
|
||||
$entity->{$field_name}[$language][$delta]['date'] = $use_start;
|
||||
/**
|
||||
* @todo the date_type_format line could be simplified based upon a patch
|
||||
* DO issue #259308 could affect this, follow up on at some point.
|
||||
* Without this, all granularity info is lost.
|
||||
* $use_start->format(date_type_format($field['type'], $use_start->granularity));
|
||||
*/
|
||||
$entity->{$field_name}['und'][0]['value'] = $use_start->format(date_type_format($info['type']));
|
||||
$entity->{$field_name}[$language][$delta]['value'] = $use_start->format(date_type_format($info['type']));
|
||||
}
|
||||
if ($use_end) {
|
||||
// Don't ever use end to set timezone (for now)
|
||||
$entity->{$field_name}['und'][0]['offset2'] = $use_end->getOffset();
|
||||
$entity->{$field_name}[$language][$delta]['offset2'] = $use_end->getOffset();
|
||||
$use_end->setTimezone($db_tz);
|
||||
$entity->{$field_name}['und'][0]['date2'] = $use_end;
|
||||
$entity->{$field_name}['und'][0]['value2'] = $use_end->format(date_type_format($info['type']));
|
||||
$entity->{$field_name}[$language][$delta]['date2'] = $use_end;
|
||||
$entity->{$field_name}[$language][$delta]['value2'] = $use_end->format(date_type_format($info['type']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -583,12 +696,17 @@ class FeedsDateTime extends DateTime {
|
||||
* PHP DateTimeZone object, NULL allowed
|
||||
*/
|
||||
public function __construct($time = '', $tz = NULL) {
|
||||
// Assume UNIX timestamp if numeric.
|
||||
if (is_numeric($time)) {
|
||||
// Make sure it's not a simple year
|
||||
if ((is_string($time) && strlen($time) > 4) || is_int($time)) {
|
||||
// Assume UNIX timestamp if it doesn't look like a simple year.
|
||||
if (strlen($time) > 4) {
|
||||
$time = "@" . $time;
|
||||
}
|
||||
// If it's a year, add a default month too, because PHP's date functions
|
||||
// won't parse standalone years after 2000 correctly (see explanation at
|
||||
// http://aaronsaray.com/blog/2007/07/11/helpful-strtotime-reminders/#comment-47).
|
||||
else {
|
||||
$time = 'January ' . $time;
|
||||
}
|
||||
}
|
||||
|
||||
// PHP < 5.3 doesn't like the GMT- notation for parsing timezones.
|
||||
@@ -596,7 +714,7 @@ class FeedsDateTime extends DateTime {
|
||||
$time = str_replace("GMT+", "+", $time);
|
||||
|
||||
// Some PHP 5.2 version's DateTime class chokes on invalid dates.
|
||||
if (!strtotime($time)) {
|
||||
if (!date_create($time)) {
|
||||
$time = 'now';
|
||||
}
|
||||
|
||||
|
@@ -20,13 +20,84 @@ class FeedsResult {}
|
||||
abstract class FeedsPlugin extends FeedsConfigurable implements FeedsSourceInterface {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* The plugin definition.
|
||||
*
|
||||
* Initialize class variables.
|
||||
* @var array
|
||||
*/
|
||||
protected $pluginDefinition;
|
||||
|
||||
/**
|
||||
* Constructs a FeedsPlugin object.
|
||||
*
|
||||
* A copy of FeedsConfigurable::__construct() that doesn't call
|
||||
* configDefaults() so that we avoid circular dependencies.
|
||||
*
|
||||
* @param string $id
|
||||
* The importer id.
|
||||
*/
|
||||
protected function __construct($id) {
|
||||
parent::__construct($id);
|
||||
$this->source_config = $this->sourceDefaults();
|
||||
$this->id = $id;
|
||||
$this->export_type = FEEDS_EXPORT_NONE;
|
||||
$this->disabled = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a FeedsPlugin object.
|
||||
*
|
||||
* Don't use directly, use feeds_plugin() instead.
|
||||
*
|
||||
* @see feeds_plugin()
|
||||
*/
|
||||
public static function instance($class, $id, array $plugin_definition = array()) {
|
||||
if (!strlen($id)) {
|
||||
throw new InvalidArgumentException(t('Empty configuration identifier.'));
|
||||
}
|
||||
|
||||
$instances = &drupal_static(__METHOD__, array());
|
||||
|
||||
if (!isset($instances[$class][$id])) {
|
||||
$instance = new $class($id);
|
||||
|
||||
// The ordering here is important. The plugin definition should be usable
|
||||
// in getConfig().
|
||||
$instance->setPluginDefinition($plugin_definition);
|
||||
$instance->setConfig($instance->configDefaults());
|
||||
$instances[$class][$id] = $instance;
|
||||
}
|
||||
|
||||
return $instances[$class][$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of plugin.
|
||||
*
|
||||
* @return string
|
||||
* One of either 'fetcher', 'parser', or 'processor'.
|
||||
*/
|
||||
abstract public function pluginType();
|
||||
|
||||
/**
|
||||
* Returns the plugin definition.
|
||||
*
|
||||
* @return array
|
||||
* The plugin definition array.
|
||||
*
|
||||
* @see ctools_get_plugins()
|
||||
*/
|
||||
public function pluginDefinition() {
|
||||
return $this->pluginDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the plugin definition.
|
||||
*
|
||||
* This is protected since we're only using it in FeedsPlugin::instance().
|
||||
*
|
||||
* @param array $plugin_definition
|
||||
* The plugin definition.
|
||||
*/
|
||||
protected function setPluginDefinition(array $plugin_definition) {
|
||||
$this->pluginDefinition = $plugin_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +161,7 @@ abstract class FeedsPlugin extends FeedsConfigurable implements FeedsSourceInter
|
||||
*
|
||||
* @todo: Use CTools Plugin API.
|
||||
*/
|
||||
protected static function loadMappers() {
|
||||
public static function loadMappers() {
|
||||
static $loaded = FALSE;
|
||||
if (!$loaded) {
|
||||
$path = drupal_get_path('module', 'feeds') . '/mappers';
|
||||
@@ -197,15 +268,102 @@ abstract class FeedsPlugin extends FeedsConfigurable implements FeedsSourceInter
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements FeedsConfigurable::dependencies().
|
||||
*/
|
||||
public function dependencies() {
|
||||
$dependencies = parent::dependencies();
|
||||
|
||||
// Find out which module provides this plugin.
|
||||
$plugin_info = $this->pluginDefinition();
|
||||
if (isset($plugin_info['module'])) {
|
||||
$dependencies[$plugin_info['module']] = $plugin_info['module'];
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when a plugin is missing.
|
||||
*/
|
||||
class FeedsMissingPlugin extends FeedsPlugin {
|
||||
public function pluginType() {
|
||||
return 'missing';
|
||||
}
|
||||
|
||||
public function save() {}
|
||||
|
||||
/**
|
||||
* Fetcher methods.
|
||||
*/
|
||||
public function fetch(FeedsSource $source) {
|
||||
return new FeedsFetcherResult('');
|
||||
}
|
||||
|
||||
public function clear(FeedsSource $source) {}
|
||||
|
||||
public function request($feed_nid = 0) {
|
||||
drupal_access_denied();
|
||||
}
|
||||
|
||||
public function menuItem() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function subscribe(FeedsSource $source) {}
|
||||
|
||||
public function unsubscribe(FeedsSource $source) {}
|
||||
|
||||
public function importPeriod(FeedsSource $source) {}
|
||||
|
||||
/**
|
||||
* Parser methods.
|
||||
*/
|
||||
public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
|
||||
return new FeedsParserResult();
|
||||
}
|
||||
|
||||
public function getMappingSources() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processor methods.
|
||||
*/
|
||||
public function process(FeedsSource $source, FeedsParserResult $parser_result) {}
|
||||
|
||||
public function entityType() {}
|
||||
|
||||
public function bundle() {}
|
||||
|
||||
public function bundleOptions() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getLimit() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getMappings() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getMappingTargets() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function expire(FeedsSource $source, $time = NULL) {}
|
||||
|
||||
public function itemCount(FeedsSource $source) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function expiryTime() {
|
||||
return FEEDS_EXPIRE_NEVER;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ class FeedsSimplePieEnclosure extends FeedsEnclosure {
|
||||
/**
|
||||
* Serialization helper.
|
||||
*
|
||||
* Handle the simplepie enclosure class seperately ourselves.
|
||||
* Handle the simplepie enclosure class separately ourselves.
|
||||
*/
|
||||
public function __sleep() {
|
||||
$this->_serialized_simplepie_enclosure = serialize($this->simplepie_enclosure);
|
||||
@@ -52,6 +52,7 @@ class FeedsSimplePieEnclosure extends FeedsEnclosure {
|
||||
public function getMIMEType() {
|
||||
return $this->simplepie_enclosure->get_real_type();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,10 +68,6 @@ class FeedsSimplePieParser extends FeedsParser {
|
||||
public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
|
||||
feeds_include_simplepie();
|
||||
|
||||
// Please be quiet SimplePie.
|
||||
$level = error_reporting();
|
||||
error_reporting($level ^ E_DEPRECATED ^ E_STRICT);
|
||||
|
||||
// Initialize SimplePie.
|
||||
$parser = new SimplePie();
|
||||
$parser->set_raw_data($fetcher_result->getRaw());
|
||||
@@ -149,8 +146,7 @@ class FeedsSimplePieParser extends FeedsParser {
|
||||
}
|
||||
// Release parser.
|
||||
unset($parser);
|
||||
// Set error reporting back to its previous value.
|
||||
error_reporting($level);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -237,4 +233,5 @@ class FeedsSimplePieParser extends FeedsParser {
|
||||
$words = array_slice($words, 0, 3);
|
||||
return implode(' ', $words);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
protected function entityInfo() {
|
||||
$info = parent::entityInfo();
|
||||
$info['label plural'] = t('Terms');
|
||||
$info['bundle name'] = t('Vocabulary');
|
||||
return $info;
|
||||
}
|
||||
|
||||
@@ -30,25 +31,35 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
*/
|
||||
protected function newEntity(FeedsSource $source) {
|
||||
$vocabulary = $this->vocabulary();
|
||||
$term = new stdClass();
|
||||
$term = parent::newEntity($source);
|
||||
$term->vid = $vocabulary->vid;
|
||||
$term->vocabulary_machine_name = $vocabulary->machine_name;
|
||||
$term->format = isset($this->config['input_format']) ? $this->config['input_format'] : filter_fallback_format();
|
||||
|
||||
return $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an existing term.
|
||||
* Load an existing entity.
|
||||
*/
|
||||
protected function entityLoad(FeedsSource $source, $tid) {
|
||||
return taxonomy_term_load($tid);
|
||||
protected function entityLoad(FeedsSource $source, $entity_id) {
|
||||
$entity = parent::entityLoad($source, $entity_id);
|
||||
|
||||
// Avoid missing bundle errors when term has been loaded directly from db.
|
||||
if (empty($entity->vocabulary_machine_name) && !empty($entity->vid)) {
|
||||
$vocabulary = taxonomy_vocabulary_load($entity->vid);
|
||||
$entity->vocabulary_machine_name = ($vocabulary) ? $vocabulary->machine_name : NULL;
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a term.
|
||||
*/
|
||||
protected function entityValidate($term) {
|
||||
if (empty($term->name)) {
|
||||
parent::entityValidate($term);
|
||||
|
||||
if (drupal_strlen($term->name) == 0) {
|
||||
throw new FeedsValidationException(t('Term name missing.'));
|
||||
}
|
||||
}
|
||||
@@ -90,37 +101,11 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configForm().
|
||||
* Overrides parent::setTargetElement().
|
||||
*
|
||||
* Operate on a target item that is a taxonomy term.
|
||||
*/
|
||||
public function configForm(&$form_state) {
|
||||
$options = array(0 => t('Select a vocabulary'));
|
||||
foreach (taxonomy_get_vocabularies() as $vocab) {
|
||||
$options[$vocab->machine_name] = $vocab->name;
|
||||
}
|
||||
$form = parent::configForm($form_state);
|
||||
$form['vocabulary'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Import to vocabulary'),
|
||||
'#description' => t('Choose the vocabulary to import into. <strong>CAUTION:</strong> when deleting terms through the "Delete items" tab, Feeds will delete <em>all</em> terms from this vocabulary.'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->config['vocabulary'],
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent::configFormValidate().
|
||||
*/
|
||||
public function configFormValidate(&$values) {
|
||||
if (empty($values['vocabulary'])) {
|
||||
form_set_error('vocabulary', t('Choose a vocabulary'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override setTargetElement to operate on a target item that is a taxonomy term.
|
||||
*/
|
||||
public function setTargetElement(FeedsSource $source, $target_term, $target_element, $value) {
|
||||
public function setTargetElement(FeedsSource $source, $target_term, $target_element, $value, array $mapping = array()) {
|
||||
switch ($target_element) {
|
||||
case 'parent':
|
||||
if (!empty($value)) {
|
||||
@@ -142,15 +127,26 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
$target_term->parent[] = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'parentguid':
|
||||
// value is parent_guid field value
|
||||
$parent_tid = 0;
|
||||
$query = db_select('feeds_item')
|
||||
->fields('feeds_item', array('entity_id'))
|
||||
->condition('entity_type', $this->entityType());
|
||||
$parent_tid = $query->condition('guid', $value)->execute()->fetchField();
|
||||
$target_term->parent[] = ($parent_tid) ? $parent_tid : 0;
|
||||
|
||||
$term_ids = array_keys($query->condition('guid', $value)->execute()->fetchAllAssoc('entity_id'));
|
||||
if (!empty($term_ids)) {
|
||||
$terms = entity_load($this->entityType(), $term_ids);
|
||||
foreach ($terms as $term) {
|
||||
if ($term->vid == $target_term->vid) {
|
||||
$parent_tid = $term->tid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$target_term->parent[] = $parent_tid;
|
||||
break;
|
||||
|
||||
case 'weight':
|
||||
if (!empty($value)) {
|
||||
$weight = intval($value);
|
||||
@@ -160,6 +156,20 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
}
|
||||
$target_term->weight = $weight;
|
||||
break;
|
||||
|
||||
case 'description':
|
||||
if (!empty($mapping['format'])) {
|
||||
$target_term->format = $mapping['format'];
|
||||
}
|
||||
elseif (!empty($this->config['input_format'])) {
|
||||
$target_term->format = $this->config['input_format'];
|
||||
}
|
||||
else {
|
||||
$target_term->format = filter_fallback_format();
|
||||
}
|
||||
$target_term->description = $value;
|
||||
break;
|
||||
|
||||
default:
|
||||
parent::setTargetElement($source, $target_term, $target_element, $value);
|
||||
break;
|
||||
@@ -195,19 +205,13 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
'description' => array(
|
||||
'name' => t('Term description'),
|
||||
'description' => t('Description of the taxonomy term.'),
|
||||
'summary_callbacks' => array('text_feeds_summary_callback'),
|
||||
'form_callbacks' => array('text_feeds_form_callback'),
|
||||
),
|
||||
);
|
||||
|
||||
// Let implementers of hook_feeds_term_processor_targets() add their targets.
|
||||
try {
|
||||
self::loadMappers();
|
||||
$entity_type = $this->entityType();
|
||||
$bundle = $this->vocabulary()->machine_name;
|
||||
drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
$this->getHookTargets($targets);
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
@@ -235,11 +239,19 @@ class FeedsTermProcessor extends FeedsProcessor {
|
||||
* Return vocabulary to map to.
|
||||
*/
|
||||
public function vocabulary() {
|
||||
if (isset($this->config['vocabulary'])) {
|
||||
if ($vocabulary = taxonomy_vocabulary_machine_name_load($this->config['vocabulary'])) {
|
||||
return $vocabulary;
|
||||
}
|
||||
if ($vocabulary = taxonomy_vocabulary_machine_name_load($this->bundle())) {
|
||||
return $vocabulary;
|
||||
}
|
||||
throw new Exception(t('No vocabulary defined for Taxonomy Term processor.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FeedsProcessor::dependencies().
|
||||
*/
|
||||
public function dependencies() {
|
||||
$dependencies = parent::dependencies();
|
||||
$dependencies['taxonomy'] = 'taxonomy';
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,13 +2,21 @@
|
||||
|
||||
/**
|
||||
* @file
|
||||
* FeedsUserProcessor class.
|
||||
* Contains FeedsUserProcessor.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Option to block users not found in the feed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
define('FEEDS_BLOCK_NON_EXISTENT', 'block');
|
||||
|
||||
/**
|
||||
* Feeds processor plugin. Create users from feed items.
|
||||
*/
|
||||
class FeedsUserProcessor extends FeedsProcessor {
|
||||
|
||||
/**
|
||||
* Define entity type.
|
||||
*/
|
||||
@@ -29,10 +37,11 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
* Creates a new user account in memory and returns it.
|
||||
*/
|
||||
protected function newEntity(FeedsSource $source) {
|
||||
$account = new stdClass();
|
||||
$account = parent::newEntity($source);
|
||||
$account->uid = 0;
|
||||
$account->roles = array_filter($this->config['roles']);
|
||||
$account->status = $this->config['status'];
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
@@ -40,8 +49,9 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
* Loads an existing user.
|
||||
*/
|
||||
protected function entityLoad(FeedsSource $source, $uid) {
|
||||
$user = parent::entityLoad($source, $uid);
|
||||
|
||||
// Copy the password so that we can compare it again at save.
|
||||
$user = user_load($uid);
|
||||
$user->feeds_original_pass = $user->pass;
|
||||
return $user;
|
||||
}
|
||||
@@ -50,6 +60,8 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
* Validates a user account.
|
||||
*/
|
||||
protected function entityValidate($account) {
|
||||
parent::entityValidate($account);
|
||||
|
||||
if (empty($account->name) || empty($account->mail) || !valid_email_address($account->mail)) {
|
||||
throw new FeedsValidationException(t('User name missing or email not valid.'));
|
||||
}
|
||||
@@ -87,9 +99,7 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
* Delete multiple user accounts.
|
||||
*/
|
||||
protected function entityDeleteMultiple($uids) {
|
||||
foreach ($uids as $uid) {
|
||||
user_delete($uid);
|
||||
}
|
||||
user_delete_multiple($uids);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,19 +137,13 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
'#options' => $roles,
|
||||
);
|
||||
}
|
||||
// @todo Implement true updating.
|
||||
$form['update_existing'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Replace existing users'),
|
||||
'#description' => t('If an existing user is found for an imported user, replace it. Existing users will be determined using mappings that are a "unique target".'),
|
||||
'#default_value' => $this->config['update_existing'],
|
||||
);
|
||||
$form['defuse_mail'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Defuse e-mail addresses'),
|
||||
'#description' => t('This appends _test to all imported e-mail addresses to ensure they cannot be used as recipients.'),
|
||||
'#default_value' => $this->config['defuse_mail'],
|
||||
);
|
||||
$form['update_non_existent']['#options'][FEEDS_BLOCK_NON_EXISTENT] = t('Block non-existent users');
|
||||
return $form;
|
||||
}
|
||||
|
||||
@@ -201,11 +205,7 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
);
|
||||
}
|
||||
|
||||
// Let other modules expose mapping targets.
|
||||
self::loadMappers();
|
||||
$entity_type = $this->entityType();
|
||||
$bundle = $this->entityType();
|
||||
drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
|
||||
$this->getHookTargets($targets);
|
||||
|
||||
return $targets;
|
||||
}
|
||||
@@ -239,4 +239,35 @@ class FeedsUserProcessor extends FeedsProcessor {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides FeedsProcessor::clean().
|
||||
*
|
||||
* Block users instead of deleting them.
|
||||
*
|
||||
* @param FeedsState $state
|
||||
* The FeedsState object for the given stage.
|
||||
*/
|
||||
protected function clean(FeedsState $state) {
|
||||
// Delegate to parent if not blocking or option not set.
|
||||
if (!isset($this->config['update_non_existent']) || $this->config['update_non_existent'] !== FEEDS_BLOCK_NON_EXISTENT) {
|
||||
return parent::clean($state);
|
||||
}
|
||||
|
||||
if (!empty($state->removeList)) {
|
||||
// @see user_user_operations_block().
|
||||
// The following foreach is copied from above function but with an added
|
||||
// counter to count blocked users.
|
||||
foreach (user_load_multiple($state->removeList) as $account) {
|
||||
$this->loadItemInfo($account);
|
||||
$account->feeds_item->hash = $this->config['update_non_existent'];
|
||||
// For efficiency manually save the original account before applying any
|
||||
// changes.
|
||||
$account->original = clone $account;
|
||||
user_save($account, array('status' => 0));
|
||||
$state->blocked++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user