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\Operation\Request;
13
14use FreeDSx\Asn1\Asn1;
15use FreeDSx\Asn1\Type\AbstractType;
16use FreeDSx\Asn1\Type\BooleanType;
17use FreeDSx\Asn1\Type\IncompleteType;
18use FreeDSx\Asn1\Type\OctetStringType;
19use FreeDSx\Asn1\Type\SequenceType;
20use FreeDSx\Ldap\Entry\Dn;
21use FreeDSx\Ldap\Entry\Rdn;
22use FreeDSx\Ldap\Exception\ProtocolException;
23use FreeDSx\Ldap\Protocol\LdapEncoder;
24use function count;
25
26/**
27 * A Modify DN Request. RFC 4511, 4.9
28 *
29 * ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
30 *     entry           LDAPDN,
31 *     newrdn          RelativeLDAPDN,
32 *     deleteoldrdn    BOOLEAN,
33 *     newSuperior     [0] LDAPDN OPTIONAL }
34 *
35 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
36 */
37class ModifyDnRequest implements RequestInterface, DnRequestInterface
38{
39    protected const APP_TAG = 12;
40
41    /**
42     * @var Dn
43     */
44    protected $dn;
45
46    /**
47     * @var Rdn
48     */
49    protected $newRdn;
50
51    /**
52     * @var bool
53     */
54    protected $deleteOldRdn;
55
56    /**
57     * @var null|Dn
58     */
59    protected $newParentDn;
60
61    /**
62     * @param string|Dn $dn
63     * @param string|Rdn $newRdn
64     * @param bool $deleteOldRdn
65     * @param null|string|Dn $newParentDn
66     */
67    public function __construct($dn, $newRdn, bool $deleteOldRdn, $newParentDn = null)
68    {
69        $this->setDn($dn);
70        $this->setNewRdn($newRdn);
71        $this->setNewParentDn($newParentDn);
72        $this->deleteOldRdn = $deleteOldRdn;
73    }
74
75    /**
76     * @return Dn
77     */
78    public function getDn(): Dn
79    {
80        return $this->dn;
81    }
82
83    /**
84     * @param string|Dn $dn
85     * @return $this
86     */
87    public function setDn($dn)
88    {
89        $this->dn = $dn instanceof Dn ? $dn : new Dn($dn);
90
91        return $this;
92    }
93
94    /**
95     * @return Rdn
96     */
97    public function getNewRdn(): Rdn
98    {
99        return $this->newRdn;
100    }
101
102    /**
103     * @param string|Rdn $newRdn
104     * @return $this
105     */
106    public function setNewRdn($newRdn)
107    {
108        $this->newRdn = $newRdn instanceof Rdn ? $newRdn : Rdn::create($newRdn);
109
110        return $this;
111    }
112
113    /**
114     * @return bool
115     */
116    public function getDeleteOldRdn(): bool
117    {
118        return $this->deleteOldRdn;
119    }
120
121    /**
122     * @param bool $deleteOldRdn
123     * @return $this
124     */
125    public function setDeleteOldRdn(bool $deleteOldRdn)
126    {
127        $this->deleteOldRdn = $deleteOldRdn;
128
129        return $this;
130    }
131
132    /**
133     * @return null|Dn
134     */
135    public function getNewParentDn(): ?Dn
136    {
137        return $this->newParentDn;
138    }
139
140    /**
141     * @param null|string|Dn $newParentDn
142     * @return $this
143     */
144    public function setNewParentDn($newParentDn)
145    {
146        if ($newParentDn !== null) {
147            $newParentDn = $newParentDn instanceof Dn ? $newParentDn : new Dn($newParentDn);
148        }
149        $this->newParentDn = $newParentDn;
150
151        return $this;
152    }
153
154    /**
155     * {@inheritDoc}
156     * @return self
157     */
158    public static function fromAsn1(AbstractType $type)
159    {
160        if (!($type instanceof SequenceType && count($type) >= 3 && count($type) <= 4)) {
161            throw new ProtocolException('The modify dn request is malformed');
162        }
163        $entry = $type->getChild(0);
164        $newRdn = $type->getChild(1);
165        $deleteOldRdn = $type->getChild(2);
166        $newSuperior = $type->getChild(3);
167
168        if (!($entry instanceof OctetStringType && $newRdn instanceof OctetStringType && $deleteOldRdn instanceof BooleanType)) {
169            throw new ProtocolException('The modify dn request is malformed');
170        }
171        if ($newSuperior !== null && !($newSuperior->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $newSuperior->getTagNumber() === 0)) {
172            throw new ProtocolException('The modify dn request is malformed');
173        }
174        $newSuperior = ($newSuperior instanceof IncompleteType)
175            ? (new LdapEncoder())->complete($newSuperior, AbstractType::TAG_TYPE_OCTET_STRING)
176            : $newSuperior;
177        $newSuperior = ($newSuperior !== null) ? $newSuperior->getValue() : null;
178
179        return new self($entry->getValue(), $newRdn->getValue(), $deleteOldRdn->getValue(), $newSuperior);
180    }
181
182    /**
183     * @return SequenceType
184     */
185    public function toAsn1(): AbstractType
186    {
187        /** @var \FreeDSx\Asn1\Type\SequenceType $asn1 */
188        $asn1 = Asn1::application(self::APP_TAG, Asn1::sequence(
189            Asn1::octetString($this->dn->toString()),
190            // @todo Make a RDN type. Future validation purposes?
191            Asn1::octetString($this->newRdn->toString()),
192            Asn1::boolean($this->deleteOldRdn)
193        ));
194        if ($this->newParentDn !== null) {
195            $asn1->addChild(Asn1::context(0, Asn1::octetString($this->newParentDn->toString())));
196        }
197
198        return $asn1;
199    }
200}
201