I am new to Magento and using their API. I need to be able to get the product url from the API call. I see that I can access the url_key and url_path, but unfortunately that's not necessarily what the URL for the product is (ie it may be category/my-product-url.html) where url_key would contain my-product-url and url_path would only contain my-product-url.html. Further complicating things, it may even be /category/sub-category/my-product-url.html. So, how would I get the full url with the category and everything as it is setup in the url rewrite information? Seems like this should come with the product information from the product.info api call but it doesn't.
Magento Product api does not provide such functionality
Although there are easy ways to extend specific API in custom modules, but here is the quickest way if you don't want to write a custom module ( as i think it's difficult for a new magento developer).
Copy the original product API-class from the core to the local folder before editing anything (that way your Magento installation stays "update-save").
copy Api.php from:app/code/core/Mage/Catalog/Model/Product/Api.php
to:app/code/local/Mage/Catalog/Model/Product/Api.php
Now change the info method within the copied file to include the full_url. Add the following line to the $result-array. (Make sure to set necessary commas at the end of the array-lines.)
'full_url' => $product->getProductUrl(),
Your resulting method code should look like:
public function info($productId, $store = null, $attributes = null, $identifierType = null)
{
$product = $this->_getProduct($productId, $store, $identifierType);
$result = array( // Basic product data
'product_id' => $product->getId(),
'sku' => $product->getSku(),
'set' => $product->getAttributeSetId(),
'type' => $product->getTypeId(),
'categories' => $product->getCategoryIds(),
'websites' => $product->getWebsiteIds(),
'full_url' => $product->getProductUrl(),
);
foreach ($product->getTypeInstance(true)->getEditableAttributes($product) as $attribute) {
if ($this->_isAllowedAttribute($attribute, $attributes)) {
$result[$attribute->getAttributeCode()] = $product->getData(
$attribute->getAttributeCode());
}
}
return $result;
}
Afterwards you can call product.info and use the full_url field via the API.
Well actually even your path is wrong, the one he is referring to is
app\code\core\Mage\Catalog\Model\Product\Product.php
Related
I'm trying to figure out how to use the Ldap class in Symfony3. I've successfully created and bound a connection but I can't get any results on a query. To make sure that the query actually works, I ran a bare php version:
if($lconn = ldap_connect('ds.mydomain.ca')){
ldap_set_option($lconn, LDAP_OPT_REFERRALS, 0);
ldap_set_option($lconn, LDAP_OPT_PROTOCOL_VERSION, 3);
if($lbind = ldap_bind($lconn,'webuser','password')){
$filter ="(&(sn=Smith)(givenname=J*))";
if(!$result = ldap_search($lconn, "dc=ds, dc=mydomain, dc=ca", $filter)) throw \Exception("Error in search query: ".ldap_error($lconn));
$output = ldap_get_entries($lconn, $result);
}else{
$output='bind failed';
}
} else {
$output= 'connection failed';
}
It returns the expected number of results.
On the other hand, this query done with Symfony 3's Ldap component returns 0 results:
//use Symfony\Component\Ldap\Ldap
$ldap = Ldap::create('ext_ldap', array(
'host' => 'ds.mydomain.ca',
'version' => 3,
'debug' => true,
'referrals' => false,
));
$ldap->bind('webuser', 'password');
$q = $ldap->query("dc=ds, dc=nrc, dc=ca", "(&(sn=Smith)(givenname=J*))");
$output = $q->execute();
Any idea why the Symfony ldap query fails when all its options should be identical to those I used for the bare php query?
I reposted this question on the Symfony github. #ChadSikorra was there too. And he made it clear what my issue was. Here's his explanation:
If you look at the collection class, nothing is done with the result
resource until initialize() is called in the class. If you do
return array('output' => array('bare' => $bare, 'symfony' =>
$symf->toArray())); it will call initialize and you'll see the
entries populated in the class. Unless there's something else going
on.
Do you still experience this issue with the latest 3.1+ versions?
Sorry but I don't go very often on Stack Overflow and spend most of my time on Github so I didn't see your question before.
As #ChadSikorra said, you should be using the toArray() method of the resulting Collection class, or you should iterate on the results directly.
The implementation is made so that the results are traversed in a memory-efficient manner, without storing all the results in an array by default, but the toArray() method can do this for you. Behind the scenes,it actually converts the resulting itératif to an array using the appropriate PHP function (iterator_to_array).
By the way, there used to be some inconsistency between the iterator and the toArray() function call, but that has been fixed in recent versions.
Cheers!
I've set up Yii2 REST API with custom actions and everything is working just fine. However, what I'm trying to do is return some data from the API which would include database relations set by foreign keys. The relations are there and they are actually working correctly. Here's an example query in one of the controllers:
$result = \app\models\Person::find()->joinWith('fKCountry', true)
->where(..some condition..)->one();
Still in the controller, I can, for example, call something like this:
$result->fKCountry->name
and it would display the appropriate name as the relation is working. So far so good, but as soon as I return the result return $result; which is received from the API clients, the fkCountry is gone and I have no way to access the name mentioned above. The only thing that remains is the value of the foreign key that points to the country table.
I can provide more code and information but I think that's enough to describe the issue. How can I encode the information from the joined data in the return so that the API clients have access to it as well?
Set it up like this
public function actionYourAction() {
return new ActiveDataProvider([
'query' => Person::find()->with('fKCountry'), // and the where() part, etc.
]);
}
Make sure that in your Person model the extraFields function includes fKCountry. If you haven't implemented the extraFields function yet, add it:
public function extraFields() {
return ['fKCountry'];
}
and then when you call the url make sure you add the expand param to tell the action you want to include the fkCountry data. So something like:
/yourcontroller/your-action?expand=fKCountry
I managed to solve the above problem.
Using ActiveDataProvider, I have 3 changes in my code to make it work.
This goes to the controller:
Model::find()
->leftJoin('table_to_join', 'table1.id = table_to_join.table1_id')
->select('table1.*, table_to_join.field_name as field_alias');
In the model, I introduced a new property with the same name as the above alias:
public $field_alias;
Still in the model class, I modified the fields() method:
public function fields()
{
$fields = array_merge(parent::fields(), ['field_alias']);
return $fields;
}
This way my API provides me the data from the joined field.
use with for Eager loading
$result = \app\models\Person::find()->with('fKCountry')
->where(..some condition..)->all();
and then add the attribute 'fkCountry' to fields array
public function fields()
{
$fields= parent::fields();
$fields[]='fkCountry';
return $fields;
}
So $result now will return a json array of person, and each person will have attribute fkCountry:{...}
Laravel 4: In the context of consume-your-own-api, my XyzController uses my custom InternalAPiDispatcher class to create a Request object, push it onto a stack (per this consideration), then dispatch the Route:
class InternalApiDispatcher {
// ...
public function dispatch($resource, $method)
{
$this->request = \Request::create($this->apiBaseUrl . '/' . $resource, $method);
$this->addRequestToStack($this->request);
return \Route::dispatch($this->request);
}
To start with, I'm working on a basic GET for a collection, and would like the Response content to be in the format of an Eloquent model, or whatever is ready to be passed to a View (perhaps a repository thingy later on when I get more advanced). It seems inefficient to have the framework create a json response and then I decode it back into something else to display it in a view. What is a simple/efficient/elegant way to direct the Request to return the Response in the format I desire wherever I am in my code?
Also, I've looked at this post a lot, and although I'm handling query string stuff in the BaseContorller (thanks to this answer to my previous question) it all seems to be getting far too convoluted and I feel I'm getting lost in the trees.
EDIT: could the following be relevant (from laravel.com/docs/templates)?
"By specifying the layout property on the controller, the view specified will be created for you and will be the assumed response that should be returned from actions."
Feel free to mark this as OT if you like, but I'm going to suggest that you might want to reconsider your problem in a different light.
If you are "consuming your own API", which is delivered over HTTP, then you should stick to that method of consumption.
For all that it might seem weird, the upside is that you could actually replace that part of your application with some other server altogether. You could run different parts of your app on different boxes, you could rewrite the HTTP part completely, etc, etc. All the benefits of "web scale".
The route you're going down is coupling the publisher and the subscriber. Now, since they are both you, or more accurately your single app, this is not necessarily a bad thing. But if you want the benefits of being able to access your own "stuff" without resorting to HTTP (or at least "HTTP-like") requests, then I wouldn't bother with faking it. You'd be better off defining a different internal non-web Service API, and calling that.
This Service could be the basis of your "web api", and in fact the whole HTTP part could probably be a fairly thin controller layer on top of the core service.
It's not a million miles away from where you are now, but instead of taking something that is meant to output HTTP requests and mangling it, make something that can output objects, and wrap that for HTTP.
Here is how I solved the problem so that there is no json encoding or decoding on an internal request to my API. This solution also demonstrates use of route model binding on the API layer, and use of a repository by the API layer as well. This is all working nicely for me.
Routes:
Route::get('user/{id}/thing', array(
'uses' => 'path\to\Namespace\UserController#thing',
'as' => 'user.thing'));
//...
Route::group(['prefix' => 'api/v1'], function()
{
Route::model('thing', 'Namespace\Thing');
Route::model('user', 'Namespace\User');
Route::get('user/{user}/thing', [
'uses' => 'path\to\api\Namespace\UserController#thing',
'as' => 'api.user.thing']);
//...
Controllers:
UI: UserController#thing
public function thing()
{
$data = $this->dispatcher->dispatch('GET', “api/v1/user/1/thing”)
->getOriginalContent(); // dispatcher also sets config flag...
// use $data in a view;
}
API: UserController#thing
public function thing($user)
{
$rspns = $this->repo->thing($user);
if ($this->isInternalCall()) { // refs config flag
return $rspns;
}
return Response::json([
'error' => false,
'thing' => $rspns->toArray()
], 200);
Repo:
public function thing($user)
{
return $user->thing;
}
Here is how I achieved it in Laravel 5.1. It requires some fundamental changes to the controllers to work.
Instead of outputting response with return response()->make($data), do return $data.
This allows the controller methods to be called from other controllers with App::make('apicontroller')->methodname(). The return will be object/array and not a JSON.
To do processing for the external API, your existing routing stays the same. You probably need a middleware to do some massaging to the response. Here is a basic example that camel cases key names for the JSON.
<?php
namespace App\Http\Middleware;
use Closure;
class ResponseFormer
{
public function handle($request, Closure $next)
{
$response = $next($request);
if($response->headers->get('content-type') == 'application/json')
{
if (is_array($response->original)) {
$response->setContent(camelCaseKeys($response->original));
}
else if (is_object($response->original)) {
//laravel orm returns objects, it is a huge time saver to handle the case here
$response->setContent(camelCaseKeys($response->original->toArray()));
}
}
return $response;
}
}
I'm trying to make a Zend_Rest_Route for a specific controller. I want the rest of my site to behave normally, except when a particular Controller (UploadAPI) is requested. I think the sytnax should be as follow, but having a hard time verifying. The examples all have to do with modules, but I don't have a module.
Is this correct?
protected function _initRestRoute() {
$this->bootstrap('frontController');
$frontController = Zend_Controller_Front::getInstance();
$restRoute = new Zend_Rest_Route($frontController, array(), array('default' => array('UploadAPI'),));
$frontController->getRouter()->addRoute('rest', $restRoute);
}
The link here
http://weierophinney.net/matthew/archives/228-Building-RESTful-Services-with-Zend-Framework.html
gives examples with modules, but I have no modules, and am assuming "default" is the module name.
So I have the API functionality working , this is how it looks. You have to add this function in Bootstrap class to initilize Zend_Rest_Route.
This will do the Zend Rest API routing only for the controllers listed in the array, the rest of the site should work as expected
protected function _initRestRoute() {
$this->bootstrap('frontController');
$frontController = Zend_Controller_Front::getInstance();
$restRouteUL = new Zend_Rest_Route($frontController, array(), array('default' => array('UploadAPI', 'LocationMatchesAPI', 'GetMatchesByIdAPI', 'AuthAPIController') ));
$frontController->getRouter()->addRoute('rest', $restRouteUL);
}
I'm moving some of my find code inside models.
Previously in my controller I had
$this->Book->Review->find('first', array(
'conditions' => array(
'Review.book_id' => $id,
'Review.user_id' => $this->Auth->user('id')
)
));
so in my Review model I put something like
function own($id) {
$this->contain();
$review = $this->find('first', array(
'conditions' => array(
'Review.book_id' => $id,
'Review.user_id' => AuthComponent::user('id')
)
));
return $review;
}
So I'm calling AuthComponent statically from the Model. I know I can do this for the method AuthComponent::password(), which is useful for validation. But I'm getting errors using the method AuthComponent::user(), in particular
Fatal error: Call to a member function
check() on a non-object in
/var/www/MathOnline/cake/libs/controller/components/auth.php
on line 663
Is there a way to get the info about the currently logged user from a model?
Create a new function in the "app_model.php" ("AppModel.php" in CakePHP 2.x), so it will be available at all models within our application:
function getCurrentUser() {
// for CakePHP 1.x:
App::import('Component','Session');
$Session = new SessionComponent();
// for CakePHP 2.x:
App::uses('CakeSession', 'Model/Datasource');
$Session = new CakeSession();
$user = $Session->read('Auth.User');
return $user;
}
in the model:
$user = $this->getCurrentUser();
$user_id = $user['id'];
$username = $user['username'];
The way that I use is this:
App::import('component', 'CakeSession');
$thisUserID = CakeSession::read('Auth.User.id');
It seems to work quite nicely :-)
I think the code is fine as it is and belongs in the Controller, or at the very least it needs to receive the ids from the Controller and not try to get them itself. The Model should only be concerned with fetching data from a data store and returning it. It must not be concerned with how the data is handled in the rest of the application or where the parameters to its request are coming from. Otherwise you paint yourself into a corner where the ReviewModel can only retrieve data for logged in users, which might not always be what you want.
As such, I'd use a function signature like this:
function findByBookAndUserId($book_id, $user_id) {
…
}
$this->Review->findByBookAndUserId($id, $this->Auth->user('id'));
There is a nice solution by Matt Curry. You store the data of the current logged user in the app_controller using the beforeFilter callback and access it later using static calls. A description can be found here:
http://www.pseudocoder.com/archives/2008/10/06/accessing-user-sessions-from-models-or-anywhere-in-cakephp-revealed/
EDIT: the above link is outdated: https://github.com/mcurry/cakephp_static_user
I think this is not good idea to get value from Session. Better solution to get logged user id inside any model simply try this:
AuthComponent::user('id');
This will work almost every where. View, Model and Controller
Dirtiest way would be to just access the user information in the Session. Least amount of overhead associated with that.
The "proper" way would probably be to instantiate the AuthComponent object, so that it does all the stuff it needs to be fully operational. Much like a death star, the AuthComponent doesn't really work well when not fully setup.
To get a new AC object, in the model:
App::import( 'Component', 'Auth' );
$this->Auth = new AuthComponent();
Now you can use $this->Auth in the model, same as you would in the controller.
For CakePHP 3.x this easy component is available: http://cakemanager.org/docs/utils/1.0/components/globalauth/. Direct accessing the Session is not possible because of different SessionKeys.
With the GlobalAuthComponent you can access your user-data everywhere with: Configure::read('GlobalAuth');.
Greetz
Bob
I use cake 2.2 and these both work great:
$this->Session->read('Auth.User');
//or
$this->Auth->user();
You can also get a field of currently logged in user:
$this->Session->read('Auth.User.email');
//or
$this->Auth->user()['email'];
None of these solutions work in CakePHP version 3. Anyone know of a way to do this? Right now, I'm completely stepping around the framework by accessing the $_SESSION variable directly from my model.