1<?php 2 3/** 4 * This file is part of the FreeDSx LDAP package. 5 * 6 * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace FreeDSx\Ldap\Control\Sorting; 13 14use FreeDSx\Asn1\Asn1; 15use FreeDSx\Asn1\Exception\EncoderException; 16use FreeDSx\Asn1\Exception\PartialPduException; 17use FreeDSx\Asn1\Type\AbstractType; 18use FreeDSx\Asn1\Type\IncompleteType; 19use FreeDSx\Asn1\Type\OctetStringType; 20use FreeDSx\Asn1\Type\SequenceType; 21use FreeDSx\Ldap\Control\Control; 22use FreeDSx\Ldap\Exception\ProtocolException; 23use FreeDSx\Ldap\Protocol\LdapEncoder; 24 25/** 26 * A Server Side Sorting request control value. RFC 2891. 27 * 28 * SortKeyList ::= SEQUENCE OF SEQUENCE { 29 * attributeType AttributeDescription, 30 * orderingRule [0] MatchingRuleId OPTIONAL, 31 * reverseOrder [1] BOOLEAN DEFAULT FALSE } 32 * 33 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 34 */ 35class SortingControl extends Control 36{ 37 /** 38 * @var SortKey[] 39 */ 40 protected $sortKeys = []; 41 42 /** 43 * @param SortKey ...$sortKeys 44 */ 45 public function __construct(SortKey ...$sortKeys) 46 { 47 $this->sortKeys = $sortKeys; 48 parent::__construct(self::OID_SORTING); 49 } 50 51 /** 52 * @param SortKey ...$sortKeys 53 * @return $this 54 */ 55 public function addSortKeys(SortKey ...$sortKeys) 56 { 57 foreach ($sortKeys as $sortKey) { 58 $this->sortKeys[] = $sortKey; 59 } 60 61 return $this; 62 } 63 64 /** 65 * @param SortKey ...$sortKeys 66 * @return $this 67 */ 68 public function setSortKeys(SortKey ...$sortKeys) 69 { 70 $this->sortKeys = $sortKeys; 71 72 return $this; 73 } 74 75 /** 76 * @return SortKey[] 77 */ 78 public function getSortKeys(): array 79 { 80 return $this->sortKeys; 81 } 82 83 /** 84 * {@inheritDoc} 85 * @return Control 86 * @throws EncoderException 87 * @throws PartialPduException 88 */ 89 public static function fromAsn1(AbstractType $type) 90 { 91 $response = self::decodeEncodedValue($type); 92 if (!$response instanceof SequenceType) { 93 throw new ProtocolException('The sorting control is malformed.'); 94 } 95 96 $sortKeys = []; 97 foreach ($response->getChildren() as $sequence) { 98 if (!$sequence instanceof SequenceType) { 99 throw new ProtocolException('The sort key is malformed.'); 100 } 101 102 $attrName = ''; 103 $matchRule = null; 104 $useReverseOrder = false; 105 106 $encoder = new LdapEncoder(); 107 /** @var AbstractType $keyItem */ 108 foreach ($sequence->getChildren() as $keyItem) { 109 if ($keyItem instanceof OctetStringType && $keyItem->getTagClass() === AbstractType::TAG_CLASS_UNIVERSAL) { 110 $attrName = $keyItem->getValue(); 111 } elseif ($keyItem->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $keyItem->getTagNumber() === 0) { 112 $matchRule = $keyItem->getValue(); 113 } elseif ($keyItem->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $keyItem->getTagNumber() === 1) { 114 if (!$keyItem instanceof IncompleteType) { 115 throw new ProtocolException('The sorting control is malformed.'); 116 } 117 /** @var IncompleteType $keyItem */ 118 $useReverseOrder = $encoder->complete($keyItem, AbstractType::TAG_TYPE_BOOLEAN)->getValue(); 119 } else { 120 throw new ProtocolException('The sorting control contains unexpected data.'); 121 } 122 } 123 if ($attrName === '') { 124 throw new ProtocolException('The sort key is missing an attribute description.'); 125 } 126 127 $sortKeys[] = new SortKey($attrName, $useReverseOrder, $matchRule); 128 } 129 $control = new self(...$sortKeys); 130 131 return self::mergeControlData($control, $type); 132 } 133 134 /** 135 * {@inheritdoc} 136 */ 137 public function toAsn1(): AbstractType 138 { 139 $this->controlValue = Asn1::sequenceOf(); 140 141 foreach ($this->sortKeys as $sortKey) { 142 $child = Asn1::sequence(Asn1::octetString($sortKey->getAttribute())); 143 if ($sortKey->getOrderingRule() !== null) { 144 $child->addChild(Asn1::context(0, Asn1::octetString($sortKey->getOrderingRule()))); 145 } 146 if ($sortKey->getUseReverseOrder()) { 147 $child->addChild(Asn1::context(1, Asn1::boolean(true))); 148 } 149 $this->controlValue->addChild($child); 150 } 151 152 return parent::toAsn1(); 153 } 154} 155