Completed
Push — master ( 466d8a...4a1721 )
by Adam
04:54
created

Confirmer   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 242
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 81.43%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 30
lcom 2
cbo 8
dl 0
loc 242
ccs 57
cts 70
cp 0.8143
rs 10
c 7
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 2
A showConfirm() 0 21 2
B confirmClicked() 0 35 5
A cancelClicked() 0 16 2
B isConfigured() 0 11 6
B render() 0 29 4
A setTemplateFile() 0 4 1
B getDialog() 0 23 4
A generateToken() 0 4 1
A refreshPage() 0 8 3
1
<?php
2
/**
3
 * Confirmer.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        http://www.ipublikuj.eu
7
 * @author         Adam Kadlec http://www.ipublikuj.eu
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;
20
use Nette\Application;
21
use Nette\Forms;
22
use Nette\Localization;
23
24
use IPub;
25
use IPub\ConfirmationDialog;
26
use IPub\ConfirmationDialog\Exceptions;
27
use IPub\ConfirmationDialog\Storage;
28
29
/**
30
 * Confirmation dialog confirmer control
31
 *
32
 * @package        iPublikuj:ConfirmationDialog!
33
 * @subpackage     Components
34
 *
35
 * @author         Adam Kadlec <[email protected]>
36
 *
37
 * @property-read string $name
38
 * @property-read string $cssClass
39
 * @property-read string $useAjax
40
 */
41 1
final class Confirmer extends ConfirmerAttributes
42
{
43
	/**
44
	 * @var Control|Nette\ComponentModel\IContainer
45
	 */
46
	private $dialog;
47
48
	/**
49
	 * @param string|NULL $templateFile
50
	 * @param Storage\IStorage $storage
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
	public function showConfirm(array $params = []) : void
73
	{
74
		// Generate protection token
75 1
		$token = $this->generateToken();
76
77
		// Set generated token to form
78 1
		$this['form']['secureToken']->value = $token;
79
80
		// Store token to storage
81 1
		$this->storage->set($token, [
82 1
			'confirmer' => $this->getName(),
83 1
			'params'    => $params,
84
		]);
85
86 1
		if ($this->getQuestion() !== NULL) {
87
			// Invalidate confirmer snippets
88 1
			$this->redrawControl();
89
			// Invalidate dialog snippets
90 1
			$this->getDialog()->redrawControl();
91
		}
92 1
	}
93
94
	/**
95
	 * Confirm YES clicked
96
	 *
97
	 * @param Forms\Controls\SubmitButton $button
98
	 * 
99
	 * @return void
100
	 *
101
	 * @throws Exceptions\HandlerNotCallableException
102
	 * @throws Exceptions\InvalidStateException
103
	 */
104
	public function confirmClicked(Forms\Controls\SubmitButton $button) : void
105
	{
106
		// Get submitted values from form
107 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...
108
109
		// Get token from post
110 1
		$token = $values->secureToken;
111
112
		try {
113
			// Get values stored in confirmer storage
114 1
			$values = $this->getConfirmerValues($token);
115
			// Remove storage data for current confirmer
116 1
			$this->storage->clear($token);
117
118 1
			$this->getDialog()->resetConfirmer();
119
120 1
			$control = $this->getDialog()->getParent();
121
122 1
			if ($control === NULL) {
123
				throw new Exceptions\InvalidStateException('Confirmer is not attached to parent control.');
124
			}
125
126 1
			$this->callHandler($control, $values['params']);
127
128 1
		} catch (Exceptions\InvalidStateException $ex) {
129
			if (self::$strings['expired'] != '' && $this->getPresenter() instanceof Application\UI\Presenter) {
130
				$this->getPresenter()->flashMessage(self::$strings['expired']);
131
132
			} else {
133
				throw $ex;
134
			}
135
		}
136
137
		$this->refreshPage();
138
	}
139
140
	/**
141
	 * Confirm NO clicked
142
	 * 
143
	 * @return void
144
	 *
145
	 * @param Forms\Controls\SubmitButton $button
146
	 */
147
	public function cancelClicked(Forms\Controls\SubmitButton $button) : void
148
	{
149
		// Get submitted values from form
150 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...
151
152
		// Get token from post
153 1
		$token = $values->secureToken;
154
155 1
		if ($this->getConfirmerValues($token)) {
156 1
			$this->storage->clear($token);
157
		}
158
159 1
		$this->getDialog()->resetConfirmer();
160
161 1
		$this->refreshPage();
162
	}
163
164
	/**
165
	 * Check if confirmer is fully configured
166
	 *
167
	 * @return bool
168
	 */
169
	public function isConfigured() : bool
170
	{
171 1
		if ((is_string($this->heading) || is_callable($this->heading)) &&
172 1
			(is_string($this->question) || is_callable($this->question)) &&
173 1
			is_callable($this->handler)
174
		) {
175 1
			return TRUE;
176
		}
177
178 1
		return FALSE;
179
	}
180
181
	/**
182
	 * Render confirmer
183
	 * 
184
	 * @return void
185
	 *
186
	 * @throws Exceptions\InvalidStateException
187
	 */
188
	public function render()
189
	{
190
		// Create template
191 1
		$template = parent::render();
192
193
		// Check if control has template
194 1
		if ($template instanceof Nette\Bridges\ApplicationLatte\Template) {
195
			// Assign vars to template
196 1
			$template->add('name', $this->name);
197 1
			$template->add('class', $this->cssClass);
198 1
			$template->add('icon', $this->getIcon());
199 1
			$template->add('question', $this->getQuestion());
200 1
			$template->add('heading', $this->getHeading());
201 1
			$template->add('useAjax', $this->useAjax);
202
203
			// If template was not defined before...
204 1
			if ($template->getFile() === NULL) {
205
				// ...try to get base component template file
206 1
				$templateFile = !empty($this->templateFile) ? $this->templateFile : $this->getDialog()->getTemplateFile();
207 1
				$template->setFile($templateFile);
208
			}
209
210
			// Render component template
211 1
			$template->render();
212
213
		} else {
214
			throw new Exceptions\InvalidStateException('Confirmer control is without template.');
215
		}
216 1
	}
217
218
	/**
219
	 * Change default confirmer template path
220
	 *
221
	 * @param string $layoutFile
222
	 * 
223
	 * @return void
224
	 */
225
	public function setTemplateFile(string $layoutFile) : void
226
	{
227
		$this->setTemplateFilePath($layoutFile, self::TEMPLATE_CONFIRMER);
228
	}
229
230
	/**
231
	 * Get parent dialog control
232
	 *
233
	 * @return Control
234
	 *
235
	 * @throws Exceptions\InvalidStateException
236
	 */
237
	protected function getDialog() : Control
238
	{
239
		// Check if confirm dialog was loaded before...
240 1
		if (!$this->dialog) {
241
			// ...if not try to lookup for it
242 1
			$multiplier = $this->getParent();
243
244
			// Check if confirmer is in multiplier
245 1
			if ($multiplier instanceof Application\UI\Multiplier) {
246 1
				$this->dialog = $multiplier->getParent();
247
248
				// Check if parent is right
249 1
				if (!$this->dialog instanceof Control) {
250 1
					throw new Exceptions\InvalidStateException('Confirmer is not attached to parent control!');
251
				}
252
253
			} else {
254
				throw new Exceptions\InvalidStateException('Confirmer is not attached to multiplier!');
255
			}
256
		}
257
258 1
		return $this->dialog;
259
	}
260
261
	/**
262
	 * Generate unique token key
263
	 *
264
	 * @return string
265
	 */
266
	private function generateToken() : string
267
	{
268 1
		return base_convert(md5(uniqid('confirm' . $this->getName(), TRUE)), 16, 36);
269
	}
270
271
	/**
272
	 * @return void
273
	 */
274
	private function refreshPage() : void
275
	{
276
		// Check if request is done via ajax...
277 1
		if ($this->getPresenter() instanceof Application\UI\Presenter && !$this->getPresenter()->isAjax()) {
278
			// ...if not redirect to actual page
279 1
			$this->getPresenter()->redirect('this');
280
		}
281
	}
282
}
283