diff options
Diffstat (limited to 'library/Director/PropertyModifier')
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; + } + } +} |