summaryrefslogtreecommitdiffstats
path: root/library/Director/PropertyModifier
diff options
context:
space:
mode:
Diffstat (limited to 'library/Director/PropertyModifier')
-rw-r--r--library/Director/PropertyModifier/PropertyModifierArrayElementByPosition.php172
-rw-r--r--library/Director/PropertyModifier/PropertyModifierArrayFilter.php152
-rw-r--r--library/Director/PropertyModifier/PropertyModifierArrayToRow.php68
-rw-r--r--library/Director/PropertyModifier/PropertyModifierArrayUnique.php37
-rw-r--r--library/Director/PropertyModifier/PropertyModifierBitmask.php39
-rw-r--r--library/Director/PropertyModifier/PropertyModifierCombine.php40
-rw-r--r--library/Director/PropertyModifier/PropertyModifierDictionaryToRow.php94
-rw-r--r--library/Director/PropertyModifier/PropertyModifierDnsRecords.php112
-rw-r--r--library/Director/PropertyModifier/PropertyModifierExtractFromDN.php99
-rw-r--r--library/Director/PropertyModifier/PropertyModifierFromAdSid.php36
-rw-r--r--library/Director/PropertyModifier/PropertyModifierFromLatin1.php23
-rw-r--r--library/Director/PropertyModifier/PropertyModifierGetHostByAddr.php53
-rw-r--r--library/Director/PropertyModifier/PropertyModifierGetHostByName.php54
-rw-r--r--library/Director/PropertyModifier/PropertyModifierGetPropertyFromOtherImportSource.php129
-rw-r--r--library/Director/PropertyModifier/PropertyModifierJoin.php34
-rw-r--r--library/Director/PropertyModifier/PropertyModifierJsonDecode.php68
-rw-r--r--library/Director/PropertyModifier/PropertyModifierLConfCustomVar.php48
-rw-r--r--library/Director/PropertyModifier/PropertyModifierListToObject.php95
-rw-r--r--library/Director/PropertyModifier/PropertyModifierLowercase.php17
-rw-r--r--library/Director/PropertyModifier/PropertyModifierMakeBoolean.php90
-rw-r--r--library/Director/PropertyModifier/PropertyModifierMap.php97
-rw-r--r--library/Director/PropertyModifier/PropertyModifierNegateBoolean.php26
-rw-r--r--library/Director/PropertyModifier/PropertyModifierParseURL.php81
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRegexReplace.php45
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRegexSplit.php51
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php147
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRenameColumn.php34
-rw-r--r--library/Director/PropertyModifier/PropertyModifierReplace.php36
-rw-r--r--library/Director/PropertyModifier/PropertyModifierReplaceNull.php33
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSimpleGroupBy.php68
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSkipDuplicates.php26
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSplit.php51
-rw-r--r--library/Director/PropertyModifier/PropertyModifierStripDomain.php38
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSubstring.php54
-rw-r--r--library/Director/PropertyModifier/PropertyModifierToInt.php31
-rw-r--r--library/Director/PropertyModifier/PropertyModifierTrim.php54
-rw-r--r--library/Director/PropertyModifier/PropertyModifierURLEncode.php19
-rw-r--r--library/Director/PropertyModifier/PropertyModifierUpperCaseFirst.php44
-rw-r--r--library/Director/PropertyModifier/PropertyModifierUppercase.php17
-rw-r--r--library/Director/PropertyModifier/PropertyModifierUuidBinToHex.php19
-rw-r--r--library/Director/PropertyModifier/PropertyModifierXlsNumericIp.php26
41 files changed, 2457 insertions, 0 deletions
diff --git a/library/Director/PropertyModifier/PropertyModifierArrayElementByPosition.php b/library/Director/PropertyModifier/PropertyModifierArrayElementByPosition.php
new file mode 100644
index 0000000..0ffc8af
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierArrayElementByPosition.php
@@ -0,0 +1,172 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+use InvalidArgumentException;
+use stdClass;
+
+class PropertyModifierArrayElementByPosition extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Get a specific Array Element';
+ }
+
+ public function hasArraySupport()
+ {
+ return true;
+ }
+
+ /**
+ * @param QuickForm $form
+ * @throws \Zend_Form_Exception
+ */
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'position_type', [
+ 'label' => $form->translate('Position Type'),
+ 'required' => true,
+ 'multiOptions' => $form->optionalEnum([
+ 'first' => $form->translate('First Element'),
+ 'last' => $form->translate('Last Element'),
+ 'fixed' => $form->translate('Specific Element (by position)'),
+ 'keyname' => $form->translate('Specific Element (by key name)'),
+ ]),
+ ]);
+
+ $form->addElement('text', 'position', [
+ 'label' => $form->translate('Position'),
+ 'description' => $form->translate(
+ 'Numeric position or key name'
+ ),
+ ]);
+
+ $form->addElement('select', 'when_missing', [
+ 'label' => $form->translate('When not available'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'What should happen when the specified element is not available?'
+ ),
+ 'value' => 'null',
+ 'multiOptions' => $form->optionalEnum([
+ 'fail' => $form->translate('Let the whole Import Run fail'),
+ 'null' => $form->translate('return NULL'),
+ ])
+ ]);
+ }
+
+ /**
+ * @param $value
+ * @return string|null
+ * @throws ConfigurationError
+ * @throws InvalidArgumentException
+ */
+ public function transform($value)
+ {
+ // First and Last will work with hashes too:
+ if ($value instanceof stdClass) {
+ $value = (array) $value;
+ }
+
+ if (! is_array($value)) {
+ return $this->emptyValue($value);
+ }
+
+ switch ($this->getSetting('position_type')) {
+ case 'first':
+ if (empty($value)) {
+ return $this->emptyValue($value);
+ } else {
+ return array_shift($value);
+ }
+ // https://github.com/squizlabs/PHP_CodeSniffer/pull/1363
+ case 'last':
+ if (empty($value)) {
+ return $this->emptyValue($value);
+ } else {
+ return array_pop($value);
+ }
+ // https://github.com/squizlabs/PHP_CodeSniffer/pull/1363
+ case 'fixed':
+ $pos = $this->getSetting('position');
+ if (! is_int($pos) && ! ctype_digit($pos)) {
+ throw new InvalidArgumentException(sprintf(
+ '"%s" is not a valid array position',
+ $pos
+ ));
+ }
+ $pos = (int) $pos;
+
+ if (array_key_exists($pos, $value)) {
+ return $value[$pos];
+ } else {
+ return $this->emptyValue($value);
+ }
+ // https://github.com/squizlabs/PHP_CodeSniffer/pull/1363
+ case 'keyname':
+ $pos = $this->getSetting('position');
+ if (! is_string($pos)) {
+ throw new InvalidArgumentException(sprintf(
+ '"%s" is not a valid array key name',
+ $pos
+ ));
+ }
+
+ if (array_key_exists($pos, $value)) {
+ return $value[$pos];
+ } else {
+ return $this->emptyValue($value);
+ }
+ // https://github.com/squizlabs/PHP_CodeSniffer/pull/1363
+ default:
+ throw new ConfigurationError(
+ '"%s" is not a valid array position_type',
+ $this->getSetting('position_type')
+ );
+ }
+ }
+
+ /**
+ * @return string
+ * @throws ConfigurationError
+ */
+ protected function getPositionForError()
+ {
+ switch ($this->getSetting('position_type')) {
+ case 'first':
+ return 'first';
+ case 'last':
+ return 'last';
+ case 'fixed':
+ return '#' . $this->getSetting('position');
+ case 'keyname':
+ return '#' . $this->getSetting('position');
+ default:
+ throw new ConfigurationError(
+ '"%s" is not a valid array position_type',
+ $this->getSetting('position_type')
+ );
+ }
+ }
+
+ /**
+ * @param $value
+ * @return null
+ * @throws ConfigurationError
+ */
+ protected function emptyValue($value)
+ {
+ if ($this->getSetting('when_missing', 'fail') === 'null') {
+ return null;
+ } else {
+ throw new InvalidArgumentException(sprintf(
+ 'There is no %s element in %s',
+ $this->getPositionForError(),
+ json_encode($value)
+ ));
+ }
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierArrayFilter.php b/library/Director/PropertyModifier/PropertyModifierArrayFilter.php
new file mode 100644
index 0000000..0b52987
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierArrayFilter.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierArrayFilter extends PropertyModifierHook
+{
+ /** @var FilterExpression */
+ private $filterExpression;
+
+ public function getName()
+ {
+ return 'Filter Array Values';
+ }
+
+ public function hasArraySupport()
+ {
+ return true;
+ }
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'filter_method', array(
+ 'label' => $form->translate('Filter method'),
+ 'required' => true,
+ 'value' => 'wildcard',
+ 'multiOptions' => $form->optionalEnum(array(
+ 'wildcard' => $form->translate('Simple match with wildcards (*)'),
+ 'regex' => $form->translate('Regular Expression'),
+ )),
+ ));
+
+ $form->addElement('text', 'filter_string', array(
+ 'label' => 'Filter',
+ 'description' => $form->translate(
+ 'The string/pattern you want to search for. Depends on the'
+ . ' chosen method, use www.* or *linux* for wildcard matches'
+ . ' and expression like /^www\d+\./ in case you opted for a'
+ . ' regular expression'
+ ),
+ 'required' => true,
+ ));
+
+ $form->addElement('select', 'policy', array(
+ 'label' => $form->translate('Policy'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'What should happen with matching elements?'
+ ),
+ 'value' => 'keep',
+ 'multiOptions' => array(
+ 'keep' => $form->translate('Keep matching elements'),
+ 'reject' => $form->translate('Reject matching elements'),
+ ),
+ ));
+
+ $form->addElement('select', 'when_empty', array(
+ 'label' => $form->translate('When empty'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'What should happen when the result array is empty?'
+ ),
+ 'value' => 'empty_array',
+ 'multiOptions' => $form->optionalEnum(array(
+ 'empty_array' => $form->translate('return an empty array'),
+ 'null' => $form->translate('return NULL'),
+ ))
+ ));
+ }
+
+ public function matchesRegexp($string, $expression)
+ {
+ return preg_match($expression, $string);
+ }
+
+ public function matchesWildcard($string, $expression)
+ {
+ return $this->filterExpression->matches(
+ (object) array('value' => $string)
+ );
+ }
+
+ public function transform($value)
+ {
+ if (empty($value)) {
+ return $this->emptyValue();
+ }
+
+ if (is_string($value)) {
+ $value = [$value];
+ }
+
+ if (! is_array($value)) {
+ throw new InvalidPropertyException(
+ 'The ArrayFilter property modifier be applied to arrays only'
+ );
+ }
+
+ $method = $this->getSetting('filter_method');
+ $filter = $this->getSetting('filter_string');
+ $policy = $this->getSetting('policy');
+
+ switch ($method) {
+ case 'wildcard':
+ $func = 'matchesWildcard';
+ $this->filterExpression = new FilterExpression('value', '=', $filter);
+ break;
+ case 'regex':
+ $func = 'matchesRegexp';
+ break;
+ default:
+ throw new ConfigurationError(
+ '%s is not a valid value for an ArrayFilter filter_method',
+ var_export($method, 1)
+ );
+ }
+
+ $result = array();
+
+ foreach ($value as $val) {
+ if ($this->$func($val, $filter)) {
+ if ($policy === 'keep') {
+ $result[] = $val;
+ }
+ } else {
+ if ($policy === 'reject') {
+ $result[] = $val;
+ }
+ }
+ }
+
+ if (empty($result)) {
+ return $this->emptyValue();
+ }
+
+ return $result;
+ }
+
+ protected function emptyValue()
+ {
+ if ($this->getSetting('when_empty', 'empty_array') === 'empty_array') {
+ return array();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierArrayToRow.php b/library/Director/PropertyModifier/PropertyModifierArrayToRow.php
new file mode 100644
index 0000000..35ae063
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierArrayToRow.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+use InvalidArgumentException;
+use ipl\Html\Error;
+
+class PropertyModifierArrayToRow extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Clone the row for every entry of an Array';
+ }
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'on_empty', [
+ 'label' => 'When empty',
+ 'description' => $form->translate('What should we do in case the given value is empty?'),
+ 'multiOptions' => $form->optionalEnum([
+ 'reject' => $form->translate('Drop the current row'),
+ 'fail' => $form->translate('Let the whole import run fail'),
+ 'keep' => $form->translate('Keep the row, set the column value to null'),
+ ]),
+ 'value' => 'reject',
+ 'required' => true,
+ ]);
+ }
+
+ public function hasArraySupport()
+ {
+ return true;
+ }
+
+ public function expandsRows()
+ {
+ return true;
+ }
+
+ public function transform($value)
+ {
+ if (empty($value)) {
+ $onDuplicate = $this->getSetting('on_empty', 'reject');
+ switch ($onDuplicate) {
+ case 'reject':
+ return [];
+ case 'keep':
+ return [null];
+ case 'fail':
+ throw new InvalidArgumentException('Failed to clone row, value is empty');
+ default:
+ throw new InvalidArgumentException(
+ "'$onDuplicate' is not a valid 'on_duplicate' setting"
+ );
+ }
+ }
+
+ if (! \is_array($value)) {
+ throw new InvalidArgumentException(
+ "Array required to clone this row, got " . Error::getPhpTypeName($value)
+ );
+ }
+
+ return $value;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierArrayUnique.php b/library/Director/PropertyModifier/PropertyModifierArrayUnique.php
new file mode 100644
index 0000000..e3446f9
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierArrayUnique.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use function array_unique;
+use function array_values;
+use function is_array;
+
+class PropertyModifierArrayUnique extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Unique Array Values';
+ }
+
+ public function hasArraySupport()
+ {
+ return true;
+ }
+
+ public function transform($value)
+ {
+ if (empty($value)) {
+ return $value;
+ }
+
+ if (! is_array($value)) {
+ throw new InvalidPropertyException(
+ 'The ArrayUnique property modifier can be applied to arrays only'
+ );
+ }
+
+ return array_values(array_unique($value));
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierBitmask.php b/library/Director/PropertyModifier/PropertyModifierBitmask.php
new file mode 100644
index 0000000..d334f09
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierBitmask.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierBitmask extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'bitmask', array(
+ 'label' => 'Bitmask',
+ 'description' => $form->translate(
+ 'The numeric bitmask you want to apply. In case you have a hexadecimal'
+ . ' or binary mask please transform it to a decimal number first. The'
+ . ' result of this modifier is a boolean value, telling whether the'
+ . ' given mask applies to the numeric value in your source column'
+ ),
+ 'required' => true,
+ ));
+ }
+
+ public function getName()
+ {
+ return 'Bitmask match (numeric)';
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $mask = (int) $this->getSetting('bitmask');
+ return (((int) $value) & $mask) === $mask;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierCombine.php b/library/Director/PropertyModifier/PropertyModifierCombine.php
new file mode 100644
index 0000000..5be09ea
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierCombine.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Import\SyncUtils;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierCombine extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'pattern', array(
+ 'label' => $form->translate('Pattern'),
+ 'required' => false,
+ 'description' => $form->translate(
+ 'This pattern will be evaluated, and variables like ${some_column}'
+ . ' will be filled accordingly. A typical use-case is generating'
+ . ' unique service identifiers via ${host}!${service} in case your'
+ . ' data source doesn\'t allow you to ship such. The chosen "property"'
+ . ' has no effect here and will be ignored.'
+ )
+ ));
+ }
+
+ public function getName()
+ {
+ return 'Combine multiple properties';
+ }
+
+ public function requiresRow()
+ {
+ return true;
+ }
+
+ public function transform($value)
+ {
+ return SyncUtils::fillVariables($this->getSetting('pattern'), $this->getRow());
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierDictionaryToRow.php b/library/Director/PropertyModifier/PropertyModifierDictionaryToRow.php
new file mode 100644
index 0000000..2a60ab3
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierDictionaryToRow.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Data\InvalidDataException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+use InvalidArgumentException;
+use ipl\Html\Error;
+
+class PropertyModifierDictionaryToRow extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Clone the row for every entry of a nested Dictionary/Hash structure';
+ }
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'key_column', [
+ 'label' => $form->translate('Key Property Name'),
+ 'description' => $form->translate(
+ 'Every Dictionary entry has a key, its value will be provided in this column'
+ )
+ ]);
+ $form->addElement('select', 'on_empty', [
+ 'label' => $form->translate('When empty'),
+ 'description' => $form->translate('What should we do in case the given value is empty?'),
+ 'multiOptions' => $form->optionalEnum([
+ 'reject' => $form->translate('Drop the current row'),
+ 'fail' => $form->translate('Let the whole import run fail'),
+ 'keep' => $form->translate('Keep the row, set the column value to null'),
+ ]),
+ 'value' => 'reject',
+ 'required' => true,
+ ]);
+ }
+
+ public function requiresRow()
+ {
+ return true;
+ }
+
+ public function hasArraySupport()
+ {
+ return true;
+ }
+
+ public function expandsRows()
+ {
+ return true;
+ }
+
+ public function transform($value)
+ {
+ if (empty($value)) {
+ $onDuplicate = $this->getSetting('on_empty', 'reject');
+ switch ($onDuplicate) {
+ case 'reject':
+ return [];
+ case 'keep':
+ return [null];
+ case 'fail':
+ throw new InvalidArgumentException('Failed to clone row, value is empty');
+ default:
+ throw new InvalidArgumentException(
+ "'$onDuplicate' is not a valid 'on_duplicate' setting"
+ );
+ }
+ }
+
+ $keyColumn = $this->getSetting('key_column');
+
+ if (! \is_object($value)) {
+ throw new InvalidArgumentException(
+ "Object required to clone this row, got " . Error::getPhpTypeName($value)
+ );
+ }
+ $result = [];
+ foreach ($value as $key => $properties) {
+ if (! is_object($properties)) {
+ throw new InvalidDataException(
+ sprintf('Nested "%s" dictionary', $key),
+ $properties
+ );
+ }
+
+ $properties->$keyColumn = $key;
+ $result[] = $properties;
+ }
+
+ return $result;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierDnsRecords.php b/library/Director/PropertyModifier/PropertyModifierDnsRecords.php
new file mode 100644
index 0000000..d5d8d41
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierDnsRecords.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierDnsRecords extends PropertyModifierHook
+{
+ protected static $types = array(
+ 'A' => DNS_A,
+ 'AAAA' => DNS_AAAA,
+ 'CNAME' => DNS_CNAME,
+ 'MX' => DNS_MX,
+ 'NS' => DNS_NS,
+ 'PTR' => DNS_PTR,
+ 'TXT' => DNS_TXT,
+ );
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'record_type', array(
+ 'label' => 'Record type',
+ 'description' => $form->translate('DNS record type'),
+ 'multiOptions' => $form->optionalEnum(static::enumTypes()),
+ 'required' => true,
+ ));
+
+ $form->addElement('select', 'on_failure', array(
+ 'label' => 'On failure',
+ 'description' => $form->translate('What should we do if the DNS lookup fails?'),
+ 'multiOptions' => $form->optionalEnum(array(
+ 'null' => $form->translate('Set no value (null)'),
+ 'keep' => $form->translate('Keep the property as is'),
+ 'fail' => $form->translate('Let the whole import run fail'),
+ )),
+ 'required' => true,
+ ));
+ }
+
+ protected static function enumTypes()
+ {
+ $types = array_keys(self::$types);
+ return array_combine($types, $types);
+ }
+
+ public function getName()
+ {
+ return 'Get DNS records of a specific type';
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $type = self::$types[$this->getSetting('record_type')];
+ $response = dns_get_record($value, $type);
+
+ if ($response === false) {
+ switch ($this->getSetting('on_failure')) {
+ case 'null':
+ return null;
+ case 'keep':
+ return $value;
+ case 'fail':
+ default:
+ throw new InvalidPropertyException(
+ 'DNS lookup failed for "%s"',
+ $value
+ );
+ }
+ }
+
+ $result = array();
+ switch ($type) {
+ case DNS_A:
+ return $this->extractProperty('ip', $response);
+ case DNS_AAAA:
+ return $this->extractProperty('ipv6', $response);
+ case DNS_CNAME:
+ case DNS_MX:
+ case DNS_NS:
+ case DNS_PTR:
+ return $this->extractProperty('target', $response);
+ case DNS_TXT:
+ return $this->extractProperty('txt', $response);
+ return $response;
+ }
+
+ return $result;
+ }
+
+ protected function extractProperty($key, $response)
+ {
+ $result = array();
+ foreach ($response as $entry) {
+ $result[] = $entry[$key];
+ }
+
+ if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
+ sort($result, SORT_NATURAL);
+ } else {
+ natsort($result);
+ $result = array_values($result);
+ }
+
+ return $result;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierExtractFromDN.php b/library/Director/PropertyModifier/PropertyModifierExtractFromDN.php
new file mode 100644
index 0000000..c79c5b2
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierExtractFromDN.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+use Icinga\Protocol\Ldap\LdapUtils;
+
+class PropertyModifierExtractFromDN extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'dn_component', array(
+ 'label' => $form->translate('DN component'),
+ 'description' => $form->translate('What should we extract from the DN?'),
+ 'multiOptions' => $form->optionalEnum(array(
+ 'cn' => $form->translate('The first (leftmost) CN'),
+ 'ou' => $form->translate('The first (leftmost) OU'),
+ 'first' => $form->translate('Any first (leftmost) component'),
+ 'last_ou' => $form->translate('The last (rightmost) OU'),
+ )),
+ 'required' => true,
+ ));
+
+ $form->addElement('select', 'on_failure', array(
+ 'label' => $form->translate('On failure'),
+ 'description' => $form->translate('What should we do if the desired part does not exist?'),
+ 'multiOptions' => $form->optionalEnum(array(
+ 'null' => $form->translate('Set no value (null)'),
+ 'keep' => $form->translate('Keep the DN as is'),
+ 'fail' => $form->translate('Let the whole import run fail'),
+ )),
+ 'required' => true,
+ ));
+ }
+
+ public function getName()
+ {
+ return 'Extract from a Distinguished Name (DN)';
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $parts = LdapUtils::explodeDN($value);
+ $result = null;
+
+ switch ($this->getSetting('dn_component')) {
+ case 'cn':
+ $result = $this->extractFirst($parts, 'cn');
+ break;
+ case 'ou':
+ $result = $this->extractFirst($parts, 'ou');
+ break;
+ case 'last_ou':
+ $result = $this->extractFirst(array_reverse($parts), 'ou');
+ break;
+ case 'first':
+ $result = $this->extractFirst($parts);
+ break;
+ }
+
+ if ($result === null) {
+ switch ($this->getSetting('on_failure')) {
+ case 'null':
+ return null;
+ case 'keep':
+ return $value;
+ case 'fail':
+ default:
+ throw new InvalidPropertyException(
+ 'DN part extraction failed for %s',
+ var_export($value, 1)
+ );
+ }
+ }
+
+ return $result;
+ }
+
+ protected function extractFirst($parts, $what = null)
+ {
+ foreach ($parts as $part) {
+ if (false === ($pos = strpos($part, '='))) {
+ continue;
+ }
+
+ if (null === $what || strtolower(substr($part, 0, $pos)) === $what) {
+ return substr($part, $pos + 1);
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierFromAdSid.php b/library/Director/PropertyModifier/PropertyModifierFromAdSid.php
new file mode 100644
index 0000000..ee306e3
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierFromAdSid.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+
+class PropertyModifierFromAdSid extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Decode a binary object SID (MSAD)';
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ // Strongly inspired by
+ // http://www.chadsikorra.com/blog/decoding-and-encoding-active-directory-objectsid-php
+ //
+ // Not perfect yet, but should suffice for now. When improving this please also see:
+ // https://blogs.msdn.microsoft.com/oldnewthing/20040315-00/?p=40253
+
+ $sid = $value;
+ $sidHex = unpack('H*hex', $value);
+ $sidHex = $sidHex['hex'];
+ $subAuths = implode('-', unpack('H2/H2/n/N/V*', $sid));
+
+ $revLevel = hexdec(substr($sidHex, 0, 2));
+ $authIdent = hexdec(substr($sidHex, 4, 12));
+
+ return sprintf('S-%s-%s-%s', $revLevel, $authIdent, $subAuths);
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierFromLatin1.php b/library/Director/PropertyModifier/PropertyModifierFromLatin1.php
new file mode 100644
index 0000000..272956c
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierFromLatin1.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use function iconv;
+
+class PropertyModifierFromLatin1 extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Convert a latin1 string to utf8';
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ return iconv('ISO-8859-15', 'UTF-8', $value);
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierGetHostByAddr.php b/library/Director/PropertyModifier/PropertyModifierGetHostByAddr.php
new file mode 100644
index 0000000..2b0f9c3
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierGetHostByAddr.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierGetHostByAddr extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'on_failure', array(
+ 'label' => 'On failure',
+ 'description' => $form->translate('What should we do if the host (DNS) lookup fails?'),
+ 'multiOptions' => $form->optionalEnum(array(
+ 'null' => $form->translate('Set no value (null)'),
+ 'keep' => $form->translate('Keep the property (hostname) as is'),
+ 'fail' => $form->translate('Let the whole import run fail'),
+ )),
+ 'required' => true,
+ ));
+ }
+
+ public function getName()
+ {
+ return mt('director', 'Get host by address (Reverse DNS lookup)');
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+ $host = gethostbyaddr($value);
+ if ($host === false) {
+ switch ($this->getSetting('on_failure')) {
+ case 'null':
+ return null;
+ case 'keep':
+ return $value;
+ case 'fail':
+ default:
+ throw new InvalidPropertyException(
+ 'Reverse Host lookup failed for "%s"',
+ $value
+ );
+ }
+ }
+
+ return $host;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierGetHostByName.php b/library/Director/PropertyModifier/PropertyModifierGetHostByName.php
new file mode 100644
index 0000000..36884e8
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierGetHostByName.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierGetHostByName extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'on_failure', array(
+ 'label' => 'On failure',
+ 'description' => $form->translate('What should we do if the host (DNS) lookup fails?'),
+ 'multiOptions' => $form->optionalEnum(array(
+ 'null' => $form->translate('Set no value (null)'),
+ 'keep' => $form->translate('Keep the property (hostname) as is'),
+ 'fail' => $form->translate('Let the whole import run fail'),
+ )),
+ 'required' => true,
+ ));
+ }
+
+ public function getName()
+ {
+ return mt('director', 'Get host by name (DNS lookup)');
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $host = gethostbyname($value);
+ if (strlen(@inet_pton($host)) !== 4) {
+ switch ($this->getSetting('on_failure')) {
+ case 'null':
+ return null;
+ case 'keep':
+ return $value;
+ case 'fail':
+ default:
+ throw new InvalidPropertyException(
+ 'Host lookup failed for "%s"',
+ $value
+ );
+ }
+ }
+
+ return $host;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierGetPropertyFromOtherImportSource.php b/library/Director/PropertyModifier/PropertyModifierGetPropertyFromOtherImportSource.php
new file mode 100644
index 0000000..d57b427
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierGetPropertyFromOtherImportSource.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Objects\ImportRowModifier;
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierGetPropertyFromOtherImportSource extends PropertyModifierHook
+{
+ protected $importSource;
+
+ private $importedData;
+
+ public function getName()
+ {
+ return 'Get a property from another Import Source';
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Zend_Form_Exception
+ */
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ if (! $form instanceof DirectorObjectForm) {
+ throw new \RuntimeException('This property modifier works only with a DirectorObjectForm');
+ }
+ $db = $form->getDb();
+ $form->addElement('select', 'import_source_id', [
+ 'label' => $form->translate('Import Source'),
+ 'description' => $form->translate(
+ 'Another Import Source. We\'re going to look up the row with the'
+ . ' key matching the value in the chosen column'
+ ),
+ 'required' => true,
+ 'multiOptions' => $form->optionalEnum($db->enumImportSource()),
+ 'class' => 'autosubmit',
+ ]);
+
+ if ($form->hasBeenSent()) {
+ $sourceId = $form->getSentValue('import_source_id');
+ } else {
+ $object = $form->getObject();
+ if ($object instanceof ImportRowModifier) {
+ $sourceId = $object->getSetting('import_source_id');
+ } else {
+ $sourceId = null;
+ }
+ }
+ $extra = [];
+ if ($sourceId) {
+ $extra = [
+ 'class' => 'director-suggest',
+ 'data-suggestion-context' => 'importsourceproperties!' . (int) $sourceId,
+ ];
+ }
+ $form->addElement('text', 'foreign_property', [
+ 'label' => $form->translate('Property'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'The property to get from the row we found in the chosen Import Source'
+ ),
+ ] + $extra);
+ }
+
+ /**
+ * @param $settings
+ * @return PropertyModifierHook
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function setSettings(array $settings)
+ {
+ if (isset($settings['import_source'])) {
+ $settings['import_source_id'] = ImportSource::load(
+ $settings['import_source'],
+ $this->getDb()
+ )->get('id');
+ unset($settings['import_source']);
+ }
+
+ return parent::setSettings($settings);
+ }
+
+ public function transform($value)
+ {
+ $data = $this->getImportedData();
+
+ if (isset($data[$value])) {
+ return $data[$value]->{$this->getSetting('foreign_property')};
+ } else {
+ return null;
+ }
+ }
+
+ public function exportSettings()
+ {
+ $settings = parent::exportSettings();
+ $settings->import_source = $this->getImportSource()->getObjectName();
+ unset($settings->import_source_id);
+
+ return $settings;
+ }
+
+ protected function & getImportedData()
+ {
+ if ($this->importedData === null) {
+ $this->importedData = $this->getImportSource()
+ ->fetchLastRun(true)
+ ->fetchRows([$this->getSetting('foreign_property')]);
+ }
+
+ return $this->importedData;
+ }
+
+ protected function getImportSource()
+ {
+ if ($this->importSource === null) {
+ $this->importSource = ImportSource::loadWithAutoIncId(
+ $this->getSetting('import_source_id'),
+ $this->getDb()
+ );
+ }
+
+ return $this->importSource;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierJoin.php b/library/Director/PropertyModifier/PropertyModifierJoin.php
new file mode 100644
index 0000000..daa6fdb
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierJoin.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierJoin extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'glue', array(
+ 'label' => $form->translate('Glue'),
+ 'required' => false,
+ 'description' => $form->translate(
+ 'One or more characters that will be used to glue an input array to a string. Can be left empty'
+ )
+ ));
+ }
+
+ public function hasArraySupport()
+ {
+ return true;
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ return implode($this->getSetting('glue'), $value);
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierJsonDecode.php b/library/Director/PropertyModifier/PropertyModifierJsonDecode.php
new file mode 100644
index 0000000..f6b9af8
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierJsonDecode.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Exception\JsonException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierJsonDecode extends PropertyModifierHook
+{
+ /**
+ * @param QuickForm $form
+ * @return QuickForm|void
+ * @throws \Zend_Form_Exception
+ */
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'on_failure', array(
+ 'label' => 'On failure',
+ 'description' => $form->translate(
+ 'What should we do in case we are unable to decode the given string?'
+ ),
+ 'multiOptions' => $form->optionalEnum(array(
+ 'null' => $form->translate('Set no value (null)'),
+ 'keep' => $form->translate('Keep the JSON string as is'),
+ 'fail' => $form->translate('Let the whole import run fail'),
+ )),
+ 'required' => true,
+ ));
+ }
+
+ public function getName()
+ {
+ return 'Decode a JSON string';
+ }
+
+ /**
+ * @param $value
+ * @return mixed|null
+ * @throws InvalidPropertyException
+ */
+ public function transform($value)
+ {
+ if (null === $value) {
+ return $value;
+ }
+
+ $decoded = @json_decode($value);
+ if ($decoded === null && JSON_ERROR_NONE !== json_last_error()) {
+ switch ($this->getSetting('on_failure')) {
+ case 'null':
+ return null;
+ case 'keep':
+ return $value;
+ case 'fail':
+ default:
+ throw new InvalidPropertyException(
+ 'JSON decoding failed with "%s" for %s',
+ JsonException::getJsonErrorMessage(json_last_error()),
+ substr($value, 0, 128)
+ );
+ }
+ }
+
+ return $decoded;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierLConfCustomVar.php b/library/Director/PropertyModifier/PropertyModifierLConfCustomVar.php
new file mode 100644
index 0000000..35db6c8
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierLConfCustomVar.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+
+class PropertyModifierLConfCustomVar extends PropertyModifierHook
+{
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $vars = (object) array();
+ $this->extractLConfVars($value, $vars);
+
+ return $vars;
+ }
+
+ public function getName()
+ {
+ return 'Transform LConf CustomVars to Hash';
+ }
+
+ public function hasArraySupport()
+ {
+ return true;
+ }
+
+ protected function extractLConfVars($value, $vars)
+ {
+ if (is_string($value)) {
+ $this->extractLConfVar($value, $vars);
+ } elseif (is_array($value)) {
+ foreach ($value as $val) {
+ $this->extractLConfVar($val, $vars);
+ }
+ }
+ }
+
+ protected function extractLConfVar($value, $vars)
+ {
+ list($key, $val) = preg_split('/ /', $value, 2);
+ $key = ltrim($key, '_');
+ $vars->$key = $val;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierListToObject.php b/library/Director/PropertyModifier/PropertyModifierListToObject.php
new file mode 100644
index 0000000..9889c8f
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierListToObject.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+use InvalidArgumentException;
+use ipl\Html\Error;
+
+class PropertyModifierListToObject extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'key_property', [
+ 'label' => $form->translate('Key Property'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'Each Array in the list must contain this property. It\'s value'
+ . ' will be used as the key/object property name for the row.'
+ )
+ ]);
+ $form->addElement('select', 'on_duplicate', [
+ 'label' => 'On duplicate key',
+ 'description' => $form->translate('What should we do, if the same key occurs twice?'),
+ 'multiOptions' => $form->optionalEnum([
+ 'fail' => $form->translate('Let the whole import run fail'),
+ 'keep_first' => $form->translate('Keep the first row with that key'),
+ 'keep_last' => $form->translate('Keep the last row with that key'),
+ ]),
+ 'required' => true,
+ ]);
+ }
+
+ public function getName()
+ {
+ return 'Transform Array/Object list into single Object';
+ }
+
+ public function hasArraySupport()
+ {
+ return true;
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+ if (! \is_array($value)) {
+ throw new InvalidArgumentException(
+ 'Array expected, got ' . Error::getPhpTypeName($value)
+ );
+ }
+ $keyProperty = $this->getSetting('key_property');
+ $onDuplicate = $this->getSetting('on_duplicate');
+ $result = (object) [];
+ foreach ($value as $key => $row) {
+ if (\is_object($row)) {
+ $row = (array) $row;
+ }
+ if (! \is_array($row)) {
+ throw new InvalidArgumentException(
+ "List of Arrays expected expected. Array entry '$key' is "
+ . Error::getPhpTypeName($value)
+ );
+ }
+
+ if (! \array_key_exists($keyProperty, $row)) {
+ throw new InvalidArgumentException(
+ "Key property '$keyProperty' is required, but missing on row '$key'"
+ );
+ }
+
+ $property = $row[$keyProperty];
+ if (isset($result->$property)) {
+ switch ($onDuplicate) {
+ case 'fail':
+ throw new InvalidArgumentException(
+ "Duplicate row with $keyProperty=$property found on row '$key'"
+ );
+ case 'keep_first':
+ // Do nothing
+ break;
+ case 'keep_last':
+ $result->$property = (object) $row;
+ break;
+ }
+ } else {
+ $result->$property = (object) $row;
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierLowercase.php b/library/Director/PropertyModifier/PropertyModifierLowercase.php
new file mode 100644
index 0000000..1fdbb4d
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierLowercase.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+
+class PropertyModifierLowercase extends PropertyModifierHook
+{
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ return \mb_strtolower($value, 'UTF-8');
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierMakeBoolean.php b/library/Director/PropertyModifier/PropertyModifierMakeBoolean.php
new file mode 100644
index 0000000..ed91bcf
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierMakeBoolean.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierMakeBoolean extends PropertyModifierHook
+{
+ protected static $validStrings = array(
+ '0' => false,
+ 'false' => false,
+ 'n' => false,
+ 'no' => false,
+ '1' => true,
+ 'true' => true,
+ 'y' => true,
+ 'yes' => true,
+ );
+
+ public function getName()
+ {
+ return 'Convert to a boolean value';
+ }
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'on_invalid', array(
+ 'label' => 'Invalid properties',
+ 'required' => true,
+ 'description' => $form->translate(
+ 'This modifier transforms 0/"0"/false/"false"/"n"/"no" to false'
+ . ' and 1, "1", true, "true", "y" and "yes" to true, both in a'
+ . ' case insensitive way. What should happen if the given value'
+ . ' does not match any of those?'
+ . ' You could return a null value, or default to false or true.'
+ . ' You might also consider interrupting the whole import process'
+ . ' as of invalid source data'
+ ),
+ 'multiOptions' => $form->optionalEnum(array(
+ 'null' => $form->translate('Set null'),
+ 'true' => $form->translate('Set true'),
+ 'false' => $form->translate('Set false'),
+ 'fail' => $form->translate('Let the import fail'),
+ )),
+ ));
+ }
+
+ public function transform($value)
+ {
+ if ($value === false || $value === true || $value === null) {
+ return $value;
+ }
+
+ if ($value === 0) {
+ return false;
+ }
+
+ if ($value === 1) {
+ return true;
+ }
+
+ if (is_string($value)) {
+ $value = strtolower($value);
+
+ if (array_key_exists($value, self::$validStrings)) {
+ return self::$validStrings[$value];
+ }
+ }
+
+ switch ($this->getSetting('on_invalid')) {
+ case 'null':
+ return null;
+
+ case 'false':
+ return false;
+
+ case 'true':
+ return true;
+
+ case 'fail':
+ default:
+ throw new InvalidPropertyException(
+ '"%s" cannot be converted to a boolean value',
+ $value
+ );
+ }
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierMap.php b/library/Director/PropertyModifier/PropertyModifierMap.php
new file mode 100644
index 0000000..a6cb422
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierMap.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierMap extends PropertyModifierHook
+{
+ private $cache;
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'datalist_id', array(
+ 'label' => 'Lookup list',
+ 'required' => true,
+ 'description' => $form->translate(
+ 'Please choose a data list that can be used for map lookups'
+ ),
+ 'multiOptions' => $form->optionalEnum($form->getDb()->enumDatalist()),
+ ));
+
+ $form->addElement('select', 'on_missing', array(
+ 'label' => 'Missing entries',
+ 'required' => true,
+ 'description' => $form->translate(
+ 'What should happen if the lookup key does not exist in the data list?'
+ . ' You could return a null value, keep the unmodified imported value'
+ . ' or interrupt the import process'
+ ),
+ 'multiOptions' => $form->optionalEnum(array(
+ 'null' => $form->translate('Set null'),
+ 'keep' => $form->translate('Return lookup key unmodified'),
+ 'fail' => $form->translate('Let the import fail'),
+ )),
+ ));
+
+ // TODO: ignore case
+ }
+
+ public function transform($value)
+ {
+ $this->loadCache();
+ if (array_key_exists($value, $this->cache)) {
+ return $this->cache[$value];
+ }
+
+ switch ($this->getSetting('on_missing')) {
+ case 'null':
+ return null;
+
+ case 'keep':
+ return $value;
+
+ case 'fail':
+ default:
+ throw new InvalidPropertyException(
+ '"%s" cannot be found in the "%s" data list',
+ $value,
+ $this->getDatalistName()
+ );
+ }
+ }
+
+ protected function getDatalistName()
+ {
+ $db = $this->getDb()->getDbAdapter();
+ $query = $db->select()->from(
+ 'director_datalist',
+ 'list_name'
+ )->where(
+ 'id = ?',
+ $this->getSetting('datalist_id')
+ );
+ $result = $db->fetchOne($query);
+
+ return $result;
+ }
+
+ protected function loadCache($force = false)
+ {
+ if ($this->cache === null || $force) {
+ $this->cache = array();
+ $db = $this->getDb()->getDbAdapter();
+ $select = $db->select()->from(
+ 'director_datalist_entry',
+ array('entry_name', 'entry_value')
+ )->where('list_id = ?', $this->getSetting('datalist_id'))
+ ->order('entry_value');
+
+ $this->cache = $db->fetchPairs($select);
+ }
+
+ return $this;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierNegateBoolean.php b/library/Director/PropertyModifier/PropertyModifierNegateBoolean.php
new file mode 100644
index 0000000..e60d692
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierNegateBoolean.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use function ipl\Stdlib\get_php_type;
+
+class PropertyModifierNegateBoolean extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Negate a boolean value';
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return true;
+ }
+ if (! is_bool($value)) {
+ throw new \InvalidArgumentException('Boolean expected, got ' . get_php_type($value));
+ }
+
+ return ! $value;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierParseURL.php b/library/Director/PropertyModifier/PropertyModifierParseURL.php
new file mode 100644
index 0000000..ce7c81b
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierParseURL.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierParseURL extends PropertyModifierHook
+{
+
+ /**
+ * Array with possible components that can be returned from URL.
+ */
+ protected static $components = [
+ 'scheme' => PHP_URL_SCHEME,
+ 'host' => PHP_URL_HOST,
+ 'port' => PHP_URL_PORT,
+ 'path' => PHP_URL_PATH,
+ 'query' => PHP_URL_QUERY,
+ 'fragment' => PHP_URL_FRAGMENT,
+ ];
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'url_component', [
+ 'label' => $form->translate('URL component'),
+ 'description' => $form->translate('URL component'),
+ 'multiOptions' => $form->optionalEnum(static::enumComponents()),
+ 'required' => true,
+ ]);
+
+ $form->addElement('select', 'on_failure', [
+ 'label' => $form->translate('On failure'),
+ 'description' => $form->translate(
+ 'What should we do if the URL could not get parsed or component not found?'
+ ),
+ 'multiOptions' => $form->optionalEnum([
+ 'null' => $form->translate('Set no value (null)'),
+ 'keep' => $form->translate('Keep the property as is'),
+ 'fail' => $form->translate('Let the whole import run fail'),
+ ]),
+ 'required' => true,
+ ]);
+ }
+
+ protected static function enumComponents()
+ {
+ $components = array_keys(self::$components);
+ return array_combine($components, $components);
+ }
+
+ public function getName()
+ {
+ return 'Parse a URL and return its components';
+ }
+
+ public function transform($value)
+ {
+ $component = self::$components[$this->getSetting('url_component')];
+ $response = parse_url($value, $component);
+
+ // if component not found $response will be null, false if seriously malformed URL
+ if ($response === null || $response === false) {
+ switch ($this->getSetting('on_failure')) {
+ case 'null':
+ return null;
+ case 'keep':
+ return $value;
+ case 'fail':
+ default:
+ throw new InvalidPropertyException(
+ 'Parsing URL "%s" failed.',
+ $value
+ );
+ }
+ }
+
+ return $response;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierRegexReplace.php b/library/Director/PropertyModifier/PropertyModifierRegexReplace.php
new file mode 100644
index 0000000..59cb245
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierRegexReplace.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierRegexReplace extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'pattern', array(
+ 'label' => 'Regex pattern',
+ 'description' => $form->translate(
+ 'The pattern you want to search for. This can be a regular expression like /^www\d+\./'
+ ),
+ 'required' => true,
+ ));
+
+ $form->addElement('text', 'replacement', array(
+ 'label' => 'Replacement',
+ 'description' => $form->translate(
+ 'The string that should be used as a preplacement'
+ ),
+ ));
+ }
+
+ public function getName()
+ {
+ return 'Regular expression based replacement';
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ return preg_replace(
+ $this->getSetting('pattern'),
+ $this->getSetting('replacement'),
+ $value
+ );
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierRegexSplit.php b/library/Director/PropertyModifier/PropertyModifierRegexSplit.php
new file mode 100644
index 0000000..829810f
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierRegexSplit.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierRegexSplit extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'pattern', array(
+ 'label' => $form->translate('Pattern'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'Regular expression pattern to split the string (e.g. /\s+/ or /[,;]/)'
+ )
+ ));
+
+ $form->addElement('select', 'when_empty', array(
+ 'label' => $form->translate('When empty'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'What should happen when the given string is empty?'
+ ),
+ 'value' => 'empty_array',
+ 'multiOptions' => $form->optionalEnum(array(
+ 'empty_array' => $form->translate('return an empty array'),
+ 'null' => $form->translate('return NULL'),
+ ))
+ ));
+ }
+
+ public function transform($value)
+ {
+ if (! strlen(trim($value))) {
+ if ($this->getSetting('when_empty', 'empty_array') === 'empty_array') {
+ return array();
+ } else {
+ return null;
+ }
+ }
+
+ return preg_split(
+ $this->getSetting('pattern'),
+ $value,
+ -1,
+ PREG_SPLIT_NO_EMPTY
+ );
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php b/library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php
new file mode 100644
index 0000000..1485d5d
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierRejectOrSelect extends PropertyModifierHook
+{
+ /** @var FilterExpression */
+ private $filterExpression;
+
+ public function getName()
+ {
+ return mt('director', 'Reject or keep rows based on property value');
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Zend_Form_Exception
+ */
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'filter_method', [
+ 'label' => $form->translate('Filter method'),
+ 'required' => true,
+ 'value' => 'wildcard',
+ 'multiOptions' => $form->optionalEnum([
+ 'wildcard' => $form->translate('Simple match with wildcards (*)'),
+ 'regex' => $form->translate('Regular Expression'),
+ 'is_true' => $form->translate('Match boolean TRUE'),
+ 'is_false' => $form->translate('Match boolean FALSE'),
+ 'is_null' => $form->translate('Match NULL value columns'),
+ ]),
+ 'class' => 'autosubmit',
+ ]);
+
+ $method = $form->getSetting('filter_method');
+ switch ($method) {
+ case 'wildcard':
+ $form->addElement('text', 'filter_string', [
+ 'label' => $form->translate('Filter'),
+ 'description' => $form->translate(
+ 'The string/pattern you want to search for, use wildcard'
+ . ' matches like www.* or *linux*'
+ ),
+ 'required' => true,
+ ]);
+ break;
+ case 'regex':
+ $form->addElement('text', 'filter_string', [
+ 'label' => $form->translate('Filter'),
+ 'description' => $form->translate(
+ 'The string/pattern you want to search for, use regular'
+ . ' expression like /^www\d+\./'
+ ),
+ 'required' => true,
+ ]);
+ break;
+ }
+
+ $form->addElement('select', 'policy', [
+ 'label' => $form->translate('Policy'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'What should happen with the row, when this property matches the given expression?'
+ ),
+ 'value' => 'reject',
+ 'multiOptions' => [
+ 'reject' => $form->translate('Reject the whole row'),
+ 'keep' => $form->translate('Keep only matching rows'),
+ ],
+ ]);
+ }
+
+ public function matchesRegexp($string, $expression)
+ {
+ return preg_match($expression, $string);
+ }
+
+ public function isNull($string, $expression)
+ {
+ return $string === null;
+ }
+
+ public function isTrue($string, $expression)
+ {
+ return $string === true;
+ }
+
+ public function isFalse($string, $expression)
+ {
+ return $string === false;
+ }
+
+ public function matchesWildcard($string, $expression)
+ {
+ return $this->filterExpression->matches(
+ (object) ['value' => $string]
+ );
+ }
+
+ public function transform($value)
+ {
+ $method = $this->getSetting('filter_method');
+ $filter = $this->getSetting('filter_string');
+ $policy = $this->getSetting('policy');
+
+ switch ($method) {
+ case 'wildcard':
+ $func = 'matchesWildcard';
+ $this->filterExpression = new FilterExpression('value', '=', $filter);
+ break;
+ case 'regex':
+ $func = 'matchesRegexp';
+ break;
+ case 'is_null':
+ $func = 'isNull';
+ break;
+ case 'is_true':
+ $func = 'isTrue';
+ break;
+ case 'is_false':
+ $func = 'isFalse';
+ break;
+ default:
+ throw new ConfigurationError(
+ '%s is not a valid value for an ArrayFilter filter_method',
+ var_export($method, 1)
+ );
+ }
+
+ if ($this->$func($value, $filter)) {
+ if ($policy === 'reject') {
+ $this->rejectRow();
+ }
+ } else {
+ if ($policy === 'keep') {
+ $this->rejectRow();
+ }
+ }
+
+ return $value;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierRenameColumn.php b/library/Director/PropertyModifier/PropertyModifierRenameColumn.php
new file mode 100644
index 0000000..12524d5
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierRenameColumn.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+
+class PropertyModifierRenameColumn extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Rename a Property/Column';
+ }
+
+ public function requiresRow()
+ {
+ return true;
+ }
+
+ public function hasArraySupport()
+ {
+ return true;
+ }
+
+ public function transform($value)
+ {
+ $row = $this->getRow();
+ $property = $this->getPropertyName();
+ if ($row) {
+ unset($row->$property);
+ }
+ // $this->rejectRow();
+ return $value;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierReplace.php b/library/Director/PropertyModifier/PropertyModifierReplace.php
new file mode 100644
index 0000000..54e6616
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierReplace.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierReplace extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'string', array(
+ 'label' => 'Search string',
+ 'description' => $form->translate('The string you want to search for'),
+ 'required' => true,
+ ));
+
+ $form->addElement('text', 'replacement', array(
+ 'label' => 'Replacement',
+ 'description' => $form->translate('Your replacement string'),
+ ));
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ return str_replace(
+ $this->getSetting('string'),
+ $this->getSetting('replacement'),
+ $value
+ );
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierReplaceNull.php b/library/Director/PropertyModifier/PropertyModifierReplaceNull.php
new file mode 100644
index 0000000..d6f9fd3
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierReplaceNull.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierReplaceNull extends PropertyModifierHook
+{
+
+ public function getName()
+ {
+ return 'Replace null value with String';
+ }
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'string', [
+ 'label' => 'Replacement String',
+ 'description' => $form->translate('Your replacement string'),
+ 'required' => true,
+ ]);
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return $this->getSetting('string');
+ } else {
+ return $value;
+ }
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierSimpleGroupBy.php b/library/Director/PropertyModifier/PropertyModifierSimpleGroupBy.php
new file mode 100644
index 0000000..6c1452f
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierSimpleGroupBy.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierSimpleGroupBy extends PropertyModifierHook
+{
+ private $keptRows = [];
+
+ public function getName()
+ {
+ return mt('director', 'Group by a column, aggregate others');
+ }
+
+ public function requiresRow()
+ {
+ return true;
+ }
+
+ public function transform($value)
+ {
+ $row = $this->getRow();
+ $aggregationColumns = preg_split(
+ '/\s*,\s*/',
+ $this->getSetting('aggregation_columns'),
+ -1,
+ PREG_SPLIT_NO_EMPTY
+ );
+ if (isset($this->keptRows[$value])) {
+ foreach ($aggregationColumns as $column) {
+ if (isset($row->$column)) {
+ $this->keptRows[$value]->{$column} = array_unique(array_merge(
+ $this->keptRows[$value]->{$column},
+ [$row->$column]
+ ));
+ sort($this->keptRows[$value]->{$column});
+ }
+ }
+ $this->rejectRow();
+ } else {
+ foreach ($aggregationColumns as $column) {
+ if (isset($row->$column)) {
+ $row->$column = [$row->$column];
+ } else {
+ $row->$column = [];
+ }
+ }
+
+ $this->keptRows[$value] = $row;
+ }
+
+ return $value;
+ }
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'aggregation_columns', [
+ 'label' => $form->translate('Aggregation Columns'),
+ 'description' => $form->translate(
+ 'Comma-separated list of columns that should be aggregated (transformed into an Array).'
+ . ' For all other columns only the first value will be kept.'
+ ),
+ 'required' => true,
+ ]);
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierSkipDuplicates.php b/library/Director/PropertyModifier/PropertyModifierSkipDuplicates.php
new file mode 100644
index 0000000..bf9bd31
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierSkipDuplicates.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+
+class PropertyModifierSkipDuplicates extends PropertyModifierHook
+{
+ private $seen = [];
+
+ public function getName()
+ {
+ return mt('director', 'Skip row if this value appears more than once');
+ }
+
+ public function transform($value)
+ {
+ if (isset($this->seen[$value])) {
+ $this->rejectRow();
+ }
+
+ $this->seen[$value] = true;
+
+ return $value;
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierSplit.php b/library/Director/PropertyModifier/PropertyModifierSplit.php
new file mode 100644
index 0000000..4a6fef6
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierSplit.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierSplit extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'delimiter', array(
+ 'label' => $form->translate('Delimiter'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'One or more characters that should be used to split this string'
+ )
+ ));
+
+ $form->addElement('select', 'when_empty', array(
+ 'label' => $form->translate('When empty'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'What should happen when the given string is empty?'
+ ),
+ 'value' => 'empty_array',
+ 'multiOptions' => $form->optionalEnum(array(
+ 'empty_array' => $form->translate('return an empty array'),
+ 'null' => $form->translate('return NULL'),
+ ))
+ ));
+ }
+
+ public function transform($value)
+ {
+ if (! strlen(trim($value))) {
+ if ($this->getSetting('when_empty', 'empty_array') === 'empty_array') {
+ return array();
+ } else {
+ return null;
+ }
+ }
+
+ return preg_split(
+ '/' . preg_quote($this->getSetting('delimiter'), '/') . '/',
+ $value,
+ -1,
+ PREG_SPLIT_NO_EMPTY
+ );
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierStripDomain.php b/library/Director/PropertyModifier/PropertyModifierStripDomain.php
new file mode 100644
index 0000000..34fb6ba
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierStripDomain.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierStripDomain extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'domain', array(
+ 'label' => 'Domain name',
+ 'description' => $form->translate('The domain name you want to be stripped'),
+ 'required' => true,
+ ));
+ }
+
+ public function getName()
+ {
+ return 'Strip a domain name';
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $domain = preg_quote(ltrim($this->getSetting('domain'), '.'), '/');
+
+ return preg_replace(
+ '/\.' . $domain . '$/',
+ '',
+ $value
+ );
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierSubstring.php b/library/Director/PropertyModifier/PropertyModifierSubstring.php
new file mode 100644
index 0000000..37a2f92
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierSubstring.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierSubstring extends PropertyModifierHook
+{
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('text', 'start', array(
+ 'label' => 'Start',
+ 'required' => true,
+ 'description' => sprintf(
+ $form->translate(
+ 'Please see %s for detailled instructions of how start and end work'
+ ),
+ 'http://php.net/manual/en/function.substr.php'
+ )
+ ));
+
+ $form->addElement('text', 'length', array(
+ 'label' => 'End',
+ 'description' => sprintf(
+ $form->translate(
+ 'Please see %s for detailled instructions of how start and end work'
+ ),
+ 'http://php.net/manual/en/function.substr.php'
+ )
+ ));
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $length = $this->getSetting('length');
+ if (is_numeric($length)) {
+ return substr(
+ $value,
+ (int) $this->getSetting('start'),
+ (int) $length
+ );
+ } else {
+ return substr(
+ $value,
+ (int) $this->getSetting('start')
+ );
+ }
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierToInt.php b/library/Director/PropertyModifier/PropertyModifierToInt.php
new file mode 100644
index 0000000..dca7db9
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierToInt.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Data\InvalidDataException;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+
+class PropertyModifierToInt extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Cast a string value to an Integer';
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ if (is_int($value)) {
+ return $value;
+ }
+
+ if (is_string($value)) {
+ return (int) $value;
+ }
+
+ throw new InvalidDataException('String, integer or null', $value);
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierTrim.php b/library/Director/PropertyModifier/PropertyModifierTrim.php
new file mode 100644
index 0000000..64a655b
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierTrim.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+use InvalidArgumentException;
+
+class PropertyModifierTrim extends PropertyModifierHook
+{
+ const VALID_METHODS = ['trim', 'ltrim', 'rtrim'];
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'trim_method', [
+ 'label' => $form->translate('Trim Method'),
+ 'description' => $form->translate('Where to trim the string(s)'),
+ 'value' => 'trim',
+ 'multiOptions' => $form->optionalEnum([
+ 'trim' => $form->translate('Beginning and Ending'),
+ 'ltrim' => $form->translate('Beginning only'),
+ 'rtrim' => $form->translate('Ending only'),
+ ]),
+ 'required' => true,
+ ]);
+
+ $form->addElement('text', 'character_mask', [
+ 'label' => $form->translate('Character Mask'),
+ 'description' => $form->translate(
+ 'Specify the characters that trim should remove.'
+ . 'Default is: " \t\n\r\0\x0B"'
+ ),
+ ]);
+ }
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ $mask = $this->getSetting('character_mask');
+ $method = $this->getSetting('trim_method');
+ if (in_array($method, self::VALID_METHODS)) {
+ if ($mask) {
+ return $method($value, $mask);
+ } else {
+ return $method($value);
+ }
+ }
+
+ throw new InvalidArgumentException("'$method' is not a valid trim method");
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierURLEncode.php b/library/Director/PropertyModifier/PropertyModifierURLEncode.php
new file mode 100644
index 0000000..34f3588
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierURLEncode.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+
+class PropertyModifierURLEncode extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'URL-encode a string';
+ }
+
+
+ public function transform($value)
+ {
+ return rawurlencode($value);
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierUpperCaseFirst.php b/library/Director/PropertyModifier/PropertyModifierUpperCaseFirst.php
new file mode 100644
index 0000000..a2fff40
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierUpperCaseFirst.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class PropertyModifierUpperCaseFirst extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Uppercase the first character of each word in a string';
+ }
+
+ public static function addSettingsFormFields(QuickForm $form)
+ {
+ $form->addElement('select', 'lowerfirst', array(
+ 'label' => $form->translate('Use lowercase first'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'Should all the other characters be lowercased first?'
+ ),
+ 'value' => 'y',
+ 'multiOptions' => array(
+ 'y' => $form->translate('Yes'),
+ 'n' => $form->translate('No'),
+ ),
+ ));
+ }
+
+
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ if ($this->getSetting('lowerfirst', 'y') === 'y') {
+ return ucwords(strtolower($value));
+ } else {
+ return ucwords($value);
+ }
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierUppercase.php b/library/Director/PropertyModifier/PropertyModifierUppercase.php
new file mode 100644
index 0000000..e3d3d59
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierUppercase.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+
+class PropertyModifierUppercase extends PropertyModifierHook
+{
+ public function transform($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ return \mb_strtoupper($value, 'UTF-8');
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierUuidBinToHex.php b/library/Director/PropertyModifier/PropertyModifierUuidBinToHex.php
new file mode 100644
index 0000000..a1e5d9c
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierUuidBinToHex.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Ramsey\Uuid\Uuid;
+
+class PropertyModifierUuidBinToHex extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return mt('director', 'UUID: from binary to hex');
+ }
+
+ public function transform($value)
+ {
+ return Uuid::fromBytes($value)->toString();
+ }
+}
diff --git a/library/Director/PropertyModifier/PropertyModifierXlsNumericIp.php b/library/Director/PropertyModifier/PropertyModifierXlsNumericIp.php
new file mode 100644
index 0000000..969068e
--- /dev/null
+++ b/library/Director/PropertyModifier/PropertyModifierXlsNumericIp.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Icinga\Module\Director\PropertyModifier;
+
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+
+class PropertyModifierXlsNumericIp extends PropertyModifierHook
+{
+ public function getName()
+ {
+ return 'Fix IP formatted as a number in MS Excel';
+ }
+
+ public function transform($value)
+ {
+ if (ctype_digit($value) && strlen($value) > 9 && strlen($value) <= 12) {
+ return preg_replace(
+ '/^(\d{1,3})(\d{3})(\d{3})(\d{3})/',
+ '\1.\2.\3.\4',
+ $value
+ );
+ } else {
+ return $value;
+ }
+ }
+}