File List
<?php
/**
* A model class for the RedBean object Page
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! This is a Framework system class - do not edit !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* @author Lindsay Marshall <lindsay.marshall@ncl.ac.uk>
* @copyright 2017-2021 Newcastle University
* @package Framework\Model
*/
namespace Framework\Model;
use \Config\Framework as FW;
use \Framework\Dispatch;
use \Framework\Exception\BadValue;
use \Support\Context;
/**
* A class implementing a RedBean model for Page beans
* @psalm-suppress UnusedClass
*/
final class Page extends \RedBeanPHP\SimpleModel
{
/**
* @var string The type of the bean that stores roles for this page
* @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements
*/
private string $roletype = FW::PAGEROLE;
/**
* @var array<array<bool>> Key is name of field and the array contains flags for checkboxes
* @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements
*/
private static array $editfields = [
'name' => [TRUE, FALSE],
'kind' => [TRUE, FALSE],
'source' => [TRUE, FALSE],
'active' => [TRUE, TRUE],
'mobileonly' => [TRUE, TRUE],
'needlogin' => [TRUE, TRUE],
'needajax' => [TRUE, TRUE],
'needfwutils' => [TRUE, TRUE],
'needfwdom' => [TRUE, TRUE],
'needvalidate' => [TRUE, TRUE],
'neededitable' => [TRUE, TRUE],
];
use \ModelExtend\FWEdit;
use \Framework\Support\HandleRole;
use \ModelExtend\MakeGuard;
/**
* Function called when a page bean is updated - do error checking in here
*
* @throws \Framework\Exception\BadValue
*/
public function update() : void
{
$this->bean->name = \strtolower($this->bean->name);
if (!\preg_match('/^[a-z][a-z0-9]*/', $this->bean->name))
{
throw new BadValue('Invalid page name');
}
\Framework\Dispatch::check($this->bean->kind, $this->bean->source);
}
/**
* Check user can access the page - does not return if they cannot
*
* @psalm-suppress PossiblyNullReference - we know we have a user when we call context->user
*/
public function check(Context $context) : void
{
if ($this->bean->needlogin)
{
if (!$context->hasuser())
{ // not logged in
$context->divert('/login/?goto='.\urlencode($context->local()->debase($_SERVER['REQUEST_URI'])), TRUE, 'You must login');
/* NOT REACHED */
}
if (\R::count(FW::PAGEROLE, 'page_id=?', [$this->bean->getID()]) > 0)
{ // there are roles to check
$match = \R::getCell('select count(p.id) = count(r.id) and count(p.id) != 0 from '.FW::USER.
' as u inner join role as r on u.id = r.'.FW::USER.'_id inner join (select * from '.
FW::PAGEROLE.' where '.FW::PAGE.'_id=?) as p on p.'.FW::ROLENAME.'_id = r.'.FW::ROLENAME.
'_id and p.'.FW::ROLECONTEXT.'_id = r.'.FW::ROLECONTEXT.'_id where u.id=?',
[$this->bean->getID(), $context->user()->getID()]);
if (!$match || // User does not have all the required roles
($this->bean->mobileonly && !$context->hasToken())) // not mobile and logged in
{
\Framework\Dispatch::basicSetup($context, 'error');
$context->web()->sendString($context->local()->getRender('@error/403.twig'), \Framework\Web\Web::HTMLMIME, \Framework\Web\StatusCodes::HTTP_FORBIDDEN);
exit;
}
}
}
}
/**
* Make a twig file if we have permission and it does not exist already
*
* @throws \Framework\Exception\InternalError
*/
private static function makeTwig(Context $context, array $fileName) : void
{
$file = $context->local()->makebasepath('twigs', ...$fileName);
if (!\file_exists($file))
{ // make the file
if (!\copy($context->local()->makebasepath('twigs', 'content', 'sample.txt'), $file))
{
throw new \Framework\Exception\InternalError('Cannot create '.$file);
}
}
}
/**
* Handle an object page
*/
private static function doObject(Context $context, \RedBeanPHP\OODBBean $page)
{
if (!\preg_match('/\\\\/', $page->source))
{ // no namespace so put it in \Pages
$page->source = '\\Pages\\'.$page->source;
\R::store($page);
}
$tl = \strtolower($page->source);
$tspl = \explode('\\', $page->source);
$base = \array_pop($tspl);
$lbase = \strtolower($base);
$namespace = \implode('\\', \array_filter($tspl));
$src = \preg_replace('/\\\\/', DIRECTORY_SEPARATOR, $tl).'.php';
$file = $context->local()->makebasepath('class', $src);
if (!\file_exists($file))
{ // make the file
$fd = \fopen($file, 'w');
if ($fd !== FALSE)
{
\fwrite($fd, $context->local()->getRender('@util/pagesample.twig', ['pagename' => $page->name, 'namespace' => $namespace]));
\fclose($fd);
}
else
{
throw new \Framework\Exception\InternalError('Cannot create PHP file');
}
}
self::makeTwig($context, ['content', $lbase.'.twig']); // make a basic twig if there is not one there already.
}
/**
* Handle a template page
*/
public static function doTemplate(Context $context, \RedBeanPHP\OODBBean $page) : void
{
if (!\preg_match('/\.twig$/', $page->source))
{ // doesn't end in .twig
$page->source .= '.twig';
}
else
{ // sometimes there are extra .twig extensions...
$page->source = \preg_replace('/(\.twig)+$/', '.twig', $page->source); // this removes extra .twigs .....
}
if (!\preg_match('/^@/', $page->source))
{
if (\preg_match('#/#', $page->source))
{ // has directory separator characters in it so leave it alone - may be new top-level twig directory.
$name = $page->source;
}
else
{ // no namespace so put it in @content
$page->source = '@content/'.$page->source;
$name = ['content', $page->source];
\R::store($page);
}
}
elseif (\preg_match('%^@content/(.*)%', $page->source, $m))
{ // this is in the User twig content directory
$name = ['content', $m[1]];
}
elseif (\preg_match('%@([a-z]+)/(.*)%', $page->source, $m))
{ // this is using a system twig
$name = ['framework', $m[1], $m[2]];
}
else
{
throw new \Framework\Exception\BadValue('Not recognised');
}
self::makeTwig($context, $name);
}
/**
* Add a Page
*
* This will be called from ajax.php
*
* @param Context $context The context object for the site
*/
public static function add(Context $context) : \RedBeanPHP\OODBBean
{
$fdt = $context->formdata('post');
$page = \R::dispense(FW::PAGE);
foreach (['name', 'kind', 'source'] as $fld)
{ // mandatory
$page->{$fld} = $fdt->mustFetch($fld);
}
foreach (['active', 'needlogin', 'mobileonly', 'needajax', 'needfwutils', 'needfwdom', 'needvalidate', 'neededitable'] as $fld)
{ // optional flags
$page->{$fld} = $fdt->fetch($fld, 0);
}
try
{
\R::store($page);
foreach ($fdt->fetchArray('context') as $ix => $cid)
{ // context, role, start, end, otherinfo
if ($cid !== '')
{
$page->addRoleByBean(
$fdt->mustFetchBean(['context', $ix], FW::ROLECONTEXT), // the context id
$fdt->mustFetchBean(['role', $ix], FW::ROLENAME), // the rolename id
$fdt->mustFetch(['otherinfo', $ix]),
$fdt->mustFetch(['start', $ix]),
$fdt->mustFetch(['end', $ix])
);
}
}
switch ($page->kind)
{
case Dispatch::OBJECT:
self::doObject($context, $page);
break;
case Dispatch::TEMPLATE:
self::doTemplate($context, $page);
break;
case Dispatch::REDIRECT:
case Dispatch::REHOME:
case Dispatch::XREDIRECT:
case Dispatch::XREHOME:
/** @todo check that the values passed in make sense */
break;
}
return $page;
}
catch (\Throwable $e)
{ // clean up the page we made above. This will cascade delete any pageroles that might have been created
\R::trash($page);
throw $e; // throw it up to the handlers above
}
}
/**
* Setup for an edit - nothing to do for pages at the moment.
*
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*/
public function startEdit(Context $context, array $rest) : void
{
}
/**
* Handle an edit form for this page
*
* @return array [TRUE if error, [error messages]]
*/
public function edit(Context $context) : array
{
$emess = $this->dofields($context->formdata('post'));
$this->editroles($context);
$admin = $this->hasrole(FW::FWCONTEXT, FW::ADMINROLE);
if (\is_object($devel = $this->hasrole(FW::FWCONTEXT, FW::DEVELROLE)) && !\is_object($admin))
{ // if we need developer then we also need admin
$admin = $this->addrole(FW::FWCONTEXT, FW::ADMINROLE, '-', $devel->start, $devel->end);
}
if (\is_object($admin) && !$this->bean->needlogin)
{ // if we need admin then we also need login!
$this->bean->needlogin = 1;
\R::store($this->bean);
}
return [!empty($emess), $emess];
}
}
?>