1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Install\FolderStructure;
17
18use TYPO3\CMS\Core\Core\Environment;
19use TYPO3\CMS\Core\Messaging\FlashMessage;
20use TYPO3\CMS\Install\FolderStructure\Exception\InvalidArgumentException;
21
22/**
23 * Abstract node implements common methods
24 * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
25 */
26abstract class AbstractNode
27{
28    /**
29     * @var string Name
30     */
31    protected $name = '';
32
33    /**
34     * @var string|null Target permissions for unix, eg. '2775' or '0664' (4 characters string)
35     */
36    protected $targetPermission;
37
38    /**
39     * @var NodeInterface|null Parent object of this structure node
40     */
41    protected $parent;
42
43    /**
44     * @var array Directories and root may have children, files and link always empty array
45     */
46    protected $children = [];
47
48    /**
49     * Get name
50     *
51     * @return string Name
52     */
53    public function getName()
54    {
55        return $this->name;
56    }
57
58    /**
59     * Get target permission
60     *
61     * Make sure to call octdec on the value when passing this to chmod
62     *
63     * @return string Permissions as a 4 character octal string, i.e. 2775 or 0644
64     */
65    protected function getTargetPermission()
66    {
67        return $this->targetPermission ?? '';
68    }
69
70    /**
71     * Set target permission
72     *
73     * @param string $permission Permissions as a 4 character octal string, i.e. 2775 or 0644
74     */
75    protected function setTargetPermission($permission)
76    {
77        // Normalize the permission string to "4 characters", padding with leading "0" if necessary:
78        $permission = substr($permission, 0, 4);
79        $permission = str_pad($permission, 4, '0', STR_PAD_LEFT);
80        $this->targetPermission = $permission;
81    }
82
83    /**
84     * Get children
85     *
86     * @return array
87     */
88    protected function getChildren()
89    {
90        return $this->children;
91    }
92
93    /**
94     * Get parent
95     *
96     * @return NodeInterface|null
97     */
98    protected function getParent()
99    {
100        return $this->parent;
101    }
102
103    /**
104     * Get absolute path of node
105     *
106     * @return string
107     */
108    public function getAbsolutePath()
109    {
110        return $this->getParent()->getAbsolutePath() . '/' . $this->name;
111    }
112
113    /**
114     * Current node is writable if parent is writable
115     *
116     * @return bool TRUE if parent is writable
117     */
118    public function isWritable()
119    {
120        return $this->getParent()->isWritable();
121    }
122
123    /**
124     * Checks if node exists.
125     * Returns TRUE if it is there, even if it is only a link.
126     * Does not check the type!
127     *
128     * @return bool
129     */
130    protected function exists()
131    {
132        if (@is_link($this->getAbsolutePath())) {
133            return true;
134        }
135        return @file_exists($this->getAbsolutePath());
136    }
137
138    /**
139     * Fix permission if they are not equal to target permission
140     *
141     * @throws Exception
142     * @return FlashMessage
143     */
144    protected function fixPermission(): FlashMessage
145    {
146        if ($this->isPermissionCorrect()) {
147            throw new Exception(
148                'Permission on ' . $this->getAbsolutePath() . ' are already ok',
149                1366744035
150            );
151        }
152        $result = @chmod($this->getAbsolutePath(), (int)octdec($this->getTargetPermission()));
153        if ($result === true) {
154            return new FlashMessage(
155                '',
156                'Fixed permission on ' . $this->getRelativePathBelowSiteRoot() . '.'
157            );
158        }
159        return new FlashMessage(
160            'Permissions could not be changed to ' . $this->getTargetPermission()
161                . '. This only is a problem if files and folders within this node cannot be written.',
162            'Permission change on ' . $this->getRelativePathBelowSiteRoot() . ' not successful',
163            FlashMessage::NOTICE
164        );
165    }
166
167    /**
168     * Checks if current permission are identical to target permission
169     *
170     * @return bool
171     */
172    protected function isPermissionCorrect()
173    {
174        if ($this->isWindowsOs()) {
175            return true;
176        }
177        if ($this->getCurrentPermission() === $this->getTargetPermission()) {
178            return true;
179        }
180        return false;
181    }
182
183    /**
184     * Get current permission of node
185     *
186     * @return string eg. 2775 for dirs, 0664 for files
187     */
188    protected function getCurrentPermission()
189    {
190        $permissions = decoct((int)fileperms($this->getAbsolutePath()));
191        return substr($permissions, -4);
192    }
193
194    /**
195     * Returns TRUE if OS is windows
196     *
197     * @return bool TRUE on windows
198     */
199    protected function isWindowsOs()
200    {
201        return Environment::isWindows();
202    }
203
204    /**
205     * Cut off project path from given path
206     *
207     * @param string $path Given path
208     * @return string Relative path, but beginning with /
209     * @throws Exception\InvalidArgumentException
210     */
211    protected function getRelativePathBelowSiteRoot($path = null)
212    {
213        if ($path === null) {
214            $path = $this->getAbsolutePath();
215        }
216        $projectPath = Environment::getProjectPath();
217        if (strpos($path, $projectPath, 0) !== 0) {
218            throw new InvalidArgumentException(
219                'Public path is not first part of given path',
220                1366398198
221            );
222        }
223        $relativePath = substr($path, strlen($projectPath), strlen($path));
224        // Add a forward slash again, so we don't end up with an empty string
225        if ($relativePath === '') {
226            $relativePath = '/';
227        }
228        return $relativePath;
229    }
230}
231