1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\Validator\File;
11
12use Zend\Validator\AbstractValidator;
13use Zend\Validator\Exception;
14
15/**
16 * Validator for counting all given files
17 *
18 */
19class Count extends AbstractValidator
20{
21    /**#@+
22     * @const string Error constants
23     */
24    const TOO_MANY = 'fileCountTooMany';
25    const TOO_FEW  = 'fileCountTooFew';
26    /**#@-*/
27
28    /**
29     * @var array Error message templates
30     */
31    protected $messageTemplates = array(
32        self::TOO_MANY => "Too many files, maximum '%max%' are allowed but '%count%' are given",
33        self::TOO_FEW  => "Too few files, minimum '%min%' are expected but '%count%' are given",
34    );
35
36    /**
37     * @var array Error message template variables
38     */
39    protected $messageVariables = array(
40        'min'   => array('options' => 'min'),
41        'max'   => array('options' => 'max'),
42        'count' => 'count'
43    );
44
45    /**
46     * Actual filecount
47     *
48     * @var int
49     */
50    protected $count;
51
52    /**
53     * Internal file array
54     * @var array
55     */
56    protected $files;
57
58    /**
59     * Options for this validator
60     *
61     * @var array
62     */
63    protected $options = array(
64        'min' => null,  // Minimum file count, if null there is no minimum file count
65        'max' => null,  // Maximum file count, if null there is no maximum file count
66    );
67
68    /**
69     * Sets validator options
70     *
71     * Min limits the file count, when used with max=null it is the maximum file count
72     * It also accepts an array with the keys 'min' and 'max'
73     *
74     * If $options is an integer, it will be used as maximum file count
75     * As Array is accepts the following keys:
76     * 'min': Minimum filecount
77     * 'max': Maximum filecount
78     *
79     * @param  int|array|\Traversable $options Options for the adapter
80     */
81    public function __construct($options = null)
82    {
83        if (is_string($options) || is_numeric($options)) {
84            $options = array('max' => $options);
85        }
86
87        if (1 < func_num_args()) {
88            $options['min'] = func_get_arg(0);
89            $options['max'] = func_get_arg(1);
90        }
91
92        parent::__construct($options);
93    }
94
95    /**
96     * Returns the minimum file count
97     *
98     * @return int
99     */
100    public function getMin()
101    {
102        return $this->options['min'];
103    }
104
105    /**
106     * Sets the minimum file count
107     *
108     * @param  int|array $min The minimum file count
109     * @return Count Provides a fluent interface
110     * @throws Exception\InvalidArgumentException When min is greater than max
111     */
112    public function setMin($min)
113    {
114        if (is_array($min) and isset($min['min'])) {
115            $min = $min['min'];
116        }
117
118        if (!is_string($min) and !is_numeric($min)) {
119            throw new Exception\InvalidArgumentException('Invalid options to validator provided');
120        }
121
122        $min = (int) $min;
123        if (($this->getMax() !== null) && ($min > $this->getMax())) {
124            throw new Exception\InvalidArgumentException(
125                "The minimum must be less than or equal to the maximum file count, but {$min} > {$this->getMax()}"
126            );
127        }
128
129        $this->options['min'] = $min;
130        return $this;
131    }
132
133    /**
134     * Returns the maximum file count
135     *
136     * @return int
137     */
138    public function getMax()
139    {
140        return $this->options['max'];
141    }
142
143    /**
144     * Sets the maximum file count
145     *
146     * @param  int|array $max The maximum file count
147     * @return Count Provides a fluent interface
148     * @throws Exception\InvalidArgumentException When max is smaller than min
149     */
150    public function setMax($max)
151    {
152        if (is_array($max) and isset($max['max'])) {
153            $max = $max['max'];
154        }
155
156        if (!is_string($max) and !is_numeric($max)) {
157            throw new Exception\InvalidArgumentException('Invalid options to validator provided');
158        }
159
160        $max = (int) $max;
161        if (($this->getMin() !== null) && ($max < $this->getMin())) {
162            throw new Exception\InvalidArgumentException(
163                "The maximum must be greater than or equal to the minimum file count, but {$max} < {$this->getMin()}"
164            );
165        }
166
167        $this->options['max'] = $max;
168        return $this;
169    }
170
171    /**
172     * Adds a file for validation
173     *
174     * @param string|array $file
175     * @return Count
176     */
177    public function addFile($file)
178    {
179        if (is_string($file)) {
180            $file = array($file);
181        }
182
183        if (is_array($file)) {
184            foreach ($file as $name) {
185                if (!isset($this->files[$name]) && !empty($name)) {
186                    $this->files[$name] = $name;
187                }
188            }
189        }
190
191        return $this;
192    }
193
194    /**
195     * Returns true if and only if the file count of all checked files is at least min and
196     * not bigger than max (when max is not null). Attention: When checking with set min you
197     * must give all files with the first call, otherwise you will get a false.
198     *
199     * @param  string|array $value Filenames to check for count
200     * @param  array        $file  File data from \Zend\File\Transfer\Transfer
201     * @return bool
202     */
203    public function isValid($value, $file = null)
204    {
205        if (($file !== null) && !array_key_exists('destination', $file)) {
206            $file['destination'] = dirname($value);
207        }
208
209        if (($file !== null) && array_key_exists('tmp_name', $file)) {
210            $value = $file['destination'] . DIRECTORY_SEPARATOR . $file['name'];
211        }
212
213        if (($file === null) || !empty($file['tmp_name'])) {
214            $this->addFile($value);
215        }
216
217        $this->count = count($this->files);
218        if (($this->getMax() !== null) && ($this->count > $this->getMax())) {
219            return $this->throwError($file, self::TOO_MANY);
220        }
221
222        if (($this->getMin() !== null) && ($this->count < $this->getMin())) {
223            return $this->throwError($file, self::TOO_FEW);
224        }
225
226        return true;
227    }
228
229    /**
230     * Throws an error of the given type
231     *
232     * @param  string $file
233     * @param  string $errorType
234     * @return false
235     */
236    protected function throwError($file, $errorType)
237    {
238        if ($file !== null) {
239            if (is_array($file)) {
240                if (array_key_exists('name', $file)) {
241                    $this->value = $file['name'];
242                }
243            } elseif (is_string($file)) {
244                $this->value = $file;
245            }
246        }
247
248        $this->error($errorType);
249        return false;
250    }
251}
252