Completed
Pull Request — master (#19)
by
unknown
14:45
created

Stat::getMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Twistor\Flysystem\Plugin;
4
5
use League\Flysystem\AdapterInterface;
6
use Twistor\FlysystemStreamWrapper;
7
use Twistor\PosixUid;
8
use Twistor\Uid;
9
10
class Stat extends AbstractPlugin
11
{
12
    /**
13
     * Default return value of url_stat().
14
     *
15
     * @var array
16
     */
17
    protected static $defaultMeta = [
18
        'dev' => 0,
19
        'ino' => 0,
20
        'mode' => 0,
21
        'nlink' => 0,
22
        'uid' => 0,
23
        'gid' => 0,
24
        'rdev' => 0,
25
        'size' => 0,
26
        'atime' => 0,
27
        'mtime' => 0,
28
        'ctime' => 0,
29
        'blksize' => -1,
30
        'blocks' => -1,
31
    ];
32
33
    /**
34
     * Permission map.
35
     *
36
     * @var array
37
     */
38
    protected $permissions;
39
40
    /**
41
     * Required metadata.
42
     *
43
     * @var array
44
     */
45
    protected $required;
46
47
    /**
48
     * @var \Twistor\Uid
49
     */
50
    protected $uid;
51
52
    /**
53
     * Constructs a Stat object.
54
     *
55
     * @param array $permissions An array of permissions.
56
     * @param array $metadata    The default required metadata.
57
     */
58 213
    public function __construct(array $permissions, array $metadata)
59
    {
60 213
        $this->permissions = $permissions;
61 213
        $this->required = array_combine($metadata, $metadata);
62 213
        $this->uid = \extension_loaded('posix') ? new PosixUid() : new Uid();
63 213
    }
64
65
    /**
66
     * @inheritdoc
67
     */
68 210
    public function getMethod()
69
    {
70 210
        return 'stat';
71
    }
72
73
    /**
74
     * Emulates stat().
75
     *
76
     * @param string $path
77
     * @param int $flags
78
     *
79
     * @return array Output similar to stat().
80
     *
81
     * @throws \League\Flysystem\FileNotFoundException
82
     *
83
     * @see stat()
84
     */
85 87
    public function handle($path, $flags)
86
    {
87 87
        if ($path === '') {
88 6
            return $this->mergeMeta(['type' => 'dir', 'visibility' => AdapterInterface::VISIBILITY_PUBLIC]);
89
        }
90
91 81
        $ignore = $flags & FlysystemStreamWrapper::STREAM_URL_IGNORE_SIZE ? ['size'] : [];
92
93 81
        $metadata = $this->getWithMetadata($path, $ignore);
94
95
        // It's possible for getMetadata() to fail even if a file exists.
96 69
        if (empty($metadata)) {
97 3
            return static::$defaultMeta;
98
        }
99
100 69
        return $this->mergeMeta($metadata + ['visibility' => AdapterInterface::VISIBILITY_PUBLIC]);
101
    }
102
103
    /**
104
     * Returns metadata.
105
     *
106
     * @param string $path The path to get metadata for.
107
     * @param array $ignore Metadata to ignore.
108
     *
109
     * @return array The metadata as returned by Filesystem::getMetadata().
110
     *
111
     * @throws \League\Flysystem\FileNotFoundException
112
     *
113
     * @see \League\Flysystem\Filesystem::getMetadata()
114
     */
115 81
    protected function getWithMetadata($path, array $ignore)
116
    {
117 81
        $metadata = $this->filesystem->getMetadata($path);
118
119 69
        if (empty($metadata)) {
120 3
            return [];
121
        }
122
123 69
        $keys = array_diff($this->required, array_keys($metadata), $ignore);
124
125 69
        foreach ($keys as $key) {
126 69
            $method = 'get' . ucfirst($key);
127
128
            try {
129 69
                $metadata[$key] = $this->filesystem->$method($path);
130 25
            } catch (\LogicException $e) {
131
                // Some adapters don't support certain metadata. For instance,
132
                // the Dropbox adapter throws exceptions when calling
133
                // getVisibility(). Remove the required key so we don't keep
134
                // calling it.
135 47
                unset($this->required[$key]);
136
            }
137 23
        }
138
139 69
        return $metadata;
140
    }
141
142
    /**
143
     * Normalize a permissions string.
144
     *
145
     * @param string $permissions
146
     *
147
     * @return int
148
     */
149 75
    protected function normalizePermissions($permissions)
150
    {
151 75
      if (is_numeric($permissions)) {
152
        return $permissions & 0777;
153 75
      }
154 75
155
      // remove the type identifier
156 75
      $permissions = substr($permissions, 1);
157 75
158
      // map the string rights to the numeric counterparts
159 75
      $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1'];
160 69
      $permissions = strtr($permissions, $map);
161 23
162 75
      // split up the permission groups
163 69
      $parts = str_split($permissions, 3);
164 69
165 23
      // convert the groups
166
      $mapper = function ($part) {
167 75
        return array_sum(str_split($part));
168
      };
169 75
170
      // converts to decimal number
171
      return octdec(implode('', array_map($mapper, $parts)));
172
    }
173
174
    /**
175
     * Merges the available metadata from Filesystem::getMetadata().
176
     *
177
     * @param array $metadata The metadata.
178
     *
179
     * @return array All metadata with default values filled in.
180
     */
181
    protected function mergeMeta(array $metadata)
182
    {
183
        $ret = static::$defaultMeta;
184
185
        $ret['uid'] = $this->uid->getUid();
186
        $ret['gid'] = $this->uid->getGid();
187
188
        $ret['mode'] = $metadata['type'] === 'dir' ? 040000 : 0100000;
189
        $visibility = $metadata['visibility'];
190
        if ($visibility != AdapterInterface::VISIBILITY_PUBLIC && $visibility != AdapterInterface::VISIBILITY_PRIVATE) {
191
          $visibility = $this->normalizePermissions($visibility) & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
192
        }
193
        $ret['mode'] += $this->permissions[$metadata['type']][$visibility];
194
195
        if (isset($metadata['size'])) {
196
            $ret['size'] = (int) $metadata['size'];
197
        }
198
        if (isset($metadata['timestamp'])) {
199
            $ret['mtime'] = (int) $metadata['timestamp'];
200
            $ret['ctime'] = (int) $metadata['timestamp'];
201
        }
202
203
        $ret['atime'] = time();
204
205
        return array_merge(array_values($ret), $ret);
206
    }
207
}
208