Completed
Pull Request — develop (#280)
by
unknown
11:07
created

FormFileUpload::setAllowRemove()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
/**
3
 * YAWIK
4
 *
5
 * @filesource
6
 * @copyright (c) 2013 - 2016 Cross Solution (http://cross-solution.de)
7
 * @license       MIT
8
 */
9
10
/**  */
11
namespace Core\Form\View\Helper;
12
13
use Zend\Form\View\Helper\FormFile;
14
use Zend\Form\ElementInterface;
15
use Core\Form\Element\FileUpload;
16
17
/**
18
 * View helper to render a file upload element.
19
 *
20
 * @author Mathias Gelhausen <[email protected]>
21
 * @author Miroslav Fedeleš <[email protected]>
22
 */
23
class FormFileUpload extends FormFile
24
{
25
    /**
26
     * Javascript file to inject to headscript helper
27
     *
28
     * @var string
29
     */
30
    protected $scriptFile = 'Core/js/forms.file-upload.js';
31
    
32
    /**
33
     * @var string
34
     */
35
    protected $emptyNotice;
36
    
37
    /**
38
     * @var string
39
     */
40
    protected $nonEmptyNotice;
41
    
42
    /**
43
     * @var bool
44
     */
45
    protected $allowRemove = true;
46
    
47
    /**
48
     * @var bool
49
     */
50
    protected $allowClickableDropZone = true;
51
52
    public function render(ElementInterface $element)
53
    {
54
        if (!$element instanceof FileUpload) {
55
            throw new \InvalidArgumentException('Expects element of type "Core\Form\Element\FileUpload"');
56
        }
57
58
        $markup = $this->renderMarkup($element);
59
        $markup = str_replace('__input__', $this->renderFileElement($element), $markup);
60
61
        return $markup;
62
    }
63
    
64
    /**
65
     * @param FileUpload $element
66
     * @return string
67
     * @since 0.27
68
     */
69
    public function renderFileList(FileUpload $element)
70
    {
71
        /* @var $renderer \Zend\View\Renderer\PhpRenderer */
72
        /* @var $basepath \Zend\View\Helper\BasePath */
73
        $renderer = $this->getView();
74
        $basepath = $renderer->plugin('basepath');
75
        $renderer->headscript()
76
            ->appendFile($basepath('js/jquery-file-upload/vendor/jquery.ui.widget.js'))
77
            ->appendFile($basepath('js/jquery-file-upload/jquery.iframe-transport.js'))
78
            ->appendFile($basepath('js/jquery-file-upload/jquery.fileupload.js'))
79
            ->appendFile($basepath($this->scriptFile));
80
        
81
        $file       = $element->getFileEntity();
82
        $preview    = '';
83
        $translator = $this->getTranslator();
84
        $textDomain = $this->getTranslatorTextDomain();
85
86
        $template = '
87
<li class="fu-file fu-working">'.($this->allowRemove ? '
88
    <a href="#abort" class="fu-delete-button btn btn-default btn-xs">
89
        <span class="yk-icon yk-icon-minus"></span>
90
    </a>
91
    ' : '').'<div class="fu-progress">
92
        <span class="yk-icon yk-icon-spinner fa-spin"></span>
93
        <span class="fu-progress-text">0</span> %
94
    </div>
95
    <a class="fu-file-info" href="__file-uri__" target="_blank">
96
        <span class="yk-icon fa-file-o fa-4x"></span>
97
        __file-name__ <br> (__file-size__)
98
    </a>
99
    <div class="fu-errors input-error">
100
        <ul class="errors">
101
            <li class="fu-error-size">' . $translator->translate('The file is too big', $textDomain) . '</li>
102
            <li class="fu-error-type">' . $translator->translate('The file type is not supported', $textDomain) . '</li>
103
            <li class="fu-error-count">' . sprintf(
104
    $translator->translate('You may only upload %d files', $textDomain),
105
    $element->getAttribute('data-maxfilecount')
106
) . '</li><li class="fu-error-general">' . $translator->translate('An unknown error occured.') . '</li>
107
        </ul>
108
   </div>
109
</li>';
110
        /* @var $renderer \Zend\View\Renderer\PhpRenderer */
111
        /* @var $basepath \Zend\View\Helper\BasePath */
112
        $renderer          = $this->getView();
113
        $basepath          = $renderer->plugin('basepath');
114
        $createFileDisplay = function ($file) use ($template, $basepath) {
115
            /* @var $file \Core\Entity\FileInterface */
116
            $uri  = $basepath($file->getUri());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Core\Entity\FileInterface as the method getUri() does only exist in the following implementations of said interface: Applications\Entity\Attachment, Auth\Entity\UserImage, Cv\Entity\Attachment, Cv\Entity\ContactImage, Organizations\Entity\OrganizationImage.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
117
            $name = $file->getName();
118
            $size = $file->getPrettySize();
119
            $icon = 0 === strpos($file->getType(), 'image/')
120
                ? 'fa-file-image-o' : 'fa-file-o';
121
122
            return str_replace(
123
                array('#abort',
124
                      '__file-uri__',
125
                      '__file-name__',
126
                      '__file-size__',
127
                      'fu-working',
128
                      'fa-file-o'
129
                ),
130
                array("$uri?do=delete", $uri, $name, $size, '', $icon),
131
                $template
132
            );
133
134
        };
135
136
        if ($element->isMultiple()) {
137
            if (count($file)) {
138
                foreach ($file as $f) {
0 ignored issues
show
Bug introduced by
The expression $file of type null|object<Doctrine\Com...e\Entity\FileInterface> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
139
                    $preview .= $createFileDisplay($f);
140
                }
141
            }
142
        } else {
143
            if ($file) {
144
                $preview = $createFileDisplay($file);
145
            }
146
        }
147
148
        $nonemptynotice =
149
            '<div class="fu-nonempty-notice"' . ('' == trim($preview) ? ' style="display:none;"' : '') . '>'
150
            . $this->getNonEmptyNotice() . '</div>';
151
        $emptynotice    = '<div class="fu-empty-notice"'
152
                          . ('' == trim($preview) ? '' : ' style="display: none;"') . '>
153
                       ' . $this->getEmptyNotice() . '
154
                  </div>';
155
156
        $markup = '
157
    <span class="fu-template" data-template="%2$s"></span>
158
    %4$s
159
    <ul class="fu-files">
160
    %1$s
161
    </ul>
162
    %3$s';
163
164
        $markup = sprintf(
165
            $markup,
166
            $preview,
167
            $renderer->escapeHtmlAttr(trim($template)),
168
            $emptynotice,
169
            $nonemptynotice
170
        );
171
172
        return $markup;
173
    }
174
    
175
    /**
176
     * @param FileUpload $element
177
     * @return string
178
     * @throws \DomainException
179
     * @since 0.27
180
     */
181
    public function renderFileElement(FileUpload $element)
182
    {
183
        $name = $element->getName();
184
        if ($name === null || $name === '') {
185
            throw new \DomainException(
186
                sprintf(
187
                    '%s requires that the element has an assigned name; none discovered',
188
                    __METHOD__
189
                )
190
            );
191
        }
192
        
193
        $attributes         = $element->getAttributes();
194
        $attributes['type'] = $this->getType($element);
195
        $attributes['name'] = $name;
196
197
        return sprintf(
198
            '<input %s%s',
199
            $this->createAttributesString($attributes),
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by $element->getAttributes() on line 193 can also be of type object<Traversable>; however, Zend\Form\View\Helper\Ab...reateAttributesString() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
200
            $this->getInlineClosingBracket()
201
        );
202
    }
203
204
    /**
205
     * Renders the markup for the file upload element.
206
     *
207
     * @param FileUpload $element
208
     *
209
     * @return string
210
     */
211
    protected function renderMarkup(FileUpload $element)
212
    {
213
        $markup = '
214
<div class="%s" id="%s-dropzone">
215
    %s
216
   __input__
217
</div>';
218
        
219
        return sprintf(
220
            $markup,
221
            $this->getDropZoneClass($element),
222
            $element->getAttribute('id'),
223
            $this->renderFileList($element)
224
        );
225
    }
226
    
227
    /**
228
     * @param FileUpload $element
229
     * @return string
230
     * @since 0.27
231
     */
232
    public function getDropZoneClass(FileUpload $element)
233
    {
234
        return sprintf('fu-dropzone fu-%s%s',
235
            $element->isMultiple() ? 'multiple' : 'single',
236
            $this->allowClickableDropZone ? '' : ' fu-non-clickable'
237
        );
238
    }
239
    
240
    /**
241
	 * @param string $emptyNotice
242
	 * @return FormFileUpload
243
	 * @since 0.27
244
	 */
245
	public function setEmptyNotice($emptyNotice)
246
	{
247
		$this->emptyNotice = $emptyNotice;
248
		
249
		return $this;
250
	}
251
    
252
    /**
253
	 * @return string
254
	 * @since 0.27
255
	 */
256
	protected function getEmptyNotice()
257
	{
258
	    if (!isset($this->emptyNotice))
259
	    {
260
	        $this->emptyNotice = '
261
	            <div class="pull-left">
262
                    <span class="yk-icon fa-files-o fa-5x"></span>
263
                </div>' . $this->getDefaultNotice();
264
	    }
265
	    
266
		return $this->emptyNotice;
267
	}
268
269
    /**
270
	 * @param string $nonEmptyNotice
271
	 * @return FormFileUpload
272
	 * @since 0.27
273
	 */
274
	public function setNonEmptyNotice($nonEmptyNotice)
275
	{
276
		$this->nonEmptyNotice = $nonEmptyNotice;
277
		
278
		return $this;
279
	}
280
281
    /**
282
	 * @return string
283
	 * @since 0.27
284
	 */
285
	protected function getNonEmptyNotice()
286
	{
287
	    if (!isset($this->nonEmptyNotice))
288
	    {
289
	        $this->nonEmptyNotice = $this->getDefaultNotice();
290
	    }
291
	    
292
		return $this->nonEmptyNotice;
293
	}
294
295
    /**
296
	 * @return string
297
	 * @since 0.27
298
	 */
299
	protected function getDefaultNotice()
300
	{
301
		return '<small>' . $this->getTranslator()->translate('Click here to add files or use drag and drop.') . '</small>';
302
	}
303
	
304
    /**
305
	 * @param boolean $allowRemove
306
	 * @return FormFileUpload
307
	 * @since 0.27
308
	 */
309
	public function setAllowRemove($allowRemove)
310
	{
311
		$this->allowRemove = (bool)$allowRemove;
312
		
313
		return $this;
314
	}
315
	
316
    /**
317
	 * @param boolean $allowClickableDropZone
318
	 * @return FormFileUpload
319
	 * @since 0.27
320
	 */
321
	public function setAllowClickableDropZone($allowClickableDropZone)
322
	{
323
		$this->allowClickableDropZone = (bool)$allowClickableDropZone;
324
		
325
		return $this;
326
	}
327
}
328