Passed
Push — master ( 29da27...7ca054 )
by Aimeos
03:58
created

Standard::getSubClient()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 76
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 76
rs 10
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Standard::addDownload() 0 34 5

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2016-2022
6
 * @package Client
7
 * @subpackage Html
8
 */
9
10
11
namespace Aimeos\Client\Html\Account\Download;
12
13
14
/**
15
 * Default implementation of account download HTML client.
16
 *
17
 * @package Client
18
 * @subpackage Html
19
 */
20
class Standard
21
	extends \Aimeos\Client\Html\Common\Client\Factory\Base
22
	implements \Aimeos\Client\Html\Common\Client\Factory\Iface
23
{
24
	/**
25
	 * Returns the HTML code for insertion into the body.
26
	 *
27
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
28
	 * @return string HTML code
29
	 */
30
	public function body( string $uid = '' ) : string
31
	{
32
		return '';
33
	}
34
35
36
	/**
37
	 * Returns the HTML string for insertion into the header.
38
	 *
39
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
40
	 * @return string|null String including HTML tags for the header on error
41
	 */
42
	public function header( string $uid = '' ) : ?string
43
	{
44
		return null;
45
	}
46
47
48
	/**
49
	 * Processes the input, e.g. store given values.
50
	 *
51
	 * A view must be available and this method doesn't generate any output
52
	 * besides setting view variables if necessary.
53
	 */
54
	public function init()
55
	{
56
		$context = $this->context();
57
58
		try
59
		{
60
			$view = $this->view();
61
			$id = $view->param( 'dl_id' );
62
63
			/** client/html/account/download/error/url/target
64
			 * Destination of the URL to redirect the customer if the file download isn't allowed
65
			 *
66
			 * The destination can be a page ID like in a content management system or the
67
			 * module of a software development framework. This "target" must contain or know
68
			 * the controller that should be called by the generated URL.
69
			 *
70
			 * @param string Destination of the URL
71
			 * @since 2019.04
72
			 * @category Developer
73
			 */
74
			$target = $context->config()->get( 'client/html/account/download/error/url/target' );
75
76
			if( $this->checkAccess( $id ) === false ) {
77
				return $view->response()->withStatus( 401 )->withHeader( 'Location', $view->url( $target ) );
78
			}
79
80
			$manager = \Aimeos\MShop::create( $context, 'order/base/product/attribute' );
81
			$item = $manager->get( $id );
82
83
			if( $this->checkDownload( $id ) === false ) {
84
				return $view->response()->withStatus( 403 )->withHeader( 'Location', $view->url( $target ) );
85
			} else {
86
				$this->addDownload( $item );
87
			}
88
89
			parent::init();
90
		}
91
		catch( \Exception $e )
92
		{
93
			$this->logException( $e );
94
		}
95
	}
96
97
98
	/**
99
	 * Adds the necessary headers and the download content to the reponse object
100
	 *
101
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Attribute\Iface $item Order product attribute item with file reference
102
	 */
103
	protected function addDownload( \Aimeos\MShop\Order\Item\Base\Product\Attribute\Iface $item )
104
	{
105
		$fs = $this->context()->fs( 'fs-secure' );
106
		$response = $this->view()->response();
107
		$value = (string) $item->getValue();
108
109
		if( $fs->has( $value ) )
110
		{
111
			$name = $item->getName();
112
113
			if( pathinfo( $name, PATHINFO_EXTENSION ) == null
114
					&& ( $ext = pathinfo( $value, PATHINFO_EXTENSION ) ) != null
115
			) {
116
				$name .= '.' . $ext;
0 ignored issues
show
Bug introduced by
Are you sure $ext of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

116
				$name .= '.' . /** @scrutinizer ignore-type */ $ext;
Loading history...
117
			}
118
119
			$response->withHeader( 'Content-Description', 'File Transfer' );
120
			$response->withHeader( 'Content-Type', 'application/octet-stream' );
121
			$response->withHeader( 'Content-Disposition', 'attachment; filename="' . $name . '"' );
122
			$response->withHeader( 'Content-Length', (string) $fs->size( $value ) );
123
			$response->withHeader( 'Cache-Control', 'must-revalidate' );
124
			$response->withHeader( 'Pragma', 'private' );
125
			$response->withHeader( 'Expires', '0' );
126
127
			$response->withBody( $response->createStream( $fs->reads( $value ) ) );
128
		}
129
		elseif( filter_var( $value, FILTER_VALIDATE_URL ) !== false )
130
		{
131
			$response->withHeader( 'Location', $value );
132
			$response->withStatus( 303 );
133
		}
134
		else
135
		{
136
			$response->withStatus( 404 );
137
		}
138
	}
139
140
141
	/**
142
	 * Checks if the customer is allowed to download the file
143
	 *
144
	 * @param string|null $id Unique order base product attribute ID referencing the download file
145
	 * @return bool True if download is allowed, false if not
146
	 */
147
	protected function checkAccess( string $id = null ) : bool
148
	{
149
		$context = $this->context();
150
151
		if( ( $customerId = $context->user() ) !== null && $id !== null )
152
		{
153
			$manager = \Aimeos\MShop::create( $context, 'order/base' );
154
155
			$search = $manager->filter();
156
			$expr = array(
157
				$search->compare( '==', 'order.base.customerid', $customerId ),
158
				$search->compare( '==', 'order.base.product.attribute.id', $id ),
159
			);
160
			$search->setConditions( $search->and( $expr ) );
161
			$search->slice( 0, 1 );
162
163
			if( !$manager->search( $search )->isEmpty() ) {
164
				return true;
165
			}
166
		}
167
168
		return false;
169
	}
170
171
172
	/**
173
	 * Updates the download counter for the downloaded file
174
	 *
175
	 * @param string|null $id Unique order base product attribute ID referencing the download file
176
	 * @return bool True if download is allowed, false if not
177
	 */
178
	protected function checkDownload( string $id = null ) : bool
179
	{
180
		$context = $this->context();
181
182
		/** client/html/account/download/maxcount
183
		 * Maximum number of file downloads allowed for an ordered product
184
		 *
185
		 * This configuration setting enables you to limit the number of downloads
186
		 * of a product download file. The count is the maximum number for each
187
		 * bought product and customer, i.e. setting the count to "3" allows
188
		 * a customer to download the bought product file up to three times.
189
		 *
190
		 * The default value of null enforces no limit.
191
		 *
192
		 * @param integer Maximum number of downloads
193
		 * @since 2016.02
194
		 * @category Developer
195
		 * @category User
196
		 */
197
		$maxcnt = $context->config()->get( 'client/html/account/download/maxcount' );
198
199
		$cntl = \Aimeos\Controller\Frontend::create( $context, 'customer' );
200
		$item = $cntl->uses( ['order' => ['download']] )->get();
201
202
		if( ( $listItem = $item->getListItem( 'order', 'download', $id ) ) === null ) {
203
			$listItem = $cntl->createListItem()->setType( 'download' )->setRefId( $id );
204
		}
205
206
		$config = $listItem->getConfig();
207
		$count = (int) $listItem->getConfigValue( 'count', 0 );
208
209
		if( $maxcnt === null || $count < $maxcnt )
210
		{
211
			$config['count'] = $count++;
212
			$cntl->addListItem( 'order', $listItem->setConfig( $config ) )->store();
213
214
			return true;
215
		}
216
217
		return false;
218
	}
219
}
220