Confirmer::showConfirm()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 9
cts 9
cp 1
rs 9.584
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * Confirmer.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        https://www.ipublikuj.eu
7
 * @author         Adam Kadlec <[email protected]>
8
 * @package        iPublikuj:ConfirmationDialog!
9
 * @subpackage     Components
10
 * @since          1.0.0
11
 *
12
 * @date           31.03.14
13
 */
14
15
declare(strict_types = 1);
16
17
namespace IPub\ConfirmationDialog\Components;
18
19
use Nette\Application;
20
use Nette\Bridges;
21
use Nette\ComponentModel;
22
use Nette\Forms;
23
24
use IPub\ConfirmationDialog\Exceptions;
25
use IPub\ConfirmationDialog\Storage;
26
27
/**
28
 * Confirmation dialog confirmer control
29
 *
30
 * @package        iPublikuj:ConfirmationDialog!
31
 * @subpackage     Components
32
 *
33
 * @author         Adam Kadlec <[email protected]>
34
 *
35
 * @property-read string $name
36
 * @property-read string $cssClass
37
 * @property-read string $useAjax
38
 */
39 1
final class Confirmer extends ConfirmerAttributes
40
{
41
	/**
42
	 * @var Control|ComponentModel\IContainer
43
	 */
44
	private $dialog;
45
46
	/**
47
	 * @param string|NULL $templateFile
48
	 * @param Storage\IStorage $storage
49
	 *
50
	 * @throws Exceptions\InvalidArgumentException
51
	 */
52
	public function __construct(
53
		string $templateFile = NULL,
54
		Storage\IStorage $storage
55
	) {
56 1
		list(, , $parent, $name) = func_get_args() + [NULL, NULL, NULL, NULL];
57
58 1
		parent::__construct($storage, $parent, $name);
59
60 1
		if ($templateFile !== NULL) {
61
			$this->setTemplateFile($templateFile);
62
		}
63 1
	}
64
65
	/**
66
	 * Show current confirmer
67
	 *
68
	 * @param array $params
69
	 * 
70
	 * @return void
71
	 *
72
	 * @throws Exceptions\InvalidStateException
73
	 */
74
	public function showConfirm(array $params = []) : void
75
	{
76
		// Generate protection token
77 1
		$token = $this->generateToken();
78
79
		// Set generated token to form
80 1
		$this['form']['secureToken']->value = $token;
81
82
		// Store token to storage
83 1
		$this->storage->set($token, [
84 1
			'confirmer' => $this->getName(),
85 1
			'params'    => $params,
86
		]);
87
88 1
		if ($this->getQuestion() !== NULL) {
89
			// Invalidate confirmer snippets
90 1
			$this->redrawControl();
91
			// Invalidate dialog snippets
92 1
			$this->getDialog()->redrawControl();
93
		}
94 1
	}
95
96
	/**
97
	 * Confirm YES clicked
98
	 *
99
	 * @param Forms\Controls\SubmitButton $button
100
	 * 
101
	 * @return void
102
	 *
103
	 * @throws Application\AbortException
104
	 * @throws Exceptions\HandlerNotCallableException
105
	 * @throws Exceptions\InvalidStateException
106
	 */
107
	public function confirmClicked(Forms\Controls\SubmitButton $button) : void
108
	{
109
		// Get submitted values from form
110 1
		$values = $button->getForm(TRUE)->getValues();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getValues() does only exist in the following implementations of said interface: Nette\Application\UI\Form, Nette\Forms\Container, Nette\Forms\Form.

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...
111
112
		// Get token from post
113 1
		$token = $values->secureToken;
114
115
		try {
116
			// Get values stored in confirmer storage
117 1
			$values = $this->getConfirmerValues($token);
118
			// Remove storage data for current confirmer
119 1
			$this->storage->clear($token);
120
121 1
			$this->getDialog()->resetConfirmer();
122
123 1
			$control = $this->getDialog()->getParent();
124
125 1
			if ($control === NULL) {
126
				throw new Exceptions\InvalidStateException('Confirmer is not attached to parent control.');
127
			}
128
129 1
			$this->callHandler($control, $values['params']);
130
131 1
		} catch (Exceptions\InvalidStateException $ex) {
132
			if (self::$strings['expired'] != '' && $this->getPresenter() instanceof Application\UI\Presenter) {
133
				$this->getPresenter()->flashMessage(self::$strings['expired']);
134
135
			} else {
136
				throw $ex;
137
			}
138
		}
139
140
		$this->refreshPage();
141
	}
142
143
	/**
144
	 * Confirm NO clicked
145
	 * 
146
	 * @return void
147
	 *
148
	 * @param Forms\Controls\SubmitButton $button
149
	 *
150
	 * @throws Application\AbortException
151
	 * @throws Exceptions\InvalidStateException
152
	 */
153
	public function cancelClicked(Forms\Controls\SubmitButton $button) : void
154
	{
155
		// Get submitted values from form
156 1
		$values = $button->getForm(TRUE)->getValues();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getValues() does only exist in the following implementations of said interface: Nette\Application\UI\Form, Nette\Forms\Container, Nette\Forms\Form.

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...
157
158
		// Get token from post
159 1
		$token = $values->secureToken;
160
161 1
		if ($this->getConfirmerValues($token)) {
162 1
			$this->storage->clear($token);
163
		}
164
165 1
		$this->getDialog()->resetConfirmer();
166
167 1
		$this->refreshPage();
168
	}
169
170
	/**
171
	 * Check if confirmer is fully configured
172
	 *
173
	 * @return bool
174
	 */
175
	public function isConfigured() : bool
176
	{
177 1
		if ((is_string($this->heading) || is_callable($this->heading)) &&
178 1
			(is_string($this->question) || is_callable($this->question)) &&
179 1
			is_callable($this->handler)
180
		) {
181 1
			return TRUE;
182
		}
183
184 1
		return FALSE;
185
	}
186
187
	/**
188
	 * Render confirmer
189
	 * 
190
	 * @return void
191
	 *
192
	 * @throws Exceptions\InvalidStateException
193
	 */
194
	public function render() : void
195
	{
196
		// Create template
197 1
		$template = parent::render();
198
199
		// Check if control has template
200 1
		if ($template instanceof Bridges\ApplicationLatte\Template) {
201
			// Assign vars to template
202 1
			$template->add('name', $this->name);
203 1
			$template->add('class', $this->cssClass);
204 1
			$template->add('icon', $this->getIcon());
205 1
			$template->add('question', $this->getQuestion());
206 1
			$template->add('heading', $this->getHeading());
207 1
			$template->add('useAjax', $this->useAjax);
208
209
			// If template was not defined before...
210 1
			if ($template->getFile() === NULL) {
211
				// ...try to get base component template file
212 1
				$templateFile = !empty($this->templateFile) ? $this->templateFile : $this->getDialog()->getTemplateFile();
213 1
				$template->setFile($templateFile);
214
			}
215
216
			// Render component template
217 1
			$template->render();
218
219
		} else {
220
			throw new Exceptions\InvalidStateException('Confirmer control is without template.');
221
		}
222 1
	}
223
224
	/**
225
	 * Change default confirmer template path
226
	 *
227
	 * @param string $layoutFile
228
	 * 
229
	 * @return void
230
	 *
231
	 * @throws Exceptions\InvalidArgumentException
232
	 */
233
	public function setTemplateFile(string $layoutFile) : void
234
	{
235
		$this->setTemplateFilePath($layoutFile, self::TEMPLATE_CONFIRMER);
236
	}
237
238
	/**
239
	 * Get parent dialog control
240
	 *
241
	 * @return Control
242
	 *
243
	 * @throws Exceptions\InvalidStateException
244
	 */
245
	protected function getDialog() : Control
246
	{
247
		// Check if confirm dialog was loaded before...
248 1
		if (!$this->dialog) {
249
			// ...if not try to lookup for it
250 1
			$multiplier = $this->getParent();
251
252
			// Check if confirmer is in multiplier
253 1
			if ($multiplier instanceof Application\UI\Multiplier) {
254 1
				$this->dialog = $multiplier->getParent();
255
256
				// Check if parent is right
257 1
				if (!$this->dialog instanceof Control) {
258 1
					throw new Exceptions\InvalidStateException('Confirmer is not attached to parent control!');
259
				}
260
261
			} else {
262
				throw new Exceptions\InvalidStateException('Confirmer is not attached to multiplier!');
263
			}
264
		}
265
266 1
		return $this->dialog;
267
	}
268
269
	/**
270
	 * Generate unique token key
271
	 *
272
	 * @return string
273
	 */
274
	private function generateToken() : string
275
	{
276 1
		return base_convert(md5(uniqid('confirm' . $this->getName(), TRUE)), 16, 36);
277
	}
278
279
	/**
280
	 * @return void
281
	 *
282
	 * @throws Application\AbortException
283
	 */
284
	private function refreshPage() : void
285
	{
286
		// Check if request is done via ajax...
287 1
		if ($this->getPresenter() instanceof Application\UI\Presenter && !$this->getPresenter()->isAjax()) {
288
			// ...if not redirect to actual page
289 1
			$this->getPresenter()->redirect('this');
290
		}
291
	}
292
}
293