AppApi
AppApi copied to clipboard
Multi Language and ProCache do not work
The module uses a before ProcessPageView::execute
to provide an endpoint for the API. However this hook is executed before the Mutli-Language and Cache Modules, so these do not work properly (Multi-Language fields for example would return the default language in every case ❓). There is a bit more of a description of the problem in this post: https://processwire.com/talk/topic/20006-module-restapi/page/3/?tab=comments#comment-190070
Possible solutions:
- Use
ProcessPageView::pageNotFound
Hook - The module creates a page in the page-tree which handles the routing to the api endpoint (the RestAPI module did this in very early versions, the logic for creating/deleting on install/uninstall could potentially be pulled from there)
+1 LoginRegisterPro doesn't work either. I submitted a bug in the LoginRegisterPro-support-forum. Ryan states: " I don't have experience with the module you mentioned, so can't speak to what potential conflicts there might be between LoginRegisterPro and that module. But the error you indicated points to LoginRegisterPro being loaded before PW's API has reached a ready() state. The "property of non-object" is referring to the $page API variable, meaning ready() state has not yet occurred in PW, so PW is still booting up. Likely it's still in the init() state — that's too early to load modules and execute any code that uses the PW API, because the PW API is not yet fully present at that stage in the boot process. It's kind of like trying to start a car while it's in gear, it's not going to be able to. I think you can likely solve this just by executing in, or anytime after, the ready() state. The ready state literally means 'API ready' to use."
I have just uploaded version 1.0.4 that makes use of ProcessPageView::pageNotFound
. I like this way instead of creating a page in the page-tree, because in doing so AppApi remains independent of the page tree.
In my tests everything looks normal. @thomasaull @spoetnik , I would be happy if you could give me some feedback if the module now works for your purposes as well. And of course, you both are mentioned as contributors to this new version. Thanks for your help 🥇
@thomasaull In my current project I managed to get multi-language to work by passing the language-id (if not default) as a parameter with every request. In my handler-class I only had to set the language from the param to processwire:
$lang = wire('input')->get->pageName('lang');
if (!empty($lang) && wire('languages')->get($lang)) {
wire('user')->language = wire('languages')->get($lang);
} else {
wire('user')->language = wire('languages')->getDefault();
}
With this, every requested multi-language field returns the requested language. But to be honest, the project is not finished yet and I cannot guarantee if this solution covers every edge-case. Let me know, how you would do it!
Sorry for the late reply. Converted my side-project to AppApi. An almost drop-in replacement.
Too bad the issue with LoginRegisterPro still exists.
In the LoginRegisterPro module a:
if($page->id === $pageId) {
// we have the page we want and config value is correct
}
gives an error:
"Trying to get property 'id' of non-object",
The $page-object is not available.
If I want to output the contents of a specific page, I must manually find the requested page (based on GET-Params or path) and do $page->render();
. Only in the template-files of this page will $page->id be available. Before that, we have no page (you could make api-outputs manually without rendering a page).
I am not familiar with LoginRegisterPro. How do you integrate it? On which requests does it throw this error? Did you write something in your Routes.php to trigger it with an api call?
This is how I integrate LoginRegisterPro
/**
* Create a new registration and send confirmation email on success
*
* A LoginRegisterException is thrown on all error conditions.
*
* @param string $email Email address for new account
* @param string $pass Password for new account
* @param array $data Assoc array of any other fields to populate in new account
* @return bool Returns true on success, throws LoginRegisterException on error
* @throws LoginRegisterException
*
*/
public static function registerUser($data) {
AppApiHelper::checkAndSanitizeRequiredParameters($data, ['email|email', 'pass|string']);
$email = $data->email;
$pass = $data->pass;
$loginRegister = wire('modules')->get('LoginRegisterPro');
$codes = $loginRegister->codes(); // LoginRegisterProCodes
$register = $loginRegister->form('register'); // LoginRegisterProRegister
$email = wire('sanitizer')->email($email);
$userName = $email ? $loginRegister->emailToName($email) : '';
$password = new Password();
$form = $register->form();
$emailInput = $form->getChildByName('register_email'); // InputfieldEmail
$passInput = $form->getChildByName('register_pass'); // InputfieldPassword
$password->pass = $pass;
$emailInput->val($email);
$passInput->val($pass);
$error = '';
// check for errors
if(empty($email) || empty($userName)) {
$error = 'Invalid email';
} else if(empty($pass)) {
$error = 'Password required';
} else if(!$this->loginRegister->allowUserEmail($email)) {
$error = 'Email already in use';
} else if(!$this->loginRegister->allowUserName($userName, $error)) {
$error = 'Registration error - ' . $error;
} else if(!$passInput->isValidPassword($pass)) {
$error = 'Password error - ' . implode(', ', $passInput->getErrors(true));
} else if(!$this->allowRegistration($form)) {
$error = 'Registration disallowed';
}
// throw exception if any errors detected
if(strlen($error)) throw new LoginRegisterException($error);
// values that we'll be saving with registration data
$values = array(
'register_name' => $userName,
'register_email' => $email,
'register_pass' => [ $password->hash, $password->salt ],
);
// add values from $data array, adding "register_" prefix on each
foreach($data as $key => $value) {
if(strpos($key, 'register_') !== 0) $key = "register_$key";
if(empty($values[$key])) $values[$key] = $value;
}
// setup confirmation code
do {
$code = $codes->create($loginRegister->codeLength, $loginRegister->codeMethod);
} while(!$codes->add($code, $email, LoginRegisterProCodes::typeRegister, $values));
// send confirmation email
$register->_callMethod('sendConfirmationEmail', [ $email, $code ]);
$register->logSuccess('New registration', $email);
return true;
}
It was posted in the LoginRegisterPro support forum thread.
@Sebiworld I just found another problem resulting in the usage of the hook, when trying to delete a page. Considering the following examples:
This uses a bootstrapped instance of ProcessWire, ✔️ works:
<?php namespace ProcessWire;
include("./index.php");
$pageToDelete = wire('pages')->get(9834);
echo $pageToDelete->deletable();
This uses an endpoint in AppApi
: ❌️ doesn't work:
<?php namespace ProcessWire;
class Test {
public static function test($data) {
$pageToDelete = wire('pages')->get(9834);
return $pageToDelete->deletable();
}
}
and throws the following error:
{
"error": "Internal Server Error",
"devmessage": {
"message": "Trying to get property 'id' of non-object",
"location": "\/home\/thomasaull\/websites\/coperion-extranet\/wire\/core\/PagesEditor.php",
"line": 296
}
}
The line which causes this problem in PagesEditor
is the following:
if($page->id === $this->wire('page')->id && $this->wire('config')->installedAfter('2019-04-04')) {
in particluar the part where $this->wire('page')->id
tries to access the currently visited page. The problem, this variable is not set, because this would happen in the pageNotFound
method, which the AppApi intercepts beforehand. And that's also the reason the code does work with a bootstrapped instance of ProcessWire ($page is the 404 page in this case).
I know I could just create a PR to processwire to add an additional check to the if-statement:
if(isset($this->wire('page')->id) && $page->id === $this->wire('page')->id && $this->wire('config')->installedAfter('2019-04-04')) {
But I don't feel this is the way to go since ProcessWire seems to assume the $page
variable is set basically all the time with the current hook method in AppApi there might be other/similar problems in other places.
I do like the solution of providing the API endpoint with a setting in the module better, but meanwhile I guess it would in fact be better to handle the endpoint with a dedicated page. There does not seem to be any hook currently available which works…
Problems I know of are:
- Deletion of pages throws an error
- Multi-language does not work out of the box
- ProCache only works with a workaround
- LoginRegisterPro does not work according to @spoetnik
I'm not sure if it's worth it to hold on to the settings method for longer considering all these cases above.
What do you think?
I found a potential workaround for the delete issue, when changing the code of handleApiRequest
in AppApi.module.php
to this, it seems to be working:
public function handleApiRequest(HookEvent $event) {
if ($this->checkIfApiRequest()) {
// Save url, this will be changed by page creation below
$url = wire('input')->url;
$page = new Page();
$page->id = wire('config')->http404PageID;
$page->name = 'api';
$page->template = new Template();
$this->wire('page', $page);
// Reset url
wire('input')->url = $url;
// trigger ready in ProcessPageView
$processPageView = $event->object;
$processPageView->ready();
$this->apiCall = true;
Auth::getInstance()->initApikey();
$router = new Router();
$router->go();
$event->replace = true;
}
}
Basically it creates a new page on the fly and sets it as the default $page
variable. It still feels a bit hacky, but if it's resolving all the issues I mentioned above it's probably a good alternative… To fire the ready in ProcessPageView
is not necessary for my problem, but it's done in the pageNotFound
function, so I thought I'd include it here aswell.
@spoetnik Can you maybe try if this resolves your issue aswell? Just temporarily change the code to test it.
any updates on ([Multi Language) support ?
@mustafa-online I recently got my AppApiPage module to work smoothly with multi language urls. Perhaps this will also satisfy your requirements?
It was a bit complicated because ProcessWires LanguageSupport functions only trigger on real urls. It does not set the needed language automatically because our api-page is only a 404 page with no linked language. But i figured out how to get the target language from the requested path and set it. Alternatively I included setting the language via a "lang" GET-param.