Display related entries across different entry types using tags in different tag groups? I'm working on a site that has two different entry types (e.g., "blog" and "photos"), and each type has it's own tag group. When viewing a particular entry, I'd like to display related entries found in both entry types.

So, for example, let's say that I've written "Entry A" and "Entry B" -- "Entry A" is in "blog" and "Entry B" is a "photos." They both have the same tag ("foo"), but that tag exists in each entry type's respective tag group.

If I view "Entry A," can I display a list of related entries that includes "Entry B" even though the tag that they share isn't in the same tag group?

Ok, I believe this does what you want:

{% if entry.tags1 | length %}

    {# get tag names from our current entry #}
    {% set entryTagNames = [] %}
    {% for tag in entry.tags1 %}
        {% set entryTagNames = entryTagNames | merge([tag.name]) %}
    {% endfor %}

    {% if entryTagNames | length %}

        {# get tags from the other tag group with the same names #}
        {% set otherTags = craft.tags({ group: 'group2', name: entryTagNames }) %}

        {# get entries related to these tags #}
        {% set retaledEntries = craft.entries({ section: 'news', relatedTo: otherTags }) %}

        {# output related entries. just debugging that this actually works #}
        {% for relatedEntry in retaledEntries %}
            {{ relatedEntry }}

            {% for relatedEntryTag in relatedEntry.tags2 %}
                - {{ relatedEntryTag.name }}

            {% endfor %}

        {% endfor %}

    {% endif %}

{% endif %}

My first section, which the current entry is in, has a field named tags1, connected to the tag group group1. My second section is named news, has a field named tags2, connected to the tag group group2.

It seems a bit sketchy to rely on tag names, but.


Rename Assets when it is uploaded I would like to rename new assets by PHP, but somehow it does not work.
In Craft 3, I suppose it is like;

Event::on(\craft\services\Elements::class, \craft\services\Elements::EVENT_AFTER_SAVE_ELEMENT, function(Event $event) {
    if ($event->params['isNewAsset']) {
        $asset = $event->params['asset'];
        $folder_id = $asset->folderId;

        if ($folder_id == "22")
        {
            $filename = $asset->filename . "_test";
            craft()->assets->renameFile($asset, $filename);
        }
    }
});

My code is like that, but this message appears;

Upload failed. The error message was: “Getting unknown property: craft\events\ElementEvent::params

Could you help me?

You are mixing Craft 2 methods with Craft 3 methods.
This will get the the assets extension with $asset->getFilename(true) and add test_ in the beginning of the filename:

Event::on(
    \craft\services\Elements::class,
    \craft\services\Elements::EVENT_AFTER_SAVE_ELEMENT,
    function(Event $event)   {
        $asset = $event->element;
        $folderId = $asset->getFolder();

        if ($event->isNew && $asset instanceof craft\elements\Asset && $folderId == '22') {
            $newName =  'test_' . $asset->getFilename(true);
            Craft::$app->assets->moveAsset($asset, $folderId, $newName);
        }
    }
);


myFieldHandle error in Craft using Smart Map I am getting a Twig runtime error on the below. The map and everything else works when I remove myFieldHandle(params).orderBy('distance').
Error:
Calling unknown method: craft\elements\db\EntryQuery::myFieldHandle()

Code:
{% set target = '90210' %}
{% set params = {
    target: target,
    range: 100
} %}

{% set entries = craft.entries.section('contractors').myFieldHandle(params).orderBy('distance') %}

{% set options = {
    height: 700,
    draggable: false,
    markerInfo: '_includes/mapInfoBubble',
    infoWindowOptions: {
        maxWidth: 250
    }
} %}

The example of myFieldHandle is just a placeholder for your actual field handle.
So if your Address field has a handle of address, it would look something like this...
{% set entries = craft.entries.section('contractors').address(params).orderBy('distance') %}


Can I give every structure entry a custom layout by using the same template? I want every structure entry to have a follow up page, http://thisismysite.com/handle/slug/followuppage.

So every slug, get a follow up page with different content but the same template. Is there a simple way of doing this?

Thank you in advance!

You can setup a dynamic route for /handle/<slug>/followuppage and route the requests to your follow-up page template. From that template you’d have access to a variable called slug that you would then use to query for your structure entry.

{% set entry = craft.entries.section('mystructure').slug(slug).first() %}

{% if not entry %}
    {% exit 404 %}
{% endif %}


Are there any plans to get Craft Docs in Dash App I use Dash to browse docs and was wondering if there are any plans to get Crafts docs into it. If it's not too difficult I'd like to request it for the future (eventually).

We've discussed it off and on because so many people have requested it and have got some ideas on how we'd tackle it.  No ETA, but it's on “The List” ™.


Image transform is turning my jpegs dark I'm experiencing a strange effect where an image transform is causing my images to turn very dark when compared to their originals. I have ImageMagick and gd installed. The images are all sRGB. Some have Adobe RGB profiles, some have no profiles.

The image transform makes the jpeg 1200px wide, height auto, quality high. Not sure what could be causing the problem.

Setting 'preserveImageColorProfiles' => true, in craft/config/general.php solved the problem.

https://craftcms.com/docs/config-settings#preserveImageColorProfiles

Note that you need to be running at least Craft 2.6.2778 for this config setting to work.


Localized Craft Build with Vanilla Forums? Anyone have any luck getting a localized Craft build working with a localized Vanilla Forums install?

I've got MAMP installed, in conjunction with CodeKit. Craft itself is running fine. I installed the Vanilla Forums directory under the Templates directory. Whenever I visit craft.dev/vanilla(MAMP) or macbook.local:5757/vanilla (CodeKit) in my browser, I get a Page Not Found error. If I go straight to the index.php file, it spews out the actual ph

If you install a secondary app within your Craft directory, you have to tell Craft to ignore all these requests. Just edit your .htaccess file accordingly.

See my answer here on how you can do this: "Is it possible to tell Craft to ignore requests to under a certain path?"


Element API Endpoint returns data but has 404 header The url: https://www.fira.co.uk/fullmembers.json

is an endpoint for Craft's element API plugin.

It has been working fine until recently I did a few things:

I changed the site to https
There have been some tweaks to the server config (nginx caching etc)

The problem I am having is that the URL returns a 404 header but still displays the JSON response in the browser. This 404 is affecting my wget script that systematically saves the response to a static file, as to improve performance.

Any ideas why the endpoint would be behaving like this?

I haven't altered the elementapi.php recently but here it is:

<?php
namespace Craft;

return [
    'endpoints' => [
        'fullmembers.json' => [
            'elementType' => 'User',
            'criteria' => ['group' => 'fullMembership', 'limit' => null, 'order' => 'companyName'],
            'paginate' => false,
            'transformer' => function(UserModel $member) {
                // var_dump($member->memberAddress);
                $businessCategories = [];
                foreach ($member->businessTypeMarketSectorsProducts as $block) {
                    switch ($block->type->handle) {
                        case 'businessCategory':

                        $productValues = [];
                        foreach ($block->product as $prod) {
                            if ($prod->selected) {
                                $productValues[] = $prod->value;
                            }
                        }

                        $businessCategories[] = [
                            'businessType' => $block->businessType->value,
                            'marketSector' => $block->marketSector->value,
                            'product' => $productValues
                        ];

                        break;
                    }
                }
                // $urlslug = strtolower(implode('-',explode(" ", $member->membersCompanyName)));
                $urlslug = StringHelper::toKebabCase($member->membersCompanyName);

                return [
                    'id' => $member->id,
                    'companyName' => $member->membersCompanyName,
                    // 'memberAddress' => $member->memberAddress->attributes,
                    'lat' => $member->memberAddress->lat,
                    'lng' => $member->memberAddress->lng,
                    'city' => $member->memberAddress->city,
                    'street1' => $member->memberAddress->street1,
                    'street2' => $member->memberAddress->street2,
                    'street3' => $member->membersAddressLine3,
                    'zip' => $member->memberAddress->zip,
                    'country' => $member->memberAddress->country,
                    'email' => $member->email,
                    'phone' => $member->membersPhoneNumber,
                    'profile' => UrlHelper::getUrl("directory/company/{$member->id}/{$urlslug}"),
                    'businessCategories' => $businessCategories
                ];
            },
        ]
    ]
];

Removing this from my site's nginx conf fixed it:

location ~* \.(?:manifest|appcache|html?|xml|json)$ {
 expires -1;
}

However, what I really want to do is cache stuff, without breaking this endpoint


Recommended way to organize product types and fields in Craft Commerce I am setting up a Craft CMS site with Craft Commerce for a B2B-customer.

Here is a little excerpt of their product type hierarchy:

Textile

Sewing machines
Heavy Duty Machines
Furniture machines
Knitting machines
Scissors
Thread

Ergonomics

Chairs

Each product type have their own set of taxonomies that describe the product. For Sewing Machines, it could be:

Area of usage
Sewing machine type
Number of needles

For scissors it could be:

Scissor type
Left or right hand?

An example

Take the following screenshot from Gearbest.com:

I've outlined the taxonomies.

Questions

How should I organize the product hierarchy? (Using Categories?)
Considering that the taxonomies differ between product types, how should I assign the different taxonomies to products (System Support, Features, Application, Brand from screenshot)
Should each of the types I've described here become individual Product Types in Craft Commerce? (Heavy Duty Machines, Furniture machines, Chairs, Thread etc)

I read and reread #15930 and it suggested that I should create one Product Type for each unique kind of product -- which would be something like:

Product Types

Sewing machine
Chair
Scissor

Each of these product types would then have their own set of fields - some being reused accross all or multiple product types, while some being specific for one product type:

Field Groups

General

Name
Image
Manufacturer
Product Family <-- category
etc.

Sewing machine

Usage area
Machine type
etc.

Thread

Thickness
Material
etc.

Product Family (from General) would be a Category-field that lets me select one or multiple categories that the product would belong to. Product Family would look something like this:

Product Family

Textile

Sewing Machines
Steam pressers

Cabinets
Tables
Dollies
Accessories

etc.

Profiling

Embroidery
T-shirt printers
Laser engraving machines
etc.

I've verified this with Nathaniel Hammon from Pixel & Tonic. He adds that "An added benifit of using categories", what I call Product Family, "is that you are able to use them in conjunction with the promotions (sales & discounts) functionality that is built into Commerce"

Field layout

I went ahead and created the following categories:

General: Product Family
General: Supplier (manufacturer)
General: Intended audience
Sewing Machine: Application
Sewing Machine: Stitch type
Sewing Machine: Type
...

I then created the following field groups:

Product
Sewing Machines
...

Then I created the folloing Product Types (in Commerce):

Other products
Sewing Machines
...

Then I split the field layout for Sewing Machines into two tabs: One for general data, not specifically associated with sewing machines and one for sewing machines specifically:

The general fields come from the general field group and the details-fields come from the Sewing Machine field group.

Final thoughts

I have not yet implemented a filter, like the one in the question. I'll revisit this post and update you when I've progressed.


Get control panel asset thumbnails into a frontend template My frontend template displays the assets that have been uploaded to Craft. I could create a series of asset transforms within that template to create a range of thumbnail sizes.

Not too sure about the way this works but am I correct in thinking that every transform generates a new asset, to be more efficient with storage could I not reuse the transformed assets that the Control Panel creates?

I inspect the Assets panel in the backend and it has a url http://localhost:8888/index.php?p=admin/actions/assets/generate-thumb&amp;uid=219035f4-92ae-41d6-ad95-0f6a4c38faa9&amp;width=30&amp;height=30&amp;v=1521142167 I could pass the UID of the image to that url or may be instead match the sizes the CP creates would this reuse the same images instead of adding to my S3 storage?

There's actually a really easy way to achieve this (Craft 3 only).

Simply do this in your code:

<img src="{{ image.thumbUrl(int) }}">

{# int is a number that represents the width/height #}

The asset thumbnails are stored in /storage/runtime/assets/thumbs/, so no, they won't be taking up space in your S3 bucket (whereas some local testing inclines me to think that images generated via transforms are stored in your Asset Volume, wherever it's located).


Headless mode/ElementAPI: how to set the frontend's baseUrl in the backend endpoint? I am having an ongoing confusion in headless mode.

Imagine a site with 2 servers:

Backend (Craft with element API) on https://backend.site
Frontend (vue.js) on https://frontend.site

Editors pick internal links in the craft cp. In the source of redactor field, they are saved as something like {entry:xxx@1:url}.

In the Backend's endpoint, they are rendered out using the domain specified in baseUrl.

So let's say we have an endpoint on backend.site/en/api/pages/somepage.json which is consumed by the vue frontend.

In the backend's JSON output, we have an internal link that should be rendered as such:

Go to our other terrific
Other Page
# Using https://frontend.site as baseURL.

So the logical consequence is to set baseUrl to https://frontend.site. Naturally, because the website's baseUrl is frontend.site – not the craft backend. But, but.

In my case, this has worked for the default language. But not for any other language – element API returned a 404 when I set the baseUrl to frontend.site

backend.site/en/api/pages/somepage.json ✅
backend.site/de/api/pages/somepage.json

When I reset baseUrl to backend.site, element API will again render all endpoints. But the links look like this:

Go to our other terrific
Other Page
# This link doesn't exist

What do you think?

A misconfiguration, a bug in element api, or a misunderstanding on my side?
Asked differently: which baseUrl do you have to set in headless mode?

Apparently, the answers are the following:

Redactor doesn't know how to output slugs/URIs instead of full URLs
Possibly a solution comes up with craft in the future
My API setup is suboptimal: In fact, I did set the baseUrl including the language: https://frontend.com/de. The recommendation is to not do that at the moment but keep one "Site" for all endpoints in all languages. This can be added as another, separate site in craft! So we'd have 3 language sites in craft and a separate site for the endpoint. The language is then part of the endpoint's data. And the endpoint's URL is https://endpoint.site/api/de/foo instead of https://backend.site/de/api/foo. Yes, I guess that makes sense! Thanks again, Oli!


.relatedTo() not returning tags with spaces I think I'm seeing a bug while using advanced search with relatedTo(). If I use a simple search, like this:

{% set param = {
   order: 'RAND()',
   limit: null,
   search: "#{request.tags}"
} %}

It finds a tag like low confidence without any issue. However, using the code I have below throws no errors, but returns no results:

{% set request = craft.request.getQuery() %}

{% set param = {
  order: 'RAND()',
  limit: null,
} %}

{% set param = craft.config.devMode or craft.config.staging
             ? param|merge({ group: 'testing' })
             : param|merge({ group: 'verified' }) %}

{% set relatedToParam = ['and'] %}

{% if request.tags %}
  {% set tagId = craft.tags.group('specialism').slug(request.tags).ids() %}
  {% set relatedToParam = relatedToParam|merge([{
    targetElement: tagId,
    field: 'tagsSpecialism',
  }]) %}
{% endif %}

{% if relatedToParam|length > 1 %}
    {% set param = param|merge({ relatedTo: relatedToParam }) %}
{% endif %}

{% set users = craft.users(param) %}

I'm fairly confident it's not an issue with my code, as every other search combination is working.

Update

This is expected behaviour and requires proper hyphenated slug-path to work properly with .slug() in my code sample. See answer below!

In your working example, you’re mapping request.tags to the search param, but in the non-working example, you’re mapping it to the slug param.

Those two params are handled in very different ways. search will do some normalization routines on the value, and perform a very loose search for those keywords (see https://craftcms.com/docs/searching for more details). But the slug param will actually look for an exact match.

So if request.tags is set to "low confidence" and you pass that value into a slug param, I have complete confidence that it will return zero results, since "low confidence" isn’t actually a valid slug (see what I did there?).

You would have slightly better luck passing request.tags through the kebab filter, which will make the value look a little more like a valid slug. But the search param is probably a better bet unless you know for sure that request.tags is actually going to be a slug.


What should my controller return in a Craft.postActionRequest Simple question: I made a button in my plugin settings that creates a Craft.postActionRequest but what should the controller return?

It does not need to return anything else than true (successful) or string (action was not successful ->  errormessage) but if I return a true or a false or anything else than a template to render I get an error...

Little example:
my template is just

{{ response }}

And my controller

//correct -> no error, this template is just empty
public function actionDeleteAccessToken(){
    return $this->renderTemplate('srgoogleanalytics/tools/empty', array(response => true));
}

but if I make something like this

public function actionDeleteAccessToken(){
    return true;
}

I receive a PHP error file not found.
What do I wrong?

If you're just looking to return true or an error message from your controller's action, then it should probably be something like:

$this->returnJson(array('success' => true));

Or on error:

$this->returnErrorJson($e->getMessage());

returnErrorJson will set an 'error' key and pass whatever mess you use as its value.


Create a specific order of paginated entries by entry type I am working with a design that requires entries to be laid out on a listing page in a specific order based on whether they are "Standard" or "Premium". The order of the paginated listing page is as follows:

Standard, Standard,  Premium
Standard, Standard, Standard, Standard
Premium, Standard, Standard
Standard, Standard, Standard, Standard
Pagination

There could be a number of approaches with how I store the entries but my approach has been to set up a channel section "Offers" with two entry types "Standard" and "Premium". If the layout was not important I have the following working:

{% paginate craft.entries.section('offers').limit(14) as pageInfo, pageEntries %}

{% for entry in pageEntries %}
  {% if entry.type == 'premium' %}
    {% include "_includes/offers/listing-card-premium" %}
  {% endif %}
  {% if entry.type == 'standard' %}
    {% include "_includes/offers/listing-card" %}
  {% endif %}
{% endfor %}

{# PAGINATIOIN NAV HERE #}

My question is, how do I achieve the above but using the specific order I need, whilst still paginating the entries and keeping track of what's been displayed.

If I get you right, you want to exclude the premium entries from the others? Carefull, code is not tested ;)

{% set premiumEntries = craft.entries({
    section: 'offers',
    entry-type: 'premium',
    order: 'postDate desc'
}) %}

{% set standardEntries = craft.entries({
    section: 'offers',
    entry-type: 'standard',
    order: 'postDate desc'
}) %}

Then you need to do a loop to merge the standard to the premium.
Something like this
Merge two arrays of ElementModels?

... and finally paginate trough all of them.
Hope that helps.


Cannot find the target folder I get an error message when trying to add an asset through a matrix field when I try to save the entry

Internal Server Error
Cannot find the target folder.

I am able to upload images through assets just can't get the entry to save. Any ideas. It was working before without issues?

Thank you for any help
Josh

Make sure that the "Restrict uploads to a single folder?" setting in the fields settings isn't enabled. Enabling this, and not providing a valid path will give you that error I think.


Creating a POST request endpoint using Element API I am currently using Element API to serve Craft contents.

What I'm wondering is, is it possible to create an endpoint that handle POST requests? Like form submission and such?

All the examples that I've seen so far is to serve Craft entries as JSON.

You might want to check out this plug-in:
Restful Api

Restful Api is a plugin for Craft CMS that exposes elements as resources via a restful api. While similar to Craft's own Element Api plugin, Restful Api offers a few more features such as support for more HTTP verbs, authentication and element type based permissions.
Restful Api utilizes Fractal to transform and serialize responses. Restful Api is also PSR-7 complaint. Its request and response objects can even be utilized by third party plugins if desired, although this has not been extensively tested yet.


Previewing different languages I have setup 10 languages for a project which are all working fine when accessed directly, eg 'craft.dev/fr/page','craft.dev/zh/page','craft.dev/es/page'.

However when in admin there are two ways of previewing the translated content.
Side by side preview shows the translation fine and uses an admin URI with the locale on the end of the admin url.
But if you external preview the local is not in the URL so throws a 404.

Is this a missing feature or a config issue? I was expecting the locale to appear in the external url

Update

My config looks like

'siteUrl' => array(
    'en' => 'http://craftyCMS.dev/en/',
    'ar' => 'http://craftyCMS.dev/ar/',
    'zh' => 'http://craftyCMS.dev/zh',
);

Share link never has the locale in it.

If you define your base site URLs using the siteUrl config setting (using the array syntax), then clicking the “Share” button should bring you to the appropriate locale’s URL.


How can I restrict tags that are able to be used? Just downloaded and am setting up Craft. Super excited! I would like to know the following:

At the moment, I am assigning tags to entries in the hopes of using them as an organizational/navigation tool at some point in the future. My question is, how—if at all—can I create a list of tags that can be used IN ADVANCE? In other words, restrict the possible tags that can be assigned to now and future entries?

So, the admin can only choose/use: "branding", "design", and "identity", for example. And not "copywriting." Thanks in advance!

That’s not possible, as tags are meant to be more of a “folksonomy” rather than a strict taxonomy.

Sounds to me like categories would be a better fit for your needs. With those, the options are defined in advance, and chosen from the fields, rather than created from the fields.

Update:

We just updated the Categories and Tags feature page with a new video that may do a better job showing the difference between the two.


Fetching Existing Users In Users Field Doesnt Work When entries.saveEntry Event Triggerred From Frontend As the title says, I can successfully return the "existing user IDs" from an entry field if the "entries.saveEntry" event is called after saving the entry from the CMS admin area; however, if I save an entry via a front-end form, the same code produces empty results.

I've used xDebug to verify that the objects are the same line by line; somehow the data isn't there for $users->ids() for front-end triggered events.

// get entry
$entry = craft()->entries->getEntryById($entryId); // elementType => "Entry"

// get ALL users related to entry
$users = craft()->elements->getCriteria(ElementType::User); // elementType => Craft\UserElementType
$users->status = null;
$users->relatedTo = [$entry]; // elementType => "Entry"
$userIds = $users->ids(); // elementType => Craft\UserElementType

Example result when triggered from backend (good):

array(
    0 => 251,
    1 => 252,
    3 => 257
)

Example result when triggered from front-end (help!):

array()

Turns out I was inadvertently "emptying" my user field on form post. Essentially I had named one of the form fields "fields[customUserField]" which upon submitting the form, was saving an empty value into the "Users" field. Then my custom event would trigger and fetch the "Users" field thereby retrieving an empty "array".

So nothing is wrong with the code below:

// get entry
$entry = craft()->entries->getEntryById($entryId);

// get ALL users related to entry
$users = craft()->elements->getCriteria(ElementType::User);
$users->status = null;
$users->relatedTo = [$entry];
$userIds = $users->ids();


How to add more options to the 'Restrict allowed file types' settings? I am wondering what would be the best approach to add another file type to the 'Restrict allowed file types' asset field settings?

For our website specification we need to have .zip files and .pdf files only for this field, however I can only restrict to .pdf.

You can now set allowedFileExtensions and extraAllowedFileExtensions in your config file. The first will re-define the list of allowed files, where the latter will allow you to add additional file types.

For example, I needed to add 3 additional formats:

'extraAllowedFileExtensions' => 'bim, dwg, rvt'

See the documentation here for details.


Sprout Forms - include hidden fields in notification emails I need to include the contents of a hidden field in my form's notification emails. I know the field value is being captured, as I can see it in the CP, but it's not included in the emails.

I thought maybe I needed to create custom template, but the path to the default Sprout Email templates in the docs is no longer accurate.

I'm using Sprout Forms 3.0.0-beta.44 and Craft 3.1.14

By default, several fields do not display in the default Email Template such as the Hidden Field, Invisible Field, Section Heading Field, Private Notes Field, and Custom HTML field.

If you'd like to display any or all of those fields, you can override the default email template by in your Notification Email settings. Select the "Custom Templates" option and add a path to a folder in your template folder where you will place your email.twig override file. More info on Notifications and Email Template overrides in our docs.

You can copy the default Email Template as a starting point and the line that you'll want to update is {% for field in fields if not field.isPlainInput() %} which you can see here.

You can remove the if not field.isPlainInput() condition in that for loop to display all fields in your email. And conditionally hide or handle other fields uniquely in your templates by targeting them by their unique folder name: field.getFieldInputFolder().


I try to add element to structure, and I want to add to this element parent ID Form code:

    Date
    <select name="fields[parent][]">
    {% set entries = craft.entries.section('order').level(1) %}
        {% for entry in entries %}
            <option value="{{ entry.id }}">{{ entry.title }}</option>
        {% endfor %}
    </select>

I want to choose Parent from list, and I want to add the element to parent.

The fields array may only contain names that represents handles in your fieldLayout but the parent attribute is a property of your object rather than a field - you can set it via parentId. You may want to inspect your CP form in order to see how you need to define attributes

    Date
    <select name="parentId">
        {% for entry in craft.entries.section('order').level(1).find() %}
            <option value="{{ entry.id }}">{{ entry.title }}</option>
        {% endfor %}
    </select>


Any examples of Craft Commerce checkout process for PayPal or any other gateway? I'm unable to find much documentation nor examples for how to actually utilize Craft Commerce and successfully convert a cart to a completed order.  Any help would be greatly appreciated!

Craft Commerce ships with a templates/shop folder that has a working check-out processes using the Dummy gateway adapter and some default products that get populated during installation.

After installing it, you'd access it by going to yoursite.com/shop.  You can use a test credit card number like 4222222222222 to complete checkout and convert a cart to a completed order.


Craft 3 Cache Busting for Pending Entries So in Craft 2 the following code was required to cache bust if an entry was set to pending or post at a future date.

{% set firstPending = craft.entries({
  section: 'projects',
  status: 'pending',
  order: 'postDate asc'
}).first %}
{% set cacheUntil = firstPending ? firstPending.postDate : now|date_modify('+1 year') %}

{% cache globally using key craft.request.path until cacheUntil %}

content
{% endcache %}

Is this still required in Craft 3 or is there some magic that now happens in the background?

That code would still work in Craft 3 and achieve the same thing it did in Craft 2.


Craft Commerce 3 Decrease line Item qty by one? I may be missing something obvious but I'm trying to update a +/- quantity button system for adding products to work on Craft Commerce 3
The desired functionality is that click '+' increases the cart amount for that item by one, clicking '-' decreases the cart amount for that item by one.
The original build was in commerce 1, and the apparent way it was written was just by running an update cart ajax call (at what was then 'commerce/cart/updateCart') and passing through a '1' or '-1' as the quantity value. That doesn't appear to work anymore. The '+' call will add the '1', but passing the '-1' through for the '-' call does nothing. It just returns the same cart as if I passed through a zero.
I tried with 'commerce/cart/remove-line-item' but that removes all quantities of the line item rather than just decrement it by one.
I've tried passing the following into an ajax post request:
var data = {
            ['CRAFT_CSRF_TOKEN']: token,
            action: 'commerce/cart/update-cart',
            lineItems: {[lnid]: {'qty': dec}}
        };

With lnid being the line item id and dec being the new desired quantity (I also tried -1 there) but that just destroys the line item completely.

Try just POSTing the new absolute quantity you want to change to - e.g. if you have 5 in your cart and want to decrement by one, you'd POST something along these lines:
{
    "redirect": "6bb2a73aac3479c68e0946e4455624e2c1b840bc2b6356074a51ba01ee1234d/cart",
    "action": "commerce/cart/update-cart",
    "lineItems[82503][qty]": "4",
    "lineItems[82503][options][note]": ""
}

That's what we do, and it works.
Result:
{
    "lineItems": [
        {
            "id": 82503,
            "weight": 0,
            "length": 0,
            "height": 0,
            "width": 0,
            "qty": 4,
            "note": "",
            "privateNote": "",
            "purchasableId": "5115",
            "orderId": 145817,
            "lineItemStatusId": null,
            "taxCategoryId": 1,
            "shippingCategoryId": 1,
            "dateCreated": "2020-10-29T11:55:02+11:00",
            "dateUpdated": "2020-11-25T11:34:47+11:00",
            ...etc
}


Blog pages not indexing SEO We are encountering an issue where some/most of our blog pages are not indexing with Google. Some random blog pages are indexed but most are not.

The pages are being rendered correctly by the google bot but are not being indexed.

This is our _entry.html that pulls in the craft blog and renders the individual blog view:

{% set title = entry.title ~ " | Blog" %}

{% if entry.metaDescription | length > 0 %}
    {% set description = entry.metaDescription  %}
{%else%}
    {% set description = entry.body|split('
') [0] | striptags | slice(0, 144)  %}
{%endif%}

{% block meta_tags %}
    {{ siteMacros.returnMetaTags({
            'description' : description,
            'og:title' : entry.title ~ ' | Default Titlge Here',
            'og:image' : siteUrl ~ 'assets/favicons/favicon-196x196.png'
        })
    }}
{% endblock %}

{% block content %}

{{entry.title}}

            {% include 'content_blocks/_entryBody.html' %}

            {% include 'content_blocks/_blogCategoryList.html' %}

{% endblock %}

This just filters everything through the _entrybody.html which looks like this:

{% import '_macros' as siteMacros %}

{%if entry.subHead | length %}

{{entry.subHead}}

{%endif%}
    {% if entry.image | length %}
        {% set image = entry.image.first() %}
    {% elseif entry.curataImage | length %}
        {% set image = entry.curataImage %}
    {%endif%}

    {% if image | length %}
        {{ siteMacros.responsiveImage(image) }}
    {%endif%}

    {%if entry.video | length %}

    {%endif%}

        {% set paragraphs = entry.body|split('
') %}
        {% set pfirstBreak = (paragraphs | length / 2 ) | round - 1 %}
        {% set firstParagraphs = paragraphs | slice (0, pfirstBreak) %}
        {% set lastParagraphs = paragraphs | slice (pfirstBreak, paragraphs | length ) %}

        {% for p in firstParagraphs %}
            {{p|raw}}
        {%endfor%}

        {%if entry.pullQuote | length %}

                {{entry.pullQuote}}

        {%endif%}

        {% for p in lastParagraphs %}
            {{p|raw}}
        {%endfor%}

        {% if entry.pdf | length %}

                Related Documents:
                {%for pdf in entry.pdf %}

{{pdf.title}} [pdf]

                {%endfor%}

        {%endif%}

        {% if entry.referringSite | length  and  entry.readMoreLink | length %}

Read the full article at {{entry.referringSite}}

        {% endif %}

    {% if not hidePostDate %}
        {% set category = entry.category.first %}

Posted {{ entry.postDate.format('F j, Y') }}{% if category | length %} in {{category.link}}{%endif%}.

    {% endif %}

I'm unsure why any of this would cause the pages not to be indexed by google. Some pages are indexed that go through this same code but others are not.

Just because Google has not indexed your pages doesn't mean there isn't necessarily anything wrong. Have you gone into the group Google search console to see what pages it has actually consumed?

Also, you might consider trying SEOmatic to handle your SEO:  https://github.com/nystudio107/seomatic


How can I localize a URL in a plugin? UrlHelper::getUrl() doesn't appear to take the current locale ID into account when generating a URL. Absolute URLs are fine as it handles that but is there a best practice way to localize relative Urls such as /foo/bar or foo/bar?

You can use UrlHelper::getSiteUrl() instead, which does have a $localeId argument, for generating a URL relative to a given locale’s base URL (per the siteUrl config setting).

So if your siteUrl config setting is set to:

'siteUrl' => array(
    'en' => 'http://example.com/',
    'de' => 'http://example.com/de/',
)

and you do this:

UrlHelper::getSiteUrl('foo/bar', null, null, 'de');

it will return this:

http://example.com/de/foo/bar

(Regardless of which site you call it from.)

UrlHelper::getSiteUrl() is also available to Twig templates via a siteUrl() global function:

{{ siteUrl('foo/bar', null, null, 'de') }}


Is there a way to force Craft to notice a new license key? We are rebuilding a site from scratch using the existing license key (same domain) for the new Craft install. The new install was initially running as a Pro Trial, but Craft isn't seeing that we've added the new license. Not a huge problem, I'm sure it'll notice when there's an update or new environment, but just wondered if there is a way to 'refresh' the license.

Clearing the Craft storage/runtime/cache folder will force the license refresh.
Licensing info is stored for 24 hours at a time.
It's also worth double-checking that you definitely have the correct license installed in /config/license.key and specify the correct domain in your Craft ID account.


Can a User Group Create Drafts of New Entries Only? I'm trying to set up a workflow where I have a group of "Drafters" who can create new entries but the new entry is not published by default. I've made the following setting for this group.

However, when I log in as a user within this group, the user is not allowed to create a new entry, even though their permission explicitly says they can create new entries. Am I missing something here?

Am I missing something here?

Yes, you’re missing a bugfix that should be going out today ;)


Upgrading site from 2.3.2636 to 2.4.2684 I have an older site that has been chugging along nicely so I haven't updated it in a while. I had an issue with an older version of SmartMap that was suddenly causing a 503 on the site so I fixed that and while I was updating things I thought I'd go ahead an update Craft to the latest version. When I do the automatic update I get the following error message and the update fails:

CDbCommand failed to execute the SQL statement: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away

Before I do a manual update I wanted to see if there was something I could do to resolve this and still have the automatic update work.

Probably a timeout on the MySQL side. You could try making a database backup manually and then changing this config value to false before trying the update again: http://buildwithcraft.com/docs/config-settings#backupDbOnUpdate

That way Craft will skip the database backup during the update, saving some time and hopefully preventing a timeout. Just remember to make a backup every time you update from then on though.


Clear template cache after update sale on Craft Commerce How can I clear products template cache after update sale ?
Because product price don't change ...
Thank you.

More than likely the issue is not the prices not updating, but the sale conditions as Luke mentioned.

In theory, Craft will automatically clear template caches when an element within the cached region is updated (note one trap is this process of deleting stale template caches runs as a job task, so may not happen absolutely immediately...give things 30 seconds or so before you check them).

If things don't seem to be working for whatever reason, you can clear caches manually using the Craft control panel.

Thus, in case it is somehow cache related:

Craft 2: Control panel -> settings -> clear caches

Craft 3:

There are now console commands to clear caches (run ./craft on its own and it will list all of the available commands e.g. ./craft clear-caches/template-caches

Or, you can of course still use the control panel-> utilities -> clear caches


Custom Plugin implementation I tried to write next class:

<?php
namespace Craft;

class TaskShortcutPlugin extends BasePlugin
{
    // ...
    // Shortcut sample [task id="42"]Reference to task with id = 42[/task]
    public function task($attributes, $content, $tag)
    {
             $id = $attributes['id'];    // 42
             $content;                   // Reference to task with id = 42
             $tag;                       // 'task'

             return ''.$content.'';
    }
}

So, what next, how to implement it into CMS? Or I should use it into Twig templates?

You've got a little ways to go before that will work as a plugin... I'd highly recommend getting a little more familiar with the basic anatomy of a plugin...

Craft Docs - Anatomy of a Plugin

Basically, you'll want to take the "task" method that you have and move it to a *Variable.php file. Then you will be able to call that variable via a Twig template...

{{ craft.taskShortcut.task(attributes, content, tag) }}

You can learn more about variables here: Template Variables

However, you'll still need your main *Plugin.php file, but the methods inside will be different (Your Primary Plugin Class).

This will most likely be the folder structure of your plugin:

/craft/plugins/taskshortcut
                           /TaskShortcutPlugin.php
                           /variables/TaskShortcutVariable.php


Two Plugins not showing in CP after pushing to staging server Two different plugins not showing in the CP after code was pushed to staging server.

My questions:

1) Why wouldn't the plugins show in CP if plugins are present in the craft_plugins table?

2) How to troubleshoot this issue?  I'm not seeing any plugin errors in php-error.log or craft.log.

What I know:

-- Two plugins are NOT showing:

truncHTML ( my own custom twigFilter )
the "Import" plugin (
boboldehampsink v 0.7.1- latest)
Other plugins load fine.

-- Both "missing" plugins are present in the craft_plugins table.

-- Site seems to work file except for missing plugins, and related missing twig filter template error:

The filter "truncHTML" does not exist

-- My Development environment is php 5.4.24.
-- Staging is PHP 5.3.3 (where Plugins not loading)

I confirmed that all Craft required PHP extensions are loading on the staging server

-- Craft dev mode enabled.

-- a similar truncate twig filter plugin loads fine.

2014/07/30 00:51:58 [trace] [system.CModule] Loading "plugins" application component
in /var/www/craft.dev/craft/app/etc/web/WebApp.php (594)
in /var/www/craft.dev/craft/app/etc/web/WebApp.php (226)
in /var/www/craft.dev/craft/app/index.php (196)
2014/07/30 00:51:58 [trace] [system.db.CDbCommand] Querying SQL: SELECT `id`, `class`, `version`, `settings`, `installDate`
FROM `craft_plugins`
WHERE enabled=1
in /var/www/craft.dev/craft/app/services/PluginsService.php (101)
in /var/www/craft.dev/craft/app/etc/web/WebApp.php (226)
in /var/www/craft.dev/craft/app/index.php (196)
2014/07/30 00:51:58 [profile] [system.db.CDbCommand.query] begin:system.db.CDbCommand.query(SELECT `id`, `class`, `version`, `settings`, `installDate`
FROM `craft_plugins`
WHERE enabled=1)
2014/07/30 00:51:58 [profile] [system.db.CDbCommand.query] end:system.db.CDbCommand.query(SELECT `id`, `class`, `version`, `settings`, `installDate`
FROM `craft_plugins`
WHERE enabled=1)
2014/07/30 00:51:58 [trace] [system.CModule] Loading "messages" application component
in /var/www/craft.dev/craft/app/etc/web/WebApp.php (594)
in /var/www/craft.dev/craft/app/Craft.php (402)
in /var/www/craft.dev/craft/plugins/truncate/TruncatePlugin.php (13)
2014/07/30 00:51:58 [trace] [system.CModule] Loading "i18n" application component
in /var/www/craft.dev/craft/app/etc/web/WebApp.php (594)
in /var/www/craft.dev/craft/app/etc/web/WebApp.php (826)
in /var/www/craft.dev/craft/app/etc/web/WebApp.php (293)

-- I can include a stack trace detailing the twig "missing filter" error resulting from missing twig filter plugin, but I don't think this helps explain the missing plugin.

Any Ideas?  Thanks!!

Check if the plugin folders in craft/plugins are named correctly. Most local environments aren't case-sensitive, but most servers are.

Valid:

craft/plugins/trunchtml/
craft/plugins/import/

Invalid:

craft/plugins/truncHTML/
craft/plugins/Import/


users.onSaveUser firing multiple times Could anyone help me with this one? I have a custom plugin which needs to update a user's custom field with a particular value if they've filled out something on the registration form.

I believe the code below would work but it's running 44 times which is causing a nesting level error with xdebug.

Could anyone tell me why it would be running 44 times on a simple /users/saveUser post request with the below hook?

talentJobFamily and talentStage are both custom fields.

public function init()
{
    craft()->on('users.onSaveUser', function (Event $event)
    {
        $user = $event->params['user'];

        if ($user->talentJobFamily->value != '') {
            $user->getContent()->setAttributes([
                'talentStage' => 'engage',
            ]);

            craft()->users->saveUser($user);
        }
    });
}

This burned me awhile back. As I understand it, your code is not just running 44 times, it's actually running an endless loop... until it blows up (and likely runs out of memory).

Basically when you attach onto onSaveUser and then save again using SaveUser, Craft is going to give other plugins—that also latch onto the onSaveUserevent—a shot at manipulating the data you just saved.

So the wrinkle there is Craft is going to call your code again, resulting in the endless loop. Basically you need a way to break the loop.

Brad mentions  one way around that is to simply update the db directly instead of calling saveUser. Since you're using a custom field, that's a bit harder to do.

Another would be to define a static variable and only allow your code to call saveUser once.

public function init()
{
    craft()->on('users.onSaveUser', function (Event $event)
    {
        $user = $event->params['user'];

        if ($user->talentJobFamily->value != '') {
            $user->getContent()->setAttributes([
                'talentStage' => 'engage',
            ]);

            static $recursionLevel = 0; // is set only once

            if ($recursionLevel == 0)
            {
                $recursionLevel++; // saveUser only gets called once
                craft()->users->saveUser($user);
            }
        }
    });
}

That's also documented in my original question.


How to pragmatically set registered user status to pending and then manually send activation email I have 2 front end registration forms. One for normal users and one for admins. In each form I have a hidden input with a group value like so:

<input type="hidden" name="group" value="ypo">

In craft I have allow public registration checked and verify user address unchecked

Then I have a module that does two things:
One, it assigns new registrations to the correct group based on the hidden field and that part works like a charm, but I'll still post it here just in case:

Event::on(
        Elements::class,
        Elements::EVENT_AFTER_SAVE_ELEMENT,
        function(ElementEvent $event){
          // Check it's a front end request
          if (Craft::$app->request->getIsSiteRequest()) {
            if($event->element instanceof \craft\elements\User){
              $user = $event->element;
              if($event->isNew) {
                  $formName = Craft::$app->getRequest()->post('group');
                  if($formName==='ypo')
                  {
                    Craft::$app->getUsers()->assignUserToGroups($event->element->id, [1]);
                    logMessage('Assigning Member - id: '.$event->element->id);
                  }
                  if($formName==='yp')
                  {
                    Craft::$app->getUsers()->assignUserToGroups($event->element->id, [2]);
                    logMessage('Assigning Website User - id: '.$event->element->id);
                    $users =  Craft::$app->getUsers();
                    if($users->sendActivationEmail($user)) {
                      logMessage('Activation Email Sent');
                    }
                  }
              }
            }
          }
        }
      );

Two, it blocks new users from being activated like that:

Event::on(
          Users::class,
          Users::EVENT_BEFORE_ACTIVATE_USER,
          function (UserEvent $event) {
              if (Craft::$app->request->getIsSiteRequest()) {
                // Only trigger when request comes from the specified form
                $formName = Craft::$app->getRequest()->post('group');
                if($formName==='ypo' || $formName==="yp") {
                  logMessage('Activation Withold');
                  $event->isValid = false;

                }
              }
          }
      );

I based this code on Brandon's answer to  [a very similar question][1] and it works, in a sense it sets the status to pending, but...

[1]: How can I set new user accounts to pending status in a Craft 3 module? but added the safeguard that the code will only be triggered when the accompanying hidden form fields are present.

The problem

The problem is that whenever I want to activate the account,by emailing the activation code (manually or by my script), I get an error:

Return value of craft\web\twig\variables\UserSession::getRememberedUsername() must be of the type string, null returned

EDIT: error was due to having 'autoLoginAfterAccountActivation' => true in settings. Switching that off got rid of the error, but the activation still doesn't work.

What's even more weird is when I use the activation code from the admin panel, I'm then redirected to set your new password page instead of activation.

My log doesn't show that my script has been triggered, so it shouldn't prevent activation from link or email. Any help would be appreciated.

I did a lot of testing, If I remove EVENT_BEFORE_ACTIVATE_USER code and register a user, and then put it back in and send email, activation works. This means that setting craft\events\UserEvent::$isValid to false is permanent and can no longer be activated. Any idea why?

So, for the part preventing the activation, don't use:

if (Craft::$app->request->getIsSiteRequest())

Just do:

Event::on(
    Users::class,
    Users::EVENT_BEFORE_ACTIVATE_USER,
    function(Event $event){
        $formName = Craft::$app->getRequest()->post('group');
        if ($formName==='ypo' || $formName==="yp") {
           $event->isValid = false;
        }
    }
);

For the other part:

Event::on(
Elements::class,
Elements::EVENT_AFTER_SAVE_ELEMENT,
    function(Event $event){
        // Check it's a front end request
        if (Craft::$app->request->getIsSiteRequest()) {
            if($event->element instanceof \craft\elements\User) {
                $user = $event->element;
                if($event->isNew) {
                    $user->unverifiedEmail = $user->email;
                    Craft::$app->getElements()->saveElement($user, false);
                    $formName = Craft::$app->getRequest()->post('group');

                    if($formName==='ypo') {
                        Craft::$app->getUsers()->assignUserToGroups($user->id, [1]);
                    }
                    if($formName==='yp') {
                        Craft::$app->getUsers()->assignUserToGroups($user->id, [2]);
                        $emailSent = Craft::$app->getUsers()->sendActivationEmail($user);
                    }
                }
            }
        }
    }
);

Because you don't enforce email verification in control panel/user settings, you are skipping populating the unverifiedEmail field for the new user which is done here and that was part of the issue too :)


how can pass a variable in redirect? 1.{% set workTitle = craft.request.getLastSegment()  %}
2.{% if currentUser %}
3.     {{workTitle}}
4.{% else %}
5.  {% redirect "/visit/index?artwork=workTitle" %}
6.{% endif %}

Each line of code are working fine except the line 5. I set a value on line 1. Line 3 returns the set value but on line 5 when am trying to redirect it doesn't return correct path.
Redirect returns the below path instead of the returning the set value of workTitle.

http://abc.dev/visit/index?artwork=workTitle

I think:

{% redirect "/visit/index?artwork="~workTitle %}


How do I get the siteName in a module (Craft 3) In templates I can do something like {{siteName}}. But how do I retrieve this in a module?
I've tried:
Craft::$app->config->siteName
I can grab my custom globals via Craft::$app->globals, but the class reference doesn't say anything about the defaults like siteName

When searching the Craft 3.5.x docs for siteName, I see references to Global Variables and General Config Settings. Screenshot: https://cln.sh/egvIXhxZSzaFjeCgNLxX
However, AFAICT, it works differently for modules/plugins. As per the Sites section in the Craft 3.5.x Class Reference docs at https://docs.craftcms.com/api/v3/craft-services-sites.html, you would do something like one of the following:
Craft::$app->sites->currentSite->name;
Craft::$app->sites->primarySite->name;
Craft::$app->sites->getSiteByHandle('default')->name;
Craft::$app->sites->getSiteById(1)->name;

I'm getting the name part because I'm using PhpStorm with autocomplete set up as per article at https://nystudio107.com/blog/auto-complete-craft-cms-3-apis-in-twig-with-phpstorm
Screenshot of autocomplete in action: https://cln.sh/T77W1Y69SmR3qPUPv0sb
I'm successfully using Craft::$app->sites->getSiteById(1)->name; in one of my custom modules.


How to layout a tabbed div After playing around with Craft CMS for awhile I'm a little curious how some of you would approach this.

How would you add a tabbed div to an entry?

An example of the tab div below, it's the first example in the Bourbon Refill framework site.

Bourbon Refills

Thanks

You could achieve this by using a Matrix field for complex content or the awesome Super Table field for something a little more basic

Depending on how complicated the content is you are putting in the tabs would determine if you would use either the Matrix or Super Table fields respectively.

Take a look at the Super Table plugin if you aren't already using it.

https://github.com/engram-design/SuperTable


How can I limit a list based on a custom date field I a trying to display a simple event listing. I set up a custom date field called eventDate to note when the event happens.

This code delivers me the list of events sorted by eventDate:

 {% for event in craft.entries.find({section: 'events', order: 'eventDate'}) %}

        {{ event.eventDate }}
        {{ event.title }}

{% endfor %}

However, I want to omit events whose eventDate falls before the current date/time. If it were an entry date I guess I'd just use "after", but I am not sure how to apply that to a custom date field.

Any help is appreciated.

Actually you should be able to specify your custom field as part of the chained element criteria:

{% set currentAndFutureEvents = craft.entries.section('events').eventDate('>= ' ~ now).order('eventDate DESC') %}

Source: https://plus.google.com/111247306448293182882/posts/Uu6ZuTH1NBu


Is it possible to group my Craft plugins using the PSR-0 Standard? I'd like to group my Craft plugins using the PSR-0 Standard, I was hoping I could use the following:

CompanyName_PluginNamePlugin extends BasePlugin

plugins/
    companyname/
       pluginname/
           CompanyName_PluginNamePlugin.php

Is it possible to group plugins in sub folders? Or do I just need to prefix my plugin folder - eg:

plugins/
   companyname_pluginname/
       CompanyName_PluginNamePlugin.php

This gets messy if I want to group any further:

plugins/
    companyname_pluginname
    companyname_subgroup_otherpluginname
    companyname_secondsubgroup_otherpluginname

Thanks,

Phil.

Craft plugin naming conventions follow a specific format.

The plugin folder must live immediately under the craft/plugin folder and it must be the all lowercase name of your plugin's handle.

Your primary plugin class must live immediately in that folder and is named after your plugin's handle plus the word "Plugin".  i.e. PluginHandlePlugin.

Craft doesn't support any concept of multiple primary plugin class files in one plugin folder.


Uploading Assets - PNG changed from 72 dpi to 96 I've seen a couple strange things that Craft does when uploading images (like upsizing jpgs). The most recent, every time I upload a .png file it changes it's DPI from 72 to 96 for some unknown reason (why is this not in the documentation?) and it doesn't alert the user that it's doing so.

1) why?
2) why is this not spelled out clearly in the documentation?
3) Shouldn't Craft alert the user that it's altering your image so you don't spend hours trying to figure out what happened to your carefully planned images?
4) how can I stop this from happening?

1) why?

When you upload an image to Craft, the image is automatically “cleansed” using the same basic process that Craft uses to create image transforms. That happens to ensure that there are no malicious scripts hidden in the images, and it’s not something you can disable.

  2) why is this not spelled out clearly in the documentation?

Good point... we should probably mention it in the Assets section of the docs, or perhaps a knowledge base article. Will look into it.

  3) Shouldn't Craft alert the user that it's altering your image so you don't spend hours trying to figure out what happened to your carefully planned images?

It would get old soon, considering it happens for 99% of uploads.

  4) how can I stop this from happening?

Nothing natively, but you can bypass the whole process by manually FTP'ing up the images and running the Update Asset Indexes utility so Craft picks them up.

Worth noting that you might be better/more consistent results if your server is using Imagick over GD and make sure you're running a recent version of it.

There is a setting to this for GIFs you can disable mainly because animated GIFs  can contain hundreds of frames and transforming those can bring a server down. https://docs.craftcms.com/v3/config/config-settings.html#transformgifs


Commerce 2 Sales Not Present In Cart lineItems I have an issue where sales and sale pricing seem not to be present in the cart. This is on an ecommerce flow where this was previously all working but I had moved onto other dev work on this site for many weeks, applying Craft and Commerce updates over that time - and have now returned to the ecommerce flow to find this not working.

Craft Pro 3.3.20.1

Commerce 2.2.14

What is happening is that when I have a sale for a given product, the discounted sale price does not appear to be in the cart (Craft returns the base price) and the lineItems do not appear to have any sales in place for a given item. I stripped this back to a barebones template:

{% set cart = craft.commerce.carts.cart %}

{% if cart.isEmpty() %}
  Empty
{% else %}
    {% set lineItems = cart.lineItems %}
    {% for item in cart.lineItems %}
      {% set variant = item.purchasable %}
      {% set product = variant.product %}

{{ product.title }}

        {% if item.onSale %}
            Sale: {{ item.salePrice }} - Was: {{ item.price }}
        {% else %}
          No Sale: {{ item.price }}
        {% endif %}

    {% endfor %}
  {% endif %}

{{ dump(lineItems) }}

  itemTotal:{{ dump(cart.itemTotal) }}

  itemSubtotal:{{ dump(cart.itemSubtotal) }}

  total:{{ dump(cart.total) }}

  totalDiscount:{{ dump(cart.totalDiscount) }}

  totalTaxablePrice:{{ dump(cart.totalTaxablePrice) }}

  totalShippingCost:{{ dump(cart.totalShippingCost) }}

  totalSaleAmount:{{ dump(cart.totalSaleAmount) }}

  getTotal():{{ cart.getTotal() }}

  getItemSubtotal():{{ cart.getItemSubtotal() }}

  getItemTotal():{{ cart.getItemTotal() }}

So what happens:

I add a product to the cart which has an applicable sale (I've tested it has one by triple-checking the sale logic in the CP against the product and checking the product details in twig via {% set productSales = product.defaultVariant.getSales() %} to confirm it has applicable sales.
Once in the cart, via the template above, the returned prices all reflect the base price - no sale price.
The sales object has nothing in it from lineItems and item.onSale is false (though I notice lineItems[0].snapshot.onSale is true (but I'm not certain what that is supposed to represent).

I'd normally conclude I was doing something wrong, but I believe my code follows the Commerce template examples and was working fine when I last worked on it (awhile back... maybe October).

I wondered if some commerce update has fundamentally changed something about how this should all work but could find no clues in the changelog.

Can anyone confirm there isn't something fundamentally wrong with my cart code above - or has something changed in Commerce? Or could there actually be a bug?

EDIT: The non-sale (base price) I see in the cart is reflected in the actual order details in the CP.

After further testing and debugging, it turns out these issues are related to this bug in Commerce:

https://github.com/craftcms/commerce/issues/1206

As per that github issue, it was a Discount with Ignore sales when this discount is applied enabled that was the culprit, but shouldn't be once that bug is fixed.


How can I make the redactor table plugin show extened options? When I use the Redactor table plugin everything works but I can't seem to get any extended functions to work like cellpadding, cellspacing, table border color etc.

How do I do that?

I don't think the features you're asking for are possible with the default Redactor Table plugin Craft comes with, and as far as I know, it is also the only table plugin for Redactor out there.


getParam not working on live server? Not sure what is happening but

{% set searchQuery = craft.request.getParam('q') %}

Does not seem to be working at all on a different server than staging. Anyone seen this before? Any ideas what could be preventing getParam from working? Here is what the end of my url looks like with the param: results?q=test

I then later try to display the search parameter like.

Search Results {{searchQuery}}

But it only displays Search Results leaving the parameter empty... and my search results seem to be returning everything since it is basically doing a wildcard search since I'm using this below to query the matching results...

{% paginate craft.entries.search(searchQuery).order('score').limit(10) as entries %}

Everything works as expected on staging server.

The client must have added a new rule which was causing the problem.

changed from

RewriteRule (.+) index.php?p=$1 [L]

to

RewriteRule (.+) index.php [L]

Not exactly sure why they added that but...


Free orders with Craft Commerce I run Craft Commerce and want people to get certain items for free, but still pass them through Commerce order system (in order to collect the emails and also perform some automation)

So basically they enter code and get 100% discount.

Now, if the person has ONLY free item in their cart, I don't want to ask them for credit card data or send to payment gateway. I want to just authorize the 'payment' of $0.00

I know I could use some template logic and use Manual or Dummy gateway, but I'm afraid some people will try to fool the system, so I want to make sure the 'free' gateway only works if order total is zero.

Does anyone know some plugin / custom gateway that I could use here?

Since it looks like the solution does not exist yet, I ended up creating custom plugin/gateway.

Works same as Manual gateway with the only difference that it only approves free purchases.

https://github.com/intoeetive/freecheckout


How can I force a request type (HttpRequestService::isCpRequest vs. HttpRequestService::isSiteRequest)? Is there a way to force a request type (HttpRequestService::isCpRequest vs. HttpRequestService::isSiteRequest)?

In my case I'm generating some indexing data on entries.onSaveEntry. I'm trying to use some UrlHelper methods, but the resulting urls are wrong, since it thinks it is a cp request.

I have to do something like this load templates:

craft()->templates->setTemplateMode(TemplateMode::Site);

Is there any way to do something similar for request type?

There's no setRequestType() method or the like, no. The HttpRequestService will consider any request with the cpTrigger in its URL a control panel request, and I don't see any easy way around that.

It's a bit convoluted, but what you could do is to wrap whatever code you're currently executing in your entries.onSaveEntry in a controller action method, and then use Guzzle in your onSaveEntry event handler to trigger a site request (i.e. a request without the cpTrigger in the URL) to that action.

Here's how your controller action could look:

<?
namespace Craft;

class MyPluginController extends BaseController
{

    protected $allowAnonymous = ['actionGenerateIndex'];

    public function actionGenerateIndex ()
    {

        // Get entry (based on a ?entryId URL parameter
        $entryId = craft()->request->getParam('entryId');
        $entry = craft()->elements->getCriteria(ElementType::Entry, [
            'id' => $entryId,
        ])->first();

        // ...your code to generate the index data here

    }
}

Here's how you could fire off a request to this action using Guzzle, stripping the cpTrigger from the action URL to make sure Craft treats the action as a site request:

craft()->on('entries.onSaveEntry', function (Event $e) {

    // Get entry
    $entry = $e->params['entry'];

    // Get action URL, strip the `cpTrigger` segment
    $actionUrl = UrlHelper::getActionUrl('myPlugin/generateIndex', [
        'entryId' => $entry->id,
    ]);
    $actionUrl = str_replace(craft()->config->get('cpTrigger').'/', '', $actionUrl);

    // Request action
    $client = new Guzzle\Http\Client();
    $request = $client->get($actionUrl, [], [
      'headers' => [
        'X-Requested-With' => 'XMLHttpRequest',
      ],
    ]);
    $request->send();

});


What is the difference between dateModified, dateCreated and dateUpdated? Can someone explain exactly what each of the above refers to when talking about assets? In particular, the difference between dateModified and dateUpdated.

dateCreated and dateUpdated are pretty standard columns that you can find in pretty much every Craft database table. They store the date in which the row was created and last updated, respectively.

dateModified is an assets-specific thing, and that stores the time that the file was last modified, according to the file system.


What does the Before compile event inside the Contact Form plugin do? I see there is an undocumented event called onBeforeMessageCompile in the Contact Form plugin source:

Craft::import('plugins.contactform.events.ContactFormMessageEvent');
$event = new ContactFormMessageEvent($this, array('postedMessage' => $postedMessage));
craft()->contactForm->onBeforeMessageCompile($event);

My questions are:

How does one use this event? What can it be used for? and
Why is an $event object being instantiated at this point in the controller?

How does one use this event? What can it be used for?

It gives plugins a chance to manipulate the posted message to however they want instead of using the plugin's default implementation.  A plugin would listen to it like they listen to any other event. Something like:

public function init()
{
    craft()->on('contactForm.onBeforeMessageCompile', function(Event $event) {
        $postedMessage = $event->params['postedMessage'];
        $event->messageFields = $postedMessage;

        // Do your thing.

        $event->message = "Your custom compiled message";
        $event->htmlMessage = "Your custom compiled HTML message";
    });
}

  Why is an $event object being instantiated at this point in the controller?

The short answer is "because that's how events work". :) Is there a particular thing that's confusing you?


Can I set a Commerce order custom field from a plugin On a site with Commerce 1, I have set up some custom fields for order related data. I can set them using form inputs like so:

<input type="hidden" name="fields[myCustomField]" value="1">

I'd prefer to set it from a plugin using an even hook.

craft()->on('commerce_orders.onBeforeOrderComplete', function($event) {
    $order = $event->params['order'];
    $order>myCustomField = 3;

}

It fails with a CExeption:

  Property "Craft\Commerce_OrderModel.myCustomField" is not defined.

I can set my field from the form and then read the value from my plugin, so the field is readable, but I am unable to set it. So far, all I can figure out is that the Craft BaseModel strictAttributes parameter is set to true, and the model does not seem to have any attributes set for my field. The BaseModel attempts to set the field value by calling parent::_set(), which is what triggers the CExeption.

Is there any way to set my field from my plugin?

You can use $order->setContent()

craft()->on('commerce_orders.onBeforeOrderComplete', function($event) {
  $order = $event->params['order'];
  $content = array('myCustomField' => 3);
  $order->setContent($content);
}


Including CSS and JS files on multilanguage sites Up till now, all my craft sites were single language. When including CSS and JS files from web folder, i used to use {{siteUrl}} variable, like this:

<script src='{{siteUrl}}/plugins/jquery.js'></script>

Such path was rendered to this:

<script src='http://website.com/plugins/jquery.js'></script>

Unfortunetly, on multilanguage websites, where different languages have different url path (site.com and site.com/en), {{siteUrl}} variable changes and paths to these files are incorrectly rendered, like this:

<script src='http://website.com/en/plugins/jquery.js'></script>

What variable can i use that will give me path to web folder - regardless of currently used language?

Use the alias instead {{ alias('@web') }}

It will always point to your web directory


Rebuilding my portfolio site from scratch in CraftCMS 3 and having an issue getting content to display I'm rebuilding my portfolio site in CraftCMS 3 and started building some VERY basic templates to test the sections and ensure everything is connecting properly before uploading a bunch of content and building all the more complicated elements in my templates. I have a layout template and a home page index template that extends this layout. The layout is just a basic HTML5 boilerplate from emmet and a block tag. The index page template has a headline pulled from a single and an entry query that will generate a list of portfolio pieces from a structure. Below is the code for this page in its current, basic state.
{% extends "_layouts/main" %}

{% block main %}

{{ entry.title }}

{# Create an entry query #}
{% set portfolioGrid = craft.entries().section('portfolio').all() %}

{# Display the entries #}

    {% for portfolioTile in portfolioGrid %}

{{ portfolioTile.title }}

    {% endfor %}

{% endblock %}

{{ entry.title }} displays fine and the  tags are generated but the {% for %} loop isn't doing anything. I'm not getting any errors in the debug toolbar that I can see. The section name is correct. What else am I missing here? This should be a simple, straightforward query but it's blank.

Craft 3.5 (not sure when this occurred so maybe earlier) seems to default to a "save" draft"c option for new entries. You have to publish the draft before it can go live. All working now though.


Change image title programatically? I save image files from within my plugin through assets->insertFileByLocalPath().
Unfortunately, i can't give the image title as paramter to the method.

What is the easiest way to change the image title after insert, espacially considering different languages?

UPDATE

Ok, i found a solution (moved to the answer), but how do i save values for different languages?

Here's how i solved it:

$criteria = craft()->elements->getCriteria(ElementType::Asset);
$criteria->id = $assetId;
$elements = $criteria->find();
$element[0]->getContent()->title = 'New Title';
craft()->elements->saveElement($element[0]);


Prescribed way to set cookies (from plugin or Twig) From either a plugin or a Twig template, is there a prescribed way to set (and get) cookies?

I've seen https://github.com/lewisjenkins/craft-lj-cookies/, which just uses PHP's setcookie.

Also noticed getCookie in craft.request. https://craftcms.com/docs/templating/craft.request#getCookie

I've noticed the cookies set with setcookie aren't readable by getCookie. How should one set a cookie so that it is readable by getCookie?

Cookies cannot be set via templates. However, in a plugin you have access to Craft's services. The UserSessionService has the saveCookie function.

craft()->userSession->saveCookie($cookieName, $data, $duration);


Mandatory order fields in Craft Commerce 2 Ist it possible to add validation rules for Carts/Orders? I need to check whether there is at least one list item added to the cart before payment is initiated.

Unlike addresses (Address::EVENT_REGISTER_ADDRESS_VALIDATION_RULES) there seems not to be an event for manipulating rules.

I’d prefer a server-side solution.

There is a setting for this specific instance allowEmptyCartOnCheckout.

Creating a commerce.php file in the config directory with the following code will prevent customers from checkout out with an empty cart.

<?php

return [
  'allowEmptyCartOnCheckout' => false
];


Craft Setup 3.6.5.1 with MYSQL with SSL version 5.7.29 I'm trying to install Craft version 3.6.5.1 with MYSQL version 5.7.29 from Azure.
While setup it fails with the following error
Testing database credentials ... failed: SQLSTATE[HY000] [9002] SSL connection is required. Please specify SSL options and retry.

Looks like you got this sorted using Craft's database attributes config setting.
https://github.com/craftcms/cms/issues/7583#issuecomment-781168257


Craft causing Apache2 to max out memory and swap Background: Our Craft 2 site has been working for years. Now I want to upgrade to Craft 3. On a test machine, I have been having problems with the upgrade process. I have tried 3 classes of things:

Install Craft 3 from scratch. This is working, but I'd really like to bring the old database along.
Install Craft 3 and the old database, and try to do the upgrade.
Just duplicate the old Craft 2 system with the old database, to get something to work.

The problem: Anything I do with the old database (#2 or #3, above) is likely to cause Apache2 to go berserk, using up all memory and swap and bringing the system to a nearly complete halt. Not every page does this, but, for example, the CP Entries page does this when it attempts to list entries.

I haven't been able find any errors in logs, though I'm not especially adept at that.  Any thoughts?

I sent the Craft 2 database to Pixel and Tonic, and Brad kindly updated it for me.


Featured thumb with image or video I've got a use case where I'd like to be able to not only upload an image for a blog entry's "featuredThumb" but also have the ability to upload a video. I was originally using animated GIFs but they are huge. So much like twitter I'm going to switch them for videos.

Currently, I have something that I'm sure you'll be very familiar with. I've got an asset field within my blog section that accepts an image and my template code looks like this:

{% for entry in craft.entries.section('blog').find() %}

                {{ entry.title }}

{% endfor %}

I'd like to be able to switch the image for a small HTML5 video. This poses a few problems, as numerous video formats are required for cross browser compatibility, along with an image as a poster / fallback.

My current solution is to switch the "featuredThumb" asset field, for a new matrix field that contains two block types:

Image

Asset field - Name: Image

Video

Asset field - Name: Videos (allows multiple files so the user uploads MP4, OGV, WebM)
Asset field - Name: Fallback Image

Then in my template update to:

{% for entry in craft.entries.section('blog').find() %}

            {% for block in entry.featuredThumb %}
                {% switch block.type %}

                    {% case "image" %}

                    {% case "video" %}

                        <video width="320" height="180" autoplay loop>
                            {% for video in block.videos %}
                                <source src="{{ video.url }}" type="{{ video.getMimeType() }}">
                            {% endfor %}

                        </video>

                {% endswitch %}
            {% endfor %}

                {{ entry.title }}

{% endfor %}

This is the best solution I'm able to come up with, but it seems a little over-engineered. I'd love to see other people's take on this.

Cheers,

Tom

Tom, we applied the same technique to several clients. We also tried having one asset input and only allowing images and videos.  Then as we loop through the asset field we check for media type. However I think breaking it up like you have, is a better user experience so they know to add either video or image. We have also added a plain text field and allowed them to add Vimeo/YouTube embed code for the video.


Create a new row every 3 items in a matrix I need to output a row every three items in a matrix. Here is what I have so far. The problem is that this outputs a row every three items but it duplicates each matrix item three times and puts them in a row rather than taking the items as they come. Any help here would be very much appreciated Thanks.

{% set aboutInfo = entry.imageLinkBoxes %}
{% set infoGrouped = aboutInfo|batch(3) %}

{% for group in infoGrouped %}
    {% for block in entry.imageLinkBoxes %}

        {% if block.type == "imageLinkBox" %}

            {% set row = 'row--' ~ loop.index %}

                {% for entry in group %}

                                    {{ block.entry.first().title }}

{{ block.text }}

                {% endfor %}

        {% endif %}

    {% endfor %}
{% endfor %}

To my understanding this should work for a matrix field (called imageLinkBoxes) that contains only one type of block (imageLinkBox) that has 3 fields (imageLinkBox.linkText, imageLinkBox.linkUrl and imageLinkBox.text)

{% set aboutInfo = entry.imageLinkBoxes %} {# get all the blocks in the matrix #}

{% if aboutInfo|length %} {# check we have at least one block in the matrix #}

    {% set infoGrouped = aboutInfo|batch(3) %} {# group them in groups of 3 blocks #}

    {% for group in infoGrouped %}  {# loop through all the groups #}

        {% set row = 'row--' ~ loop.index %} {# set the class #}

 {# open the row #}

        {% for block in group %} {# loop through all the blocks in current group #}

                            {{ block.linkText }}

{{ block.text }}

        {% endfor %}

 {# close the row #}

    {% endfor %}

{% endif %}

If for some reason you have more than one type of block in the matrix, then you would need to do a change:

Replace:

{% set aboutInfo = entry.imageLinkBoxes %}

With:

{% set aboutInfo = entry.imageLinkBoxes.type('imageLinkBox') %}

That would filter the blocks to only those of the type imageLinkBox.


Organizing repeating content on a single page I have a page with a slider on it, which includes three rotating slides. In Craft, I am currently setting up each slide as an entry under a "Home Page Content" channel, and then creating an entry field for them on the home page single.

Is that the simplest/correct way to do it? Is there an alternate way that allows me to enter the number of slides and the information for each directly on the home page single?

You could use a Matrix Field! Each block in your matrix field can be a slide with whatever information you need inside. While your method will work fine, using a matrix will help keep your content organized on a single entry.


Craft Commerce 2: PayPal Express not working - Correlation id: unknown I try to get PayPal Express running but have quite some difficulties.
I use the default templates for testing and can get through the whole checkout and payment process, but then PayPal closes and in the console I see the following

http.js:114 PaypalUncaught Error: Request to post http://xxx.local/index.php?p=actions/commerce/payments/complete-payment&commerceTransactionHash=xxx&token=xxx&PayerID=xxx failed with 400 error.
Correlation id: unknown
{"error": "Es ist nicht möglich, Ihre Dateneingabe zu prüfen."}

The error message can be translated to: It's not possible to check your data input.

In my Craft config, I changed the token name, as described here: https://github.com/craftcms/commerce-paypal

Any ideas what the reason for this could be?

The Craft support helped me to figure this out. The problem was, that the CSRF wasn't properly disabled.

So in my config/general.php I have the following:

if ( !function_exists('isApiRequest') ) {
    function isApiRequest ()
    {
        $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);

        $actions = [
            'actions/charge/webhook/callback',
            'actions/commerce/payments/complete-payment',

        ];

        foreach ($actions as $action) {
            if ( strpos($path, $action) || strpos($query, $action) ) {
                return true;
            }
        }

        return false;
    }
}

return [

'*' => [

    'enableCsrfProtection' => !isApiRequest(),
    ...


What is the recommended Nginx configuration for Craft? I'm looking for the recommended way of how to configure Nginx server to run Craft.

Currently, I use the following configuration, it works, but I don't know if it could be better:

server {
    listen 80;
    server_name example.com;
    root /home/vagrant/craft/public;

    index index.html index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/example.com-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~* (?:^|/)\. {
        deny all;
    }

    location ~* (?:\.(?:bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)|~)$ {
        deny all;
    }

    location ~* \.(?:manifest|appcache|html?|xml|json)$ {
        try_files $uri /index.php?$query_string;
        expires -1;
        access_log /var/log/nginx/example.com-access.log;
    }

    location ~* \.(?:rss|atom)$ {
        try_files $uri /index.php?$query_string;
        expires 1h;
        add_header Cache-Control "public";
    }

    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
        try_files $uri /index.php?$query_string;
        expires 1M;
        add_header Cache-Control "public";
    }

    location ~* \.(?:css|js)$ {
        try_files $uri /index.php?$query_string;
        expires 1y;
        add_header Cache-Control "public";
    }

    location ~* \.(?:ttf|ttc|otf|eot|woff)$ {
        try_files $uri /index.php?$query_string;
        expires 1M;
        add_header Cache-Control "public";
    }
}

Definitely not an nginx expert, but this has been working well for anyone that's tried it so far:

server {
  listen 8080 default;
  charset utf8;
  server_name example.com;
  root /path/to/example.com/public;
  index index.php index.html;

  # Logs
  error_log /usr/local/etc/nginx/logs/example.com.error.log debug;
  access_log /usr/local/etc/nginx/logs/example.com.access.log;

  location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }

  location ~ ^(.*)$ {
      try_files $uri $uri/ /index.php?p=$uri&$args;
  }

  location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
    expires max;
    add_header Pragma public;
    add_header Cache-Control "public, must-revalidate, proxy-revalidate";
  }

  location = /robots.txt  { access_log off; log_not_found off; }
  location = /favicon.ico { access_log off; log_not_found off; }
  location ~ /\. { access_log off; log_not_found off; deny all; }
}

Note that it's making a few assumptions about how you've setup php-fpm and what ports things are listening on, so those may need to be adjusted.


It seems /login is a reserved route - is that correct? When I hit /login in a browser, I am redirected to the home page (this is on a vanilla install without any entries or routes set up).

Is this a reserved route?
Can I change that so I can use it for my own route?
If it's reserved, what other routes are there that we should know about?

That is set by the loginPath config setting so yes, you can change it in your craft/config/general.php file.

There are a few other configurable URLs for templates and redirects, all of which are described on that page.


How can I pass variables to email's template onBeforeSendEmail I'm trying to add some variables to email's template to be rendered before email is sent.

I was able to modify emailModel like customHeaders and subject but I couldn't pass custom variables to the template.

How can I pass variables to email's template using onBeforeSendEmail event?

I don't know if this is the same kind of thing you are looking for, but I think it's close.

I created a plugin that handles a contact form submission. When the user fills out the form, an email is sent to me & the person who filled out the form. I'm not listening to onBeforeSendEmail, but rather listening for a save event that I call via controller.

I told Craft that I needed a settings area in my plugin, then I created a textarea where I could manage the email that was sent to the user(s). This is what that looks like:

Hi {{firstName}} &mdash; thank you for reaching out to us!
We wanted to drop you a quick note that we've received your information and we'll be in touch very soon!

Regards,
The friendly folks at this website

To get {{firstName}} to use the value that were entered in the form, I used this in my plugin's service file.

That way it would read "Hi Steve - thank you for..."

MyPluginService.php

...
$email = new EmailModel();
$email->fromEmail = $emailSettings['emailAddress']; // set in CP
$email->replyTo = $emailSettings['emailAddress'];   // set in CP
$email->sender = $emailSettings['emailAddress'];    // set in CP
$email->fromName = $emailSettings['senderName'];    // set in CP
$email->toEmail = $contact->email;
$email->subject = $settings->guestSubject;
$email->body = craft()->templates->renderString($settings->welcomeEmailMessage, array('firstName' => $contact->firstName));

if (craft()->email->sendEmail($email)) {
    return true;
}
....

It's the craft()->templates->renderString() part that lets me hook into my plugin to set the variable values.

Hope this helps!


relatedTo currentUser AND entry I have channel X set up where it's entries have a "users field", an "entries field" (that are picked from channel Y) and a whole bunch of other data with regards to the how a defined user has interacted with the defined entry.

Whilst viewing an entry from channel Y I wish to dig into the data kept in channel X, more specifically the entry in channel X that has both the same users field as the currentUser AND the same entries field as the entry viewed.

My current solution is the following and makes me cringe with the thought of things getting bigger...

{% set fetchedResults = craft.entries.section('channelXhandle').relatedTo(currentUser) %}
{% for resultEntry in fetchedResults %}
    {% if resultEntry.member.first().id == currentUser.id %}
        {# grab the fruit #}
    {% endif %}
{% endfor %}

It's done the trick up until now, but now I want to find out if there is an entry in channel X that has both the channel Y entry and currentUser defined together.

I've tried the following but it didn't help. It retrieves all the entries as though the relatedTo params are currentUser or entry rather than currentUser and entry that I'm looking to retrieve.

{% set fetchedResults = craft.entries.section('channelXhandle').relatedTo(currentUser, entry) %}

Any pointers in the right direction much appreciated!

Solution in light of Steve's Answer

{% set fetchedResult = craft.entries.section('channelXhandle').relatedTo('and',
    { sourceElement: currentUser, field: '[users field handle]' },
    { targetElement: entry, field: '[entries field handle]' }
).first() %}

Note, I tagged on .first() in the above since in my case there should only be one resulting match anyway.

If you take a look at the Relations page from the docs. The last example at the bottom of the page shows how you can do a query using AND for multiple criteria.

The example from the page:

  Passing Multiple Relation Criteria

  There might be times when you need to factor multiple types of
  relations into the mix. For example, outputting all of the current
  user’s favorite cocktails that include Gin:

{% set gin = craft.entries.section('ingredients').slug('gin').first() %}

{% set cocktails = craft.entries.section('cocktails').relatedTo('and',
    { sourceElement: currentUser, field: 'favoriteCocktails' },
    { targetElement: gin, field: 'ingredients' }
) %}

  That first argument ('and') specified that all of the relation
  criteria must be matched. You can pass 'or' instead if you want any of
  the relation criteria to match.

Hopefully that'll point you in the right direction.


Asset URLs not HTTPS I'm currently redirecting all requests to https. I've updated the siteUrl variable and all of my entry links have been correctly updated but the image links are still http.

When I go to the admin panel all of the CSS/JS files wont load because the site is trying to load them over HTTP instead of HTTPS.

server {

  listen   443;

  root /var/www/<url>/htdocs;
  index index.php index.html index.htm;

  ssl on;
  ssl_certificate /etc/ssl/cert.crt;
  ssl_certificate_key /etc/ssl/rsa.key;
  server_name <url>;
  proxy_set_header X-Forwarded-For $remote_addr;
  proxy_set_header X-Forwarded-Proto https;
  client_max_body_size 10M;

  location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/opt/remi/php55/root/var/run/php-fpm/php-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
  }

  location / {
  try_files $uri $uri/ @rewrites;
  }

  location @rewrites {
    rewrite ^(.*) /index.php?p=$1 last;
  }

  # Errors

  error_page 404 /index.php;
  error_page 500 502 503 504 /50x.html;

  location = /50x.html {
    root /var/www/<url>/htdocs;
  }

  # Enable gzip compression
  gzip on;
  gzip_comp_level 2;
  gzip_http_version 1.0;
  gzip_proxied any;
  gzip_min_length 1100;
  gzip_buffers 16 8k;
  gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;

  # Disable for IE < 6 because there are some known problems
  gzip_disable "MSIE [1-6].(?!.*SV1)";

  # Add a vary header for downstream proxies to avoid sending cached gzipped files to IE6
  gzip_vary on;

}

Yii will check for the following server environment variables when determining if it’s an SSL request:

HTTPS – should be set to “on”, “ON”, or “1”; or
HTTP_X_FORWARDED_PROTO – should be set to “https” or “HTTPS”

Apparently this is a common issue when running PHP on Nginx, since that HTTPS env variable is only an Apache convention.

Here’s an article that demonstrates how to fix it. According to that, you just need to add the following to your server config.

fastcgi_param HTTPS on;

You can probably add it right before/after your other fastcgi_param line.


How could I submit the Charge plugin using Ajax? I am using the Charge plugin to handle my Stripe payments. How could I submit this form via Ajax Post and get the response? I have tried using the following code but it tells me it's a bad request:

this.ajaxReq = $.ajax({
   url: 'charge/charge',
   data: $('#charge-form').serialize()
}) .done(function(data) {
   console.log('success!');
   console.log(data);
}) .fail(function(data){
   console.log(data);
});

You need to specify the HTTP method used by the $.ajax method.

By default, it uses GET, but you need to submit the form using POST.


Form field validation I am relatively new to Craft CMS and have been using the formBuilder2 plugin to create forms. No problem this far. I have been able to use ElementApi to output the forms into Json. Still no problem.
My issue starts when I have to add validation to my form fields. I have read the "The Definitive Guide to Craft Validation for Plugin Developers (The Book)" and I am still very much confused as to how I actually implement validation on specific field in my form. In a nutshell, how can I add a email validator to an field that I named email. Also, how do I implement this to the fieldset if all I have to go with is:

        {% set fields = tab.getFields() %}
        <fieldset class="{{ tab.name |lower|replace(' ', '-') }}">
            <legend>{{ tab.name }}</legend>
            {% for field in fields %}
            {% set input = craft.formBuilder2.getInputHtml(field) %}
            {{ input | raw }}
            {% endfor %}
        </fieldset>
        {% endfor %}

Not to sound silly but why, why, why why????
Please can someone help me....

The controller you're posting to is responsible for the validation. In the case of the entries/saveEntry controller for example there's an Entry model populated and then run through the validation. Each model attribute and all the fields (content) have to give their ok for the complete model to validate correctly.

For the fields it's the field type's attribute type and an optional validate method that are responsible. Have a look at this plugin by Marion Newlevant, it provides a simple field type with configurable validation and is probably a good example for what you're trying to achieve.

https://github.com/marionnewlevant/craft-mn_matchinput


Get related variants from entry and then show products for related variants and then loop only the variants that are related in commerce Struggling with this one. If anyone can help me...?
I have a section of entries called Vehicles.
In this I have a related Variants field type. This has many different variants that related to each vehicle.
On each vehicle page, I want to loop through the main products that are related by their variants but then loop through and only show the related variants that are link to the vehicle.
As an example.
I have a vehicle that has the following related variants.
Product A - Variant 1
Product A - Variant 2
Product A - Variant 3
(product A also has variant 4, 5 and 6)
Product B - Variant 2
Product B - Variant 3
Product B - Variant 6
(product B also has variant 1, 4 and 5)
On the vehicles page. I want to show products A and products B with a list in each showing the related variants. So as per the example. Product A -  would show Var1, 2 and 3 and Product B - would show Var2, 3 and 6.
Here is some code I have as a starting point:
{% set selectedVehicle = craft.entries
  .section('vehicles')
  .id(vehicle)
  .with( [
    'relatedVariants',
    'vehicleBrand',
    'oemFixings',
    'afterMarketFixings'
  ])
  .one() %}

{% set variantsQuery = selectedVehicle.relatedVariants %}
{% set variants = variantsQuery.all() %}

{% set wheelSpacers = craft.products
  .type('wheelSpacers')
  .hasVariant(variantsQuery)
  .orderBy('title ASC')
  .all() %}

After some further trial and error I have made this work like so.
However, it feels like there is or should be a simpler method....what am I missing or what is a better approach?

{% set selectedVehicle = craft.entries
  .section('vehicles')
  .id(vehicle)
  .with( [
    'vehicleBrand',
    'oemFixings',
    'afterMarketFixings'
  ])
  .one() %}

{% set variantsQuery = selectedVehicle.relatedVariants %}

{% set variantsByIDS = [] %}

{% for variant in variantsQuery %}
  {% set variantsByIDS = variantsByIDS|push(variant.id) %}
{% endfor %}

{% set wheelSpacers = craft.products
  .type('wheelSpacers')
  .hasVariant(variantsQuery)
  .orderBy('title ASC')
  .all() %}

Now Show the products and then loop only the variants that are related to the vehicle.

{% for product in wheelSpacers %}
  {% set productVars = product.variants|filter(purchasable => purchasable.id in variantsByIDS) %}

{{ product.title }}

{{ product.id }}

This product has {{ product.variants|length }} options in total

Only {{ productVars|length }} options are related

    {% for purchasable in productVars %}
      {{ purchasable.title }}
    {% endfor %}

{% endfor %}


Craft 3 Plugins - Setting Template Path To Site Templates In Craft 2, if you have a plugin that needs to load a view from the site templates directory, you could just go:

# Set the template context to site templates.
$path = craft()->path->getSiteTemplatesPath();
craft()->path->setTemplatesPath($path);

# Now we can load sweet templates from the sites templates dir, not the plugin one.
craft()->templates->render("/path/to/particular/site/template");

I'm struggling with doing this in a Craft 3 plugin. I thought I was on the right track with the below, but the context isn't updating.

# Set the template context to site templates.
$path = Craft::$app->getPath()->getSiteTemplatesPath();
Craft::$app->getView()->setTemplatesPath($path);

# Or this seems like it might do the same?
# Craft::$app->getView()->setTemplateMode(View::TEMPLATE_MODE_SITE);

# Render the view!
Craft::$app->getView()->render("/path/to/particular/site/template")

This results in explosions. It shows the error:

  Unable to resolve view file for view
  '/path/to/particular/site/template': no active view context.

Setting the template context is the bit that's not working. It's still looking at the plugin template directory.

Ok I've made sense of this - if you set the view template mode/context to site, then use the view renderTemplate() method (not render) it works.

So this is how you achieve the same thing in Craft 3.

# Set the template context to the site mode.
Craft::$app->getView()->setTemplateMode(View::TEMPLATE_MODE_SITE);

# Now using the renderTemplate() method, it will work.
$html = Craft::$app->getView()->renderTemplate("/path/to/particular/site/template");

Edit: Lenka commented pointing out there is actually some official documentation on this.