Vaskovsky Web Application  3.17.0306
AbstractPage.php
1 <?php
2 // Copyright © 2017 Alexey Vaskovsky.
3 //
4 // This file is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 3.0 of the License, or (at your option) any later version.
8 //
9 // This file is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 //
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this file. If not, see
16 // <http://www.gnu.org/licenses/>
18 /**
19  * An abstract page.
20  *
21  * @author Alexey Vaskovsky
22  */
23 abstract class AbstractPage
24 {
25  //
26  // PATHS
27  //--------------------------------------------------------------------------
28  /**
29  * Sets the application URL.
30  *
31  * @param string $url
32  * is the application URL.
33  *
34  * @throws InvalidArgumentException if `$url` is null.
35  */
36  protected final function setApplicationURL($url)
37  {
38  // url: -null
39  if (! is_string($url)) {
40  throw new \InvalidArgumentException();
41  }
42  //
43  $this->application_url = $url;
44  }
45  /**
46  * Returns a path relative to the application URL.
47  *
48  * @param string $path
49  * is a relative path.
50  *
51  * @throws InvalidArgumentException if `$path` is null.
52  */
53  protected final function getApplicationURL($path)
54  {
55  // filename: -null
56  if (! is_string($path)) {
57  throw new \InvalidArgumentException();
58  }
59  //
60  $url = $this->application_url;
61  if(empty($path)) {
62  return $url;
63  }
64  if($path[0] != '/') {
65  $url .= '/';
66  }
67  $url .= $path;
68  return $url;
69  }
70  /**
71  * Sets the application directory.
72  *
73  * @param string $directory
74  * is a directory name.
75  *
76  * @throws InvalidArgumentException if `$directory` is empty.
77  */
78  protected final function setApplicationDirectory($directory)
79  {
80  // directory: -empty
81  if (! is_string($directory) || empty(
82  $directory)) {
83  throw new \InvalidArgumentException();
84  }
85  //
86  $this->application_path = $directory;
87  }
88  /**
89  * Returns a file path relative to the application directory.
90  *
91  * @param string $filename
92  * is a file name.
93  *
94  * @throws InvalidArgumentException if `$filename` is empty.
95  */
96  protected final function getApplicationFile($filename)
97  {
98  // filename: -empty
99  if (empty($filename)) {
100  throw new \InvalidArgumentException();
101  }
102  //
103  if (empty($this->application_path)) {
104  $dir = __DIR__; // src
105  $dir = dirname($dir); // web-application
106  $dir = dirname($dir); // vaskovsky
107  $dir = dirname($dir); // vendor
108  $dir = dirname($dir); // your application
109  $this->application_path = $dir;
110  }
111  $file = $this->application_path;
112  if(empty($filename)) {
113  return $file;
114  }
115  if($filename[0] != '/') {
116  $file .= '/';
117  }
118  $file .= $filename;
119  return $file;
120  }
121  //
122  // REFLECTION
123  //--------------------------------------------------------------------------
124  /**
125  * Returns `new \ReflectionClass($this)`.
126  */
127  public function getClass()
128  {
129  //
130  return new \ReflectionClass($this);
131  }
132  /**
133  * Returns short class name without 'Page' suffix.
134  */
135  public function getPageName()
136  {
137  $name = $this->getClass()->getShortName();
138  $name = preg_replace('/(_[Pp]|P)age$/', '', $name);
139  return $name;
140  }
141  //
142  // LOCALIZATION
143  //--------------------------------------------------------------------------
144  /**
145  * Sets a locale to use with this page.
146  *
147  * @param string $locale
148  * is the locale name.
149  *
150  * @throws InvalidArgumentException if `$locale` is empty.
151  */
152  protected function setLocale($locale)
153  {
154  // locale: -empty
155  if (! is_string($locale) || empty($locale)) {
156  throw new \InvalidArgumentException();
157  }
158  //
159  putenv("LC_ALL=" . $locale);
160  setlocale(LC_ALL, $locale);
161  $domain = "messages";
162  bindtextdomain($domain, $this->getApplicationFile("locale"));
163  textdomain($domain);
164  }
165  //
166  // DATABASE
167  //--------------------------------------------------------------------------
168  /**
169  * Opens a database connection to use in this application.
170  *
171  * @param string $dsn
172  * is a string that contains the information required to connect
173  * to the database.
174  *
175  * @param string $user
176  * is a user name for the DSN string.
177  *
178  * @param string $password
179  * is a password for the DSN string.
180  *
181  * @throws PDOException if a database access error occurs.
182  *
183  * @throws InvalidArgumentException :
184  * if `$dsn` is empty;
185  * if `$user` is null;
186  * if `$password` is null.
187  */
188  protected final function openPDO($dsn, $user = "", $password = "")
189  {
190  // dsn: -empty
191  if (! is_string($dsn) || empty($dsn)) {
192  throw new \InvalidArgumentException();
193  }
194  // user: -null
195  if (! is_string($user)) {
196  throw new \InvalidArgumentException();
197  }
198  // password: -null
199  if (! is_string($password)) {
200  throw new \InvalidArgumentException();
201  }
202  //
203  if (empty($user)) {
204  $user = null;
205  }
206  if (empty($password)) {
207  $password = null;
208  }
209  $this->pdo = new \PDO(
210  $dsn,
211  $user,
212  $password,
213  array(
214  \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
215  \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_OBJ
216  ));
217  }
218  /**
219  * Returns a PDO connection.
220  *
221  * @throws PDOException if a database access error occurs.
222  *
223  * @see #openPDO()
224  */
225  protected final function getPDO()
226  {
227  //
228  if (is_null($this->pdo))
229  $this->openPDO("pgsql:", "", "");
230  return $this->pdo;
231  }
232  //
233  // AUTHORIZATION
234  //--------------------------------------------------------------------------
235  /**
236  * Sets an authentication to use with this page.
237  *
238  * @param AbstractAuthentication $auth
239  * is an authentication.
240  *
241  * @throws InvalidArgumentException if `$auth` is null.
242  */
243  protected final function setAuthentication(AbstractAuthentication $auth)
244  {
245  // auth: -null @prototype
246  $this->auth = $auth;
247  }
248  /**
249  * Returns an AbstractAuthentication object; never null.
250  *
251  * @throws PDOException if `$this->getPDO()` throws.
252  */
253  protected final function getAuthentication()
254  {
255  //
256  if (is_null($this->auth)) {
257  $this->auth = new BasicAuthentication("", $this->getPDO(), "Account");
258  }
259  return $this->auth;
260  }
261  /**
262  * Performs authorization.
263  *
264  * @return true if user is authorized; false otherwise.
265  */
266  protected function authorize()
267  {
268  //
269  return true;
270  }
271  /**
272  * Performs authorization by the account attribute.
273  *
274  * @param string $attribute
275  * is an account attribute name.
276  *
277  * @return true if user is authorized; false otherwise.
278  * @see #authorize()
279  */
280  protected function authorizeByAttribute($attribute)
281  {
282  //
283  if(!!$this->getAuthentication()->getAccountAttribute($attribute)) {
284  return true;
285  } else {
286  $this->getAuthentication()->signOut();
287  return false;
288  }
289  }
290  /**
291  * Performs authorization by the page name.
292  *
293  * @param array $public_pages
294  * are list of page names.
295  *
296  * @return true if user is authorized; false otherwise.
297  * @see #authorize()
298  */
299  protected function authorizeByPageName(array $public_pages = array())
300  {
301  //
302  $page_name = $this->getPageName();
303  if(in_array($page_name, $public_pages)) return true;
304  $role_attr = "is_" . strtolower($page_name) . "_manager";
305  return $this->authorizeByAttribute($role_attr);
306  }
307  //
308  // VIEW
309  //--------------------------------------------------------------------------
310  /**
311  * Renders a view.
312  *
313  * @param string $template
314  * is a template name.
315  *
316  * @param array $view_data
317  * is an associative array that contains a view data.
318  *
319  * @param int $code
320  * is an HTTP status code.
321  *
322  * @throws InvalidArgumentException :
323  * if `$template` is empty;
324  * if `$code` is out of the bounds 100..599.
325  *
326  * @return a string; never null.
327  */
328  protected function render($template, array $view_data = array(), $code = 200)
329  {
330  // template: -empty
331  if (! is_string($template) || empty($template)) {
332  throw new \InvalidArgumentException();
333  }
334  // view_data: -null @prototype
335  // code: 100..599
336  if (! is_int($code) || $code < 100 || $code > 599) {
337  throw new \InvalidArgumentException($code);
338  }
339  //
340  if ($code != 200)
341  http_response_code($code);
342  extract($view_data);
343  ob_start();
344  include $this->getApplicationFile("view/$template.php");
345  $contents = ob_get_contents();
346  ob_end_clean();
347  return $contents;
348  }
349  /**
350  * Renders an error.
351  *
352  * @param string $message
353  * is an error message.
354  *
355  * @param int $code
356  * is an HTTP status code.
357  *
358  * @throws InvalidArgumentException :
359  * if `$message` is null;
360  * if `$code` is out of the bounds 100..599.
361  *
362  * @return a string; never null.
363  */
364  protected function renderError($message, $code = 500)
365  {
366  // message: -null
367  if (! is_string($message)) {
368  throw new \InvalidArgumentException();
369  }
370  // code: 100..599
371  if (! is_int($code) || $code < 100 || $code > 599) {
372  throw new \InvalidArgumentException();
373  }
374  //
375  return $this->render(
376  "ErrorView",
377  array(
378  "code" => $code,
379  "message" => $message
380  ),
381  $code);
382  }
383  /**
384  * Renders an exception.
385  *
386  * @param Exception $ex
387  * is an exception.
388  *
389  * @return a string; never null.
390  */
391  protected function renderException(\Exception $ex)
392  {
393  // ex: -null @prototype
394  $file = basename($ex->getFile());
395  $line = $ex->getLine();
396  $message = $ex->getMessage();
397  $classname = get_class($ex);
398  return $this->renderError("$message ($classname@$file:$line)", 500);
399  }
400  /**
401  * Handles a server request and displays this page.
402  */
403  public final function show()
404  {
405  //
406  $action = @$_REQUEST["action"];
407  if (! isset($action))
408  $action = $_SERVER["REQUEST_METHOD"];
409  assert($action != null);
410  $action = strtolower($action);
411  $method = "do" . ucwords($action);
412  if (method_exists($this, $method)) {
413  try {
414  if (!$this->authorize())
415  return;
416  echo call_user_method($method, $this);
417  } catch (\Exception $ex) {
418  echo $this->renderException($ex);
419  }
420  } else {
421  $classname = get_class($this);
422  echo $this->renderError(
423  _("Method not found") . ": {$classname}::{$method}",
424  404);
425  }
426  }
427  // PRIVATE
428  //--------------------------------------------------------------------------
429  private $pdo = null;
430  private $auth = null;
431  private $application_path = null;
432  private $application_url = "";
433 }
setApplicationDirectory($directory)
Sets the application directory.
setApplicationURL($url)
Sets the application URL.
authorizeByAttribute($attribute)
Performs authorization by the account attribute.
render($template, array $view_data=array(), $code=200)
Renders a view.
getApplicationURL($path)
Returns a path relative to the application URL.
getPageName()
Returns short class name without 'Page' suffix.
getClass()
Returns new \ReflectionClass($this).
Performs basic HTTP authentication.
renderError($message, $code=500)
Renders an error.
show()
Handles a server request and displays this page.
getAuthentication()
Returns an AbstractAuthentication object; never null.
authorizeByPageName(array $public_pages=array())
Performs authorization by the page name.
setAuthentication(AbstractAuthentication $auth)
Sets an authentication to use with this page.
setLocale($locale)
Sets a locale to use with this page.
getPDO()
Returns a PDO connection.
authorize()
Performs authorization.
getApplicationFile($filename)
Returns a file path relative to the application directory.
renderException(\Exception $ex)
Renders an exception.
openPDO($dsn, $user="", $password="")
Opens a database connection to use in this application.