How to list an entries selected nested categories? I have a entry field populated by categories. The number of categories an entry can have is unlimited and the categories are set up as nested categories i.e.

first level
---second level
------third level
first level 2
---second level 2
------third level 2

I want to be able to list out any categories selected at the third level that the entry may have selected if a certain parent category is identified. So far I have the following code. This works but lists all child categories for the identified parent instead of just the selected ones.

What am I missing?

{% set cats = entry.categories %}
{% for cat in cats %}
    {% if cat.slug == 'secondlevel1' or cat.slug == 'secondlevel2' %}
        {% set catChildren = cats.descendantOf(cat.id).descendantDist('1') %}
        {% if catChildren | length %}
            {% for childCat in catChildren %}
                {% set image = childCat.buildingBlockIcon.first() %}
                    {% if image %}
                        <img alt="{{ childCat.title }}" src="{{ image.url }}" />
                    {% endif %}
            {% endfor %}
        {% endif %}
    {% endif %}
{% endfor %}

----- EDIT Solution -----

I worked this out

by changing one line of code line 4 above from

{% set catChildren = cat.level(2).children %}

to

{% set catChildren = cats.descendantOf(cat.id).descendantDist('1') %}

Now works a treat. Any better solutions feel free to post.

The final solution I got to work:

{% set cats = entry.categories %}
{% for cat in cats %}
    {% if cat.slug == 'secondlevel1' or cat.slug == 'secondlevel2' %}
        {% set catChildren = cats.descendantOf(cat.id).descendantDist('1') %}
        {% if catChildren | length %}
            {% for childCat in catChildren %}
                {% set image = childCat.buildingBlockIcon.first() %}
                    {% if image %}
                        <img alt="{{ childCat.title }}" src="{{ image.url }}" />
                    {% endif %}
            {% endfor %}
        {% endif %}
    {% endif %}
{% endfor %}


One structure vs a structure per section? I'm new to Craft CMS and I would appreciate some advice re the best approach to take for modelling content.

I'm coming from an ExpressionEngine background, where I would typically use one pages channel for standard text pages, usually in conjuction with the Structure add-on to generate a nice site tree in the control panel where users could move page entries around between various sections as needed.

Now in Craft, I see a Structure type and it's tempting to have one structure called Pages to act in a similar way. This would enable the main nav for the site to be dynamic using the nav tag, and users could easily move pages wherever they want site wide.

So I would end up with the following content model:

Singles: Homepage
Channels: News, Events etc
Structures: Pages

Pages would end up as a site map excluding the homepage, with each top level entry being a site section e.g.

About
-History
-Team
News (page used for news landing)
Events (page used for events landing)
Visit
-Facilities
-Opening Hours
Contact
etc

However, I see that the Happy Lager demo site takes a one structure per section approach. Also, early on in the Mijingo video series, it cautions against using one structure for most/all your site.

I am keen to use best practice from the off, so would appreciate if you could advise whether having one master pages structure is a valid approach or why it's a bad idea.

Many thanks

TL;DR: Let the content dictate how you model and architect it.

If the site is sufficiently small or sufficiently simple, I think either approach is ok. My team has used both approaches, and it really depends on the Client's needs.
An incomplete list of reasons to use separate channels and structures are:

Tagging/Categorizing things like Products is probably easier to manage when they are in their own channel
Your client has multiple divisions and they are managing their content separately, and thus need to control permissions
You want to reference content pieces in multiple places

An incomplete list of reasons to use a Site Pages approach are:

The content isn't highly relatable, or doesn't rely much on relationships/categories/tags
The site is small enough
The client has demonstrated an ignorance or apathy for understanding content and "just needs a site"
Project management constraints: fast timeline, cheap budget, or pain-in-the-ass-go-away clients.

If you're familiar with ExpressionEngine and channels, then you probably know that things don't have to be pages to be in a channel or structure. You can combine the two approaches for a slightly sophisticated site.
Here's an example we have done that uses both approaches:

We used structures for things like Executive Team Bios that appear on one page. The landing page is in the Site Pages structure, which supports 3 levels max, while the bios themselves are managed in a separate structure (a channel would have been fine here, but the structure allows us to order by rank without an "order" field). We put a Management Widget blockType in a Matrix/Neo field so the user can put that content where they need to. That block allows them to select "Management" or "Board of Directors", so the block is reusable on multiple pages.
This block could have easily been a nested block using Neo/SuperTable fields, but the categorization (Management/Board) is what led us to make it it's own structure. Some members belong to both groups, and without the relationship it would have required inputting and managing duplicate content.

If navigation is your concern, you can use plugins like Navee or A & M Navigation to allow content managers to build navigations. Or on smaller sites, we just have a "Navigation" global field group with entries fields, and users can build out header and footer navigation as they deem appropriate.
My advice is let the content dictate how you model and architect it. If you have complex content requirements, don't try to shoehorn them into a one-structure architecture. Craft's CP allows you to group channels and structures, and the Expanded Singles plugin allows you to put singles in the entry sidebar. This can help content managers know what they are editing while allowing for more complex models. See below:


Feedme import formatting into Refactor from JSON Having had a bit of a nightmare trying to get content out of Wordpress into Craft via Feedme have written an intermediary script to sanitise the WP XML export into something more palatable (JSON).
However still having issues and it looks like Feedme is breaking the supplied string for HTML content into an array (breaking on commas).
For example:
"content":"

Importance of passing your MOT first time<\/h2>
At Rygor, we understand that …
"

Is being passed into Redactor (via the Feedme debug) as:
(
    [type] => text
    [enabled] => 1
    [collapsed] =>
    [fields] => Array
        (
            [body] => ["

Importance of passing your MOT first time&lt;\/h2&gt;

At Rygor","we understand that it is important to …
        )
)

Oddly the body string has no closing quote.
Any thoughts how best to encode my HTML string or to avoid FeedMe breaking my string into an array?

So thanks to @Dajo on the Craft Discord channel for any other folk out there experiencing similar turns out there was a config set-up for the site wasn't aware of that had set the dataDelimiter parameter to  , which was exploding the HTML content into an array.
Changing this has sorted the problem!


How can I group a list of tags into ranges of first letters (A–E, F–J, etc.)? I'd like to be able to fetch a list of tags from a blog section and then group them into ranges of letters.

I've used this code to fetch and group a list of tags by first letter:

{% set entries = craft.entries.section('news') %}

{% set allTags = craft.tags.relatedTo({
  sourceElement: entries
}).order('name').limit(null) %}

{% set allTagsGrouped = allTags|group('name | first') %}

But what I'd like to do is to have groups for ranges of first letters like A–E, F–J, etc.

The end goal is to create an index of all of the tags in use, but group them under ranged letter headers instead of individual letter headers.

How would I go about doing this?

Thanks!

You can do this with the twigPCRE filter. Once you have your allTags, group them like this:

{% set allTagsGrouped = allTags|group('name|first|upper|preg_replace("/[A-M]/", "A-M", 1)|preg_replace("/[N-Z]/", "N-Z", 1)') %}

This takes the tag name, gets the first character, converts it to uppercase (so 'a' and 'A' are grouped together), and then maps all characters in the range A-M to the string "A-M", and all characters in the range N-Z to the string "N-Z". So allTagsGrouped will have two keys: "A-M", and "N-Z" (assuming all tag names start with letters). Obviously you could use more ranges.

Ordering allTags by name appears to be a case insensitive sort, so allTagsGrouped should still be in the right order.


How do I get rid of whitespace when rendering without touching template indentations? I have the following code.

{% spaceless %}
{% for date in entry.d_date %}
    {% if date.showMonth == 1 %}
    {{ date.date.month }}-
    {% endif %}
    {% if date.showDate == 1 %}
    {{ date.date.day }}-
    {% endif %}
    {{ date.date.year }}
{% endfor %}
{% endspaceless %}

Which outputs 1- 1- 2014

Is there anyway to get rid of the whitespace without touching the indentation? I've used {spaceless} but it's not working.

The spaceless tag only removes whitespace between HTML tags. You have to use the whitespace control modifier on your tags to remove leading and or trailing whitespace.

Additionally I'd use the ternary operator for your conditionals (and put the hyphen inside it):

{% for date in entry.d_date %}
    {{  date.showMonth == 1 ? date.date.month ~ '-' : '' -}}
    {{- date.showDate == 1 ? date.date.day ~ '-' : '' -}}
    {{- date.date.year }}
{% endfor %}


Search Multiple Fields Same Parameter I'm building a search form with filters. This is one of the filters.
I would like to use the parameter "agent" and have it search a text field called "listingAgent" and "coListingAgent" to check for a match either place.  I tried the code below, but can't seem to get it to work.

    {% set getAgent = craft.request.getParam('agent') %}
    {% if getAgent %}
        {% set params = params|merge({ 'listingAgent':getAgent, 'coListingAgent':getAgent }) %}
    {% endif %}

additional info:
I noticed that setting the params twice (like below) is looking for a match in both fields. I am looking for a match in either field. Does anyone know how to go about doing this?

{% set getAgent = craft.request.getParam('agent') %}
    {% if getAgent %}
        {% set params = params|merge({'listingAgent':getAgent}) %}
        {% set params = params|merge({'coListingAgent':getAgent}) %}
    {% endif %}

{% set entries = craft.entries(params).search() %}

You have two different options. You can either use Crafts search so your string would be listingAgent:*searchterm* OR coListingAgent:*searchterm* or listingAgent:searchterm OR coListingAgent:searchterm if it should be an exact search

{% set results = craft.entries()
    .search({
        query: 'listingAgent:*' ~ getAgent ~ '* OR  coListingAgent:*' ~ getAgent  ~ '*',
        subLeft: true,
        subRight: true
    })
    .all()
%}

or you query correctly for the field values but I would prefer to do that in PHP instead of Twig


Limit countries shown in checkout shipping to countries assigned in shipping zones settings? Is there a way to restrict the list of countries available in the checkout shipping address select menu to the countries assigned in the shipping zones?

You would need to either

1) Delete all the countries you don't use within your site in commerce > settings > countries.

2) Simply hide them in twig with something like this:

{% set allowedCountryIds = [1,3,4,6,7] %}
{% for key, country in craft.commerce.countriesList %}
  {% if key in allowedCountryIds %}
    <option value="{{ key }}">{{ option }}</option>
  {% endif %}
{% endfor %}


Redirecting "if entry is not defined" returns 404 instead All the documentation, and answers here say this should work... But it returns the 404 instead of redirecting and I can't figure out why?

Section: Structure = help
Entry URL Format: Top-Level Entries =
help/{slug}
Entry URL Format: Nested Entries = help/{slug}
Entry 'sTemplate = help/_entry

Top line of the "help/_entry.html" template contains

{% if entry is not defined %}
    {% redirect "/help/welcome-to-help" 301 %}
{% endif %}

where "welcome-to-help" is an Entry slug in the Help Section.

If I hit "example.com/help/welcome-to-help/" (or any other valid Slug), everything works fine. But if I hit "example.com/help/asdf/" it returns the 404 instead of redirecting.

Am at a loss at this point??

The reason you're getting a 404 is because Craft isn't even hitting your help/_entry.html template. Because there's no entry matching the URI /help/asdf, the template will never render and the request will go straight to 404.

The solution is to create a dynamic route matching your section's URL format (i.e. help/<slug>), and point it to the same template.

This works, because whenever Craft can't match a URI to an entry or category, it'll then try to render any dynamic route matching the URI's format (for a deeper understanding of how Craft manages routes and requests, I'd recommend looking at the the official docs' page on routing).


Direct access to controller I'm trying to create a plugin with a controller, but I don't want to render any view but run a script from a cronjob. I can't get into the controller with an url and I'm not sure why.

I already tried everything here but it does not state any way to directly call a controller with an url. Is this possible?

The docs explain how to generate links to controller actions. Assuming your action trigger is "actions" (as it is by default) then your URL will look as follows:

http://www.domain.com/actions/pluginName/controllerName/actionName

Following the first example in the docs, the URL will look as follows:

http://www.domain.com/actions/cocktailRecipes/ingredients/deleteIngredient

Be sure to allow anonymous access to your controller action so it can be reached by the cron job.


How to fetch entries in custom plugin Craft 3? I am creating custom search based on entries. I need to fetch entries in custom plugin.

For instance like this:

$entries = Entry::find()
    ->section('sectionName')
    ->orderBy('fieldName')
    ->limit(null)
    ->all();

Have a look here in the documentation (for the code examples you can toggle Twig and PHP view for code examples):
https://docs.craftcms.com/v3/dev/element-queries/entry-queries.html

Note that if you add use craft\elements\Entry; to the top of your plugin file, you can use the Entry::find() directly, you don't need the namespace prefix like in the examples in the documentation (for instance \craft\elements\Entry::find();).


Add home page to a structure? I am using a structure for all of my site's pages (single pages and channel index pages). The structure settings are as follows:

URL for Top-Level Entries:
{slug}

URL for Nested Entries:
{parent.uri}/{slug}

Entry Template:
_pages/_entry.html

Additionally I set up multiple entry types for the structure to relate the pages with individual templates (basically as described in the FAQs). This is my _entry.html:

{% include '_pages/_types/' ~ entry.type %}

This all works great for me [*], but now I'd like to further improve the template setup by adding my site's home page to the structure, too. With a single section I can simply trigger the option "this is the home page" in the section's settings, but how can I tell an entry of my pages structure to be my home page?

[*]: I can generate my site's main navigation from this structure (nice!) and all channels' index pages have entry slugs now, which is incredibly helpful in a multi-lingual Craft setup, if you want to translate your slugs.

This isn't documented, so it's subject to change:

When you set a Single to be the homepage, its slug is automatically set to __home__.

If you want your homepage to be part of a Structure, rather than a separate Single, you can set the slug of a Structure entry to __home__, which makes it act as the site's homepage and gives it that little Home icon in place of its slug in the structure view.


Creating HTML and Text version of entry via different templates So I'm using Craft to create code to be pasted into a blast email program. My standard template is at the following:

newsletter/_entry

However, I'm having trouble getting the text version to display in any way. I tried creating the following folder structure which did not work (404).

newsletter/text/_entry

I tried using routes in the backend to do this, but does not work either. I know I must be missing something simple. Any ideas?

The issue may have to do with the extensions on the files in your template folder. By default, Craft only renders .html and .txt files. If you want Craft to also render .txt files, you could update the defaultTemplateExtensions config variable.  Adding something like this to your general.php would get .txt files rendering:

'defaultTemplateExtensions' => array('html', 'twig', 'txt')

If you want to access a .txt file without enabling it as a defaultTemplateExtension, you would have to access the template URL with the full file extension.

This URL would work out of the box:

/newsletter/entry.txt

This would only render .html or .twig:

/newsletter/entry

These defaults makes it a little harder to use a hidden template file such as newsletter/_entry for non-standard extensions. One work around would be to pass your ID as a parameter in the URL and manually grab it in your template code to determine what to do on the page.

This is an option:

/newsletter/entry.txt?id=123

As an alternative method, the plugin Sprout Email helps manage a lot of components of HTML and Text versions of email templates, and ships with a Copy/Paste Mailer which allows a user to copy and paste the HTML and Text versions of an email directly from a modal in the the Control Panel.


Choose not to save an element on EVENT_BEFORE_SAVE_ELEMENT I want to do some server side checks to see if the entry should be saved or not.

In my plugin I have something like that

Event::on(
    Elements::class,
    Elements::EVENT_BEFORE_SAVE_ELEMENT,
    function(ElementEvent $event) {
        if ($event->element instanceof \craft\elements\Entry) {
            $entry = $event->element;
            if ( $entry->sectionId == 5 ) {
                $entry->enabled = 0;
            }
        }
    });

This works but what I want is for the entry to not be saved rather then be disabled. Something I could achive on the user model with:

$event->isValid = false;

Is there a way to withhold the entry from saving? The only other solution I can think if is to make the tests in the AFTER event and delete it if not passing the condition.

This functionality or behavior is a bit confusing. The Elements Service has its own Events for saving Elements, specifically... but because ultimately Elements are just Yii Models, they have a discrete EVENT_BEFORE_SAVE event that uses a ModelEvent, which has the isValid property.

This is the point in the life-cycle where saving the Element can be prevented. The beforeSave method is called on the Element being saved (usually overridden in the actual Element implementation, but called + returned at the end of the subclass's method, like parent::beforeSave($isNew)), and the resulting boolean ($event->isValid) determines whether or not the saving continues.

So, in short: You can listen directly to the Entry's EVENT_BEFORE_SAVE event to prevent it from saving:

use craft\elements\Entry;
use craft\events\ModelEvent;

use yii\base\Event;

Event::on(
    Entry::class,
    Entry::EVENT_BEFORE_SAVE,
    function(ModelEvent $event) {
            $entry = $event->sender;
            if ($entry->sectionId == 5) {
                $event->isValid = false;
            }
        }
    });


Is it possible to provide an option to pick up an order from the physical store in Craft Commerce? We're setting up a Craft Commerce site and the merchant wants to give the option to the customer to pickup the order from their physical store instead of shipping it to a specified address.

If the customer selects that option then they shouldn't have to provide a shipping address or billing address.

Is this possible in Craft Commerce? If not is there a plugin or a suggested way to do this?

You could make 'pickup from store' a shipping method.

Then in your checkout form, if a visitor chooses that shipping method, make sure you don't show the shipping address form, and add a hidden input to set order.shippingSameAsBilling to true. Commerce will then populate the shipping address with the billing address details. Code is untested, but it should be something like:

{% if order.shippingMethodHandle == 'pickupInStore' %}
    {{ hiddenInput('shippingSameAsBilling', 1) }}
{% else %}
    {# Show the shipping address form #}
{% endif %}


How to append 'Continue reading…' link within the last paragraph of the Excerpt The code from the RichText documentation appends the 'Continue reading…' link in a new block and on a new line:

{% for entry in craft.entries.section('blog') %}
        {{ entry.title }}
        {{ entry.richTextFieldHandle.getPage(1) }}
        {% if entry.richTextFieldHandle.getTotalPages() > 1 %}
            Continue reading…
        {% endif %}
{% endfor %}

Any ideas how to append the 'Continue reading…' link inside the final
 tag generated by Redactor at the end of the final paragraph, i.e. as a continuation of the Excerpt text, and on the same line?

I'm a total novice with Craft/Twig and I was hoping for some sort of simple and neat append function in Twig!

However, I've got close to a working solution using CSS, by overriding the Extract's
 tags' usual behavior with display: inline, and enclosing the 'Continue reading…' in a  tag.

Edit:
The following (including some additional logic for a 'Summary' field) seems to work well…

    {% if entry.summary %}
        {{ entry.summary }}
    {% else %}
        {{ entry.body.page(1) }}
    {% endif %}

    {% if entry.body.getTotalPages() > 1 or entry.summary %}
        Continue reading…
    {% endif %}

…and…

.excerpts p:last-of-type { display: inline; }


TypeError: Argument 1 passed to craft\services\Fields::getGroupById() must be of the type integer, null given When I go to control panel > Settings > Fields, I get a page that throws TypeError: Argument 1 passed to craft\services\Fields::getGroupById() must be of the type integer, null given

This is identical to the issue described here: https://github.com/craftcms/cms/issues/3561

Any ideas as to what might be causing this issue?

This could be due to an issue with your project-config being out of sync somehow. You could try the new ./craft project-config/rebuild console command that's just been added to Craft and see if that gets you back on track.


Getting an zero index and count for an entry list I currently have an entries field which outputs a list of related entries on page. I'm trying to find a way to display two values for these entries.
The first would be a zero index. I'm trying to assign each entry to link up with a slider. Long story short, each of the 4 assigned entries would be listed as such
0 Entry One
1 Entry Two
2 Entry Three
3 Entry Four
The second would be to display a simple count of sorts before each entry. This would be displayed as

Entry One
Entry Two
Entry Three
Entry Four

I'm sure this must be possible but I'm unsure about the zero based index format. If anyone could help, I'd really appreciate it.
My code is currently set up as such
{% for project in sliderWork %}

This is where the simple numbering would be displayed{{project.title}

 {% endfor %}

There's an easier way... Twig has a built-in variable in for-loops called loop, which has loop.index and loop.index0 properties which output exactly what you're looking for:
https://twig.symfony.com/doc/3.x/tags/for.html#the-loop-variable
{% for project in sliderWork %}

{{ loop.index }}{{project.title}

 {% endfor %}


Status of links to entries in body copy Is it possible to check the status of entries linked to in content from the Rich Text field? For example, the user might create a link in a paragraph to an entry that is in draft status. I would like to only display an actual link in the front end if this entry is then published.

Is this currently possible?

I don't think it's possible out-of-the-box, but you might be able to get something like that working with a shortcode approach. You could make a custom shortcode with a link parameter, and in the function for that shortcode, look up that link parameter's corresponding entry and find out its status. Then the shortcode could output a link if the entry was published, and just the original text otherwise. Here's a plugin that lets you add shortcodes: https://github.com/samhernandez/craftcms-shortcodes. If you wanted to get really fancy, you could even make a new redactor (the WYSIWYG editor Craft uses) button that inserts the shortcode instead of the normal anchor HTML.

This approach would probably take a bit of work, though. I'd be interested to see if anyone else has a better solution.


DB not connecting on Namespro linux server I am not a developer nor do I have a technical background, striving to solve a lingering issue with our it/hosting company. This means I unfortunately can't supply code for reference; simply seeking community support if anyone has encountered a similar issue.

We launched a new site that is configuring on the Namespro server but not connecting to the Craft Pro CMS DB. It seems to be a port or permissions error but our hosting company hasn't been able to resolve the issue for a couple of months now. The site has been live and encountered zero issues launching on our developer's digital ocean droplet.

Any thoughts??? Don't want to waste anyone's time but hopeful someone has encountered this issue with Namespro before.

Thank you in advance.

So if it can't connect to the database, then one of the following is not right in the config/db.php file:

server - usually it's localhost but perhaps they provide an external MySQL server?
database - the name of the database
user - the user authorized to access the database
password - the password used to connect to the database

FWIW, this type of issue (and also this lack of support) would make me want to keep the site on the Digital Ocean server, and not move it to Namespro.


Assets Uploader Just Spins and Does Nothing Else I am using Craft locally, at localhost:8888 with free MAMP. I am unable to upload images using the Assets uploader. The images in Assets were dropped in manually in their directory.

Permissions for Craft are 774 (/app, /config, /storage)
File system path: /Users/myuser/repos/craft-learn/craftapp/public/assets/i/artists/
URL: http://localhost:8888/assets/i/artists/
version 2.6.2958

What should I check?

The solution was to use MAMP Pro instead of MAMP free. i cannot answer with complete authority why, but MAMP Pro has abilities that MAMP free doesn't.


Update matrix onBeforeSaveUser I have a matrix block attached to my user, the block handler is children and has one field dob date filed something like this:

fields[children][420][fields][childrenDateOfBirth][date]

The problem is that I would like to update the date right before the user is saved. So I have an envent listener in my plugin

craft()->on('users.onBeforeSaveUser', function(Event $event) {

    $matrixData = array();
    $matrix     = $event->params['user']->getContent()->children;

      foreach ($matrix as $block){

        $blockData = array(
                            'type' => $block->getType()->handle,
                            'enabled' => $block->enabled
                           );

         $blockData['fields'] = array(
                           'childrenDateOfBirth' => array('date'=>'2000-10-10')
                        );

          $matrixData[$block->id] = $blockData;

  }

 $event->params['user']->getContent()->children = $matrixData;

}
  ...............

And I get this error : Fatal error</b>:  Call to a member function getType() on array Is there a way to do this?

I did the trick so here is the code if it can help someone else

craft()->on('users.onBeforeSaveUser', function(Event $event) {

   $matrixData = array();
   $matrix     = $event->params['user']->getContent()->children;

   foreach ($matrix as $block){

      $block->getContent()->childrenDateOfBirth = '2000-10-10';
      $matrixData[$block->id] = $block;

   }

   $event->params['user']->getContent()->children = $matrixData;

}


How to set a channel as the homepage When users enter http://www.example.com I would like to redirect them to the http://www.example.com/news template.

How could I achieve this?

You can do this with Craft’s {% redirect %} tag. Just put this in your /index.html template:

{% redirect 'news' %}

If you want to redirect to the latest entry in your News section, as you mentioned in the comments above, just fetch the entry first:

{% set latestNews = craft.entries.section('news').order('postDate desc').first() %}
{% redirect latestNews.url %}


Display a list of categories excluding the current one In a template for a category page, I'm trying to output a list of links through to all categories within a category group but exclude the category that the user is on.

Does that make sense? What I'm trying doesn't work, but I'm new to Craft and Twig!

With a little help I got there:

{% set categories = craft.categories.group('developmentType').id('not ' ~ category.id) %}

{% for category in categories %}

{{ category }} Opportunities

{% endfor %}


What is the current landscape regarding e-commerce plugins for Craft? I'm developing an online store. The store needs to be seamlessly integrated with all of the editorial content on both the front and back-end. Craft is obviously a perfect candidate for the editorial content, but at this moment the options for e-commerce plugins are limited.

What e-commerce options are currently available for Craft?

E-commerce Master List

This is a compilation of all publicly available solutions. Other answers here describe each plugin in greater detail... This is simply a complete list of every known option.

Listed in alphabetical order (not by popularity or preference)

1st party plugin

Craft Commerce (see more info below)*

3rd party plugins (built exclusively for Craft)

Charge
Sprout Commerce (in private beta)

3rd party services with official Craft plugins

FoxyCart (integration notes)
Snipcart

3rd party services with unofficial Craft plugins

Moltin
Shopify
Snipcart

DIY custom solutions (via an API)

Moltin
PayPal
Stripe

  * Craft Commerce was formerly known as "Market Commerce" (aka "Cellar Commerce"). The name was changed after the plugin (and its developer, Luke Holder) were acquired by Pixel & Tonic.


Craft Commerce Negative Stock Is it possible to set up and manage stock in Craft Commerce even if a product stock goes into the negative? The current commerce project I'm working on is open to the public so anyone can add items to a cart but only registered and approved members will have the ability to submit carts as orders. What I would like to be able to do is keep out of stock products available in the store so that they can be added to carts and orders. The orders are manually processed so an out of stock item just means that further production is needed. Tracking negative stock would also help guide production because it would give insight into how many of a given product is needed at any given moment in time.

Commerce does not have a back-ordering (negative stock) counter at the moment. To allow ordering of more items that you have on hand, you would need to either:

1) enable the 'unlimited stock' option on the variant which would not track the stock and allow orders of an unlimited number of variants.

2) Choose an arbitrarily high stock count to allow ordering above what you have on hand. For example, if you have 32 items on hand, make the stock value 2032, allowing for the stock to drop below 2000 would let you know the number of back-orders.

Either of these might not be ideal.

A third option could be using option 1 with a plugin that listens to the onOrderVariant event and updating your own stock counter within your own database tables.

We hope to have features like back-ordering in future versions of Craft Commerce, and you can vote for such features here: https://craftcms.uservoice.com/forums/295386-commerce/suggestions/7817436-pos-and-inventory-management

Hope that helps.


Craft 3 Redis configuration error when following the information for enabling Redis in Craft 3, my site just refreshes to a blank screen with a "Internal server error" generic message. Reviewing the server logs reveals:

ReflectionException: Class yii\redis\Cache does not exist in /storage/path/www/staging_html/vendor/yiisoft/yii2/di/Container.php:428

Talking with my hosting provider (Arcustech), informs me that Redis is indeed running and php-redis is installed. The details I entered into config\app.php were

return [
'components' => [
    'cache' => [
        'class' => yii\redis\Cache::class,
        'defaultDuration' => 86400,
        'redis' => [
            'hostname' => 'localhost',
            'port' => 6379,
            'database' => 0,
        ],
    ],
  ],
];

Any light that could be shone onto my predicament would be amazing.
thanks!

Turns out it was a beginners mistake, thank you to Robin in the comments above for setting me on the right track. All that was required to install was the Yii2 library, once this was done all worked as expected.


Plugin init order issue I have a plugin, which is using another plugin. The issue is that, I am calling some other plugin information in the init function, and I do receive sometimes this error:

2018-11-07 11:44:57 [-][-][k91hcavi6st542afnhsvhdp61u][error][yii\base\ErrorException:8] yii\base\ErrorException: Trying to get property 'mobileDetectService' of non-object in /home/forge/plugins/releases/20181106223004/src/services/CacheService.php:193
Stack trace:
#0 /home/forge/new.trendsideas.com/releases/20181106223038/vendor/craftcms/cms/src/web/ErrorHandler.php(76): yii\base\ErrorHandler->handleError(8, 'Trying to get p...', '/home/forge/plu...', 193)
#1 /home/forge/plugins/releases/20181106223004/src/services/CacheService.php(193): craft\web\ErrorHandler->handleError(8, 'Trying to get p...', '/home/forge/plu...', 193, Array)
#2 /home/forge/plugins/releases/20181106223004/src/services/CacheService.php(126): trendspublishing\craftplugin\services\CacheService->_generateUri('/hubs/bathroom')
#3 /home/forge/plugins/releases/20181106223004/src/Plugin.php(90): trendspublishing\craftplugin\services\CacheService->uriToFilePath('/hubs/bathroom')
#4 /home/forge/new.trendsideas.com/releases/20181106223038/vendor/yiisoft/yii2/base/BaseObject.php(109): trendspublishing\craftplugin\Plugin->init()
#5 /home/forge/new.trendsideas.com/releases/20181106223038/vendor/yiisoft/yii2/base/Module.php(158): yii\base\BaseObject->__construct(Array)
#6 /home/forge/new.trendsideas.com/releases/20181106223038/vendor/craftcms/cms/src/base/Plugin.php(113): yii\base\Module->__construct('craftplugin', Object(craft\web\Application), Array)
#7 [internal function]: craft\base\Plugin->__construct('craftplugin', Object(craft\web\Application), Array)

This is the error in question:

$mobile_detect = MobileDetect::$plugin->mobileDetectService;

I am not able to replicate it, sometimes a simple database reload is helping sometimes not. So that's why I am only guessing, because I do invoke this call from the plugin init function, that's why the other plugin is not ready yet...
Any thought, best practice on this one?

What I do is I listen for the Plugins::EVENT_AFTER_LOAD_PLUGINS event, and then do my code that relies on other plugins being loaded:

    // Handler: Plugins::EVENT_AFTER_LOAD_PLUGINS
    Event::on(
        Plugins::class,
        Plugins::EVENT_AFTER_LOAD_PLUGINS,
        function () {
            // Do stuff only after all other plugins have loaded
        }
    );


Macro _self no longer possible in Craft 3 This used to work in Craft 2:

{% macro richText(macroParam) %}
  {{ macroParam |striptags('

')|raw }}
{% endmacro %}

{# include the richtext macro on multiple occasion into another macro #}
{% macro memberMolecule( entryParam ) %}
  {{ _self.richText(entryParam.body) }}
{% endmacro %}

Now in Craft 3 this causes the error:

  Impossible to invoke a method ("richText") on a string variable
  ("path/to/file/with/the/macro").

How do I have to rewrite this for Craft 3?

You have to import the macro

{# include the richtext macro on multiple occasion into another macro #}
{% macro memberMolecule( entryParam ) %}
    {% import _self as macro %}
    {{ macro.richText(entryParam.body) }}
{% endmacro %}


How to do multi-environment app config (config/app.php)? I'm trying to setup different mail settings per environment. Here is my mail component that I'm defining in config/app.php:

'components' => [
    'mailer' => function() {
        // Get the stored email settings
        $settings = Craft::$app->systemSettings->getEmailSettings();

        // Override the transport adapter class
        $settings->transportType = craft\mail\transportadapters\Smtp::class;

        // Override the transport adapter settings
        $settings->transportSettings = [
            'host'              => 'localhost',
            'port'              => '1025',
            'useAuthentication' => false,
            'encryptionMethod'  => null,
            'timeout'           => '10',
        ];

        return craft\helpers\MailerHelper::createMailer($settings);
    },
],

How do I ensure this only applies to development environments? Multi-environment doesn't seem to work like it does for config/general.php and there's no multi-environment example in the relevant documentation. Brandon's Github comment here suggests that this is possible. Thanks.

To get this to work, have a look at the format of the general.php config file.

The key is to include the '*'=>[] at the start.


How do you programmatically update an asset index? I am trying to update an asset index from a plugin but can't seem to get it to work.  Looking at the docs this seems to me like it should work but it does not. I am on the right path here?

$ses = craft()->assetIndexing->getIndexingSessionId();
craft()->assetIndexing->processIndexForSource($ses, null, 7);

7 is the id of the asset folder I want to re-index.

This is what I ended up with which works great for me.

$ses = craft()->assetIndexing->getIndexingSessionId();
$src = craft()->assetIndexing->getIndexListForSource($ses, 7);

for ($i = 1; $i <= $src['total']; $i++) {
    craft()->assetIndexing->processIndexForSource($ses, $i, 7);
}


How to watch for changes on an Element Select Relations field? I have a situation where I'd like to watch for a change in an Element Select field and determine if any Elements have been selected or not.

Element Select fields have two input elements. One, is always present and never changes, and the other gets added and removed from the page depending on if any items have been selected. Here's a simplified version of the HTML:

    {# This input field is always present and never changes #}
    <input type="hidden" name="fields[assets]" value="">

        {# When you add or remove elements, this .element div and input
           get added and removed from the DOM #}

            <input type="hidden" name="fields[assets][]" value="430">

I've tried various listeners but so far they all get destroyed when the second input gets removed from the page, or never get added to the right input field if it is not on the page.

How do I listen for an element that may or may not exist on the page?

I think there's a few ways to skin this cat, but the most robust way would probably be to listen to the the selectElements event for the BaseElementSelectInput instances bound to each field's container element (the instance will be bound to the
 node, except for Categories fields where it'll be bound to the
 node, for some reason) and can be accessed by calling $('.elementselect').data('elementSelect').

You can use the .on() method to listen to the actual events, here's one way to do it:

$('.elementselect, .categoriesfield').each(function () {
    $(this).data('elementSelect').on('selectElements', onSelectElements);
});

function onSelectElements(e) {
    var $field = $(e.target);
    var selectedElements = e.elements;
}

HUGE CAVEAT

Depending on when your plugin's JavaScript runs, the elementSelect data attribute for the element select input DOM nodes may return null.

This is because Craft actually initialises the BaseElementSelectInput instances in some inline JavaScript injected into the source, and this JavaScript is, unfortunately, written to the page after your plugin's JavaScript resources are queued up (after any inline JavaScript your plugin adds, too).

In other words, if you need to hook into the BaseElementSelctInput instances on pageload, you'll need to poll for the data('elementSelect) attribute (ew, I know):

$('.elementselect, '.categoriesfield').each(function () {
    var now = new Date().getTime();
    var getElementSelect = (function () {
        var elementSelect = $(this).data('elementSelect');
        if (elementSelect) {
            // Woo, I can haz elementSelect
            elementSelect.on('selectElements', onSelectElements);
        } else if (new Date().getTime() - now < 5000) {
            // Poll for 5 secs (then give up)
            Garnish.requestAnimationFrame(getElementSelect);
        }
    }).bind(this)
    getElementSelect();
});

Hack alert: If you don't want to poll for the elementSelect instances, there is another way. Credit due, I learnt this trick from looking at Benjamin Fleming's source code:

var elementSelectInit = Craft.BaseElementSelectInput.prototype.init;
Craft.BaseElementSelectInput.prototype.init = function () {
    elementSelectInit.apply(this, arguments);
    this.on('selectElements', onSelectElements);
    this.on('removeElements', onRemoveElements);
}

function onSelectElements (e) {
    console.log(e);
}

What the above does, is to basically hijack the BaseElementSelectInput class' init method, in order to add some custom event listeners when each BaseElementSelectInput instance is initialised. Works like a charm and – even if it's a huge hack – is quite a bit cleaner than the polling method, in my opinion.


How to calculate the number of hours left? This question relates to my previous question on stackexchange about the difference in days between 2 dates. This ended up being the working solution:

{{ date( inschrijven.datumEvenement|date ).diff( now ).days }}

So the same question applies but this time how can I calculate the number of hours left between ( now ) and a future date {{ inschrijven.datumEvenement }}?

You can use the date filter to format the DateInterval to your needs, I guess you
are looking for the total hours until the event, so you have to do some math.

Because there's still some strange things going on with Craft DateTime objects, do what I
recommended in the question you mentioned, convert it to a string in a common format that includes time and timezone information
and then use Twig's date function to convert it to a Twig DateTime object.

To get a "DateTime string" in ISO8601 or UNIX format use the iso8601 or getTimestamp method or the date filter with the corresponding parameter.

{% set dateTimeEvent = date(inschrijven.datumEvenement|date('c')) %}
{% set dateTimeNow = date(now.iso8601) %}

{% set dateInterval = dateTimeNow.diff(dateTimeEvent) %}

{% set days = dateInterval|date('%a') %}
{% set hours = dateInterval|date('%h') %}
{% set totalHours = days * 24 + hours %}

{% set upcoming = dateInterval|date('%R') == '+' ? true %}

{% if upcoming %}
    Event start in {{ totalHours }} hours.
{% endif %}


Limit rows using Table This is my code I am using, but want to limit it to just one and possibly randomize it also.  Is it possible with Table, I looked at the example on Craft's site but did not see that option.

{% for row in block.example %}
    {{ row.examplerow }}
{% endfor %}

I have never tried it on a table field, but you could try:

{% for row in block.example.limit('1').order('RAND()') %}
    {{ row.examplerow }}
{% endfor %}

OK, above will not work. I went back on my theory and actually tried it on a table field I had laying around. This worked for me:

{% set rows = shuffle(block.example) %}

{% for row in rows | slice(0, 1) %}
   {{ row.examplerow }}
{% endfor %}


Select entry from any locale with Entries Field I'd like to be able to create an Entries field that can select from entries from all locales.

Currently it looks like there's the option to choose a single locale or specify the same locale as the source entry.

Is there any way around this?

Is there any way around this?

Not that I'm aware of.  You can currently only select entries from the locale you're viewing (same as source), or an explicit one specified in the settings.

You can't browse across multiple locales and select entries.


Creating a new entry in a specific section Let's say I created a section called, "Answers". In a custom plugin, I want to create an entry that should show up under the "Answers" tab in the CP entries page.

  $entry = new EntryModel();
  // How do I say, save this entry into the "Answers" channel?
  $saveIt = craft()->entries->saveEntry($entry);

I have literally been reading Craft docs for the past hour, and have gotten nowhere.

The EntryModel you have has a sectionId property you can set.  i.e.

 $entry = new EntryModel();
 $entry->sectionId = 1;
 $saveIt = craft()->entries->saveEntry($entry);

Note that there are other properties you'll need to set for it to pass validation.  So, if $saveIt is false after calling saveEntry($entry), then call $entry->getErrors() to get an array of validation errors to fix.


Display entries which share tags (2 or more) I need to be able to display entries (within one section) that share keywords. I don't care what the keywords are, just want to display the entries based on 2 or more shared keywords.

Anyone know how to go about this?

After chatting on Discord a bit more, here's a solution in case it helps somebody:

{# Base query #}
{% set baseQuery = craft
    .entries
    .section('stories')
    .orderBy('RAND()')
    .with(['storyKeywords']) %}

{# Fetch one story at random #}
{% set baseStory = baseQuery.one() %}

Base random story: {{ baseStory.title }}

{# Fetch a second story related to `baseStory` at random #}
{% set relatedStory = clone(baseQuery)
    .relatedTo({
        targetElement: baseStory.storyKeywords,
        field: 'storyKeywords'
    })
    .id('not ' ~ baseStory.id)
    .one() %}

Related Story: {{ relatedStory.title }}

{% set baseKeywords = baseStory.storyKeywords %}
{% set relatedKeywords = relatedStory.storyKeywords %}

All tags in common: {{ baseKeywords | filter((tag) => tag.title in relatedKeywords) | join(', ') }}

Number of tags in common: {{ baseKeywords | filter((tag) => tag.title in relatedKeywords) | length }}


Replicate entries structure in admin I am looking to see if there is an easy way or already a plugin available that would replicate the entries structure layout.  I plan on creating a plugin that would allow an admin to mass update a select set of fields.

I notice it does an ajax call to index.php?p=admin/actions/elementIndex/getElements to get the first 50 entries and then subsequent calls to get more as you get near the end of the results.

I see one of 3 potential ways to do it and wondering which way would be ideal.

Try and call this same ajax call as it returns the html and do some manipulation to the html to add additional functionality I need.
Find methods that are being called from craft core and copy them into my plugin and modify as needed then do similar ajax call to my plugin.
Just write my own custom code to grab what I need.

I am leaning on number 2 as the ideal scenario but have not looking to see if this  is unreasonable as it may be tied to a lot of different areas of the core code.

Looking to get some opinions as I am guessing others have already gone down this road.

Thanks

UPDATE with more info

I want to mimic the entries admin structure layout page for a specific section.  Once I mimic it I will add a little different functionality to it.  So currently you can select multiple entries and mass update the status or delete those entries and that is it.  I plan to add some additional options to change some custom fields that are associated with that section.  So if I am in the admin and I go to /admin/entries/meetings and meetings is a section with a type of structure I would see the tree layout of all the meetings in my case it is 5 levels deep.  There are 3 custom fields that change monthly and when they change about 30% of the meetings setup need to be changed.  The idea is they could select all of them that need these 3 fields updated and can do a mass update.  Currently they have to go into each meeting and make the change which is very time consuming.  Hope that makes more sense.

Seems like you want to create a custom Element or you would like to extend the existing Entry Element.
It is all stated in the docs You can see how to create custom actions as well here

Here is stated how to create a custom index page

{% extends '_layouts/elementindex' %}
{% set title = 'Products' %}
{% set elementType = 'ns\\prefix\\elements\\Product' %}

You can then extend the BaseElementIndex in order to change Javascript functionality

Craft.elementIndex = Craft.createElementIndex('{{ elementType|e("js") }}', $('#main'), {
    context:        '{{ context }}',
    storageKey:     'elementindex.{{ elementType }}',
    criteria:       Craft.defaultIndexCriteria
});

You can as well make use of certain events like

Event::on(Elements::class, Elements::EVENT_AFTER_SAVE_ELEMENT, function($event){
    // resave all other elements
});


Freeform: Remove default styling injection I seem to be stuck with a simple -- but quite annoying -- question. I'm setting my own styling for a form and keep running into issues where I'm fighting with Freeform's default styling. I'm not including this anywhere in the template, at least that I know of, yet Freeform still seems to be injecting it's own styling onto the page. Am I missing something here where i can turn that off? Any help here would be greatly appreciated. I have attached an image showing what gets injected that I'd like removed.

This is probably injected as part of the form template that's used to render the form. In the Freeform section of the CP, if you edit a form and choose the 'cog' icon in the right-hand pane to access the form settings, you'll see a dropdown labelled 'Formatting Template', which lets you choose a template to render out the form markup, with a number of popular frameworks listed (Bootstrap, Foundation etc.).

In order to override this markup (and remove the CSS include), you can create your own custom templates using one of these templates as a guide. The Freeform docs cover how to do this here.

The default templates are located here:

vendor/solspace/craft3-freeform/src/templates/_defaultFormTemplates.

Make a copy of the file you want to use as a base, and place it in your own templates directory; I tend to use templates/_forms for form templates. Give your new form template a meaningful name - Freeform does some nice parsing of custom template names to display them in the Formatting Template dropdown, so it's worth naming it well.

Open your new custom template and look for a line right at the top that looks like this:

{% css formCss %}

Remove that line from your template and save it. Finally, make sure in the Admin CP > Freeform Lite/Pro > Settings > Formatting Templates, you've included the path to your custom forms template directory relative to your main templates directory. For my example directory above, I'd enter _forms as the directory path.

Now, edit your form and when you open the Formatting Template dropdown in the form settings, you should see your new custom form template. Select it, save your form and refresh the front-end form page. Hopefully, the Freeform CSS that was being injected is now removed from the markup.

Hope that helps!


Generating a precache manifest for service workers I'm thinking of using Google's Workbox to pre-cache a bunch of files, and I've been scratching my head as to how to generate unique hashes for files when they change. Something like this:

 workbox.precaching.precacheAndRoute([
    {
      url: '/',
      revision: '383676',
    },
    {
      url: '/about',
      revision: 213142,
    },
    {
      town: '/location',
      revision: 742125,
    },
  ]);

Ideally the revision number would change when the entry or related entries included in the page are changed/saved. I got this working but I'm not sure how to get related content to clear the cache:

// service-worker.js

{%- cache using key 'abc' -%}
    {
      url: {{ craft.entries.id(64).one.uri }},
      revision: {{ random(100000, 999999) }},
    },
{%- endcache -%}

This seems to work when said entry is saved, but I'm not sure how to deal with a related entry being saved. I'm also not sure this is the most performant way to go.

So you shouldn't try to come up with a hashed file naming scheme for busting the cache; instead use one of Workbox's Strategies -- I think a great pattern for what you're trying to do is Stale While Revalidate

TL;DR Workbox has strategies for doing what you're trying to do.


Receiving PHP Warning on move to Production I am moving a client site from my staging VPS to my clients preferred hosting. All of Crafts requirements check out just fine:

PHP Version 7.0.29
MySQL Version   10.2.12-MariaDB-log

However, when going to either admin or main homepage I get the following warning:

Wrong parameter count for class_alias()

The file it is pointing to is /craft/app/vendor/twig/twig/lib/Twig/LoaderInterface.php(57)

What am I possibly missing?

My VPS has the following settings:
PHP Version 5.6.36
MySQL Version   5.6.39

So Brad tracked down the issue being Twig needs at least PHP 5.2.7 to run. As of 1.34, the minimum requirement was bumped to PHP 5.3.3. Craft bumped up Twig to 1.35 in version 2.6.3001

https://twig.symfony.com/doc/1.x/intro.html

My clients server was actually running 5.3.29. The server default was 7, bumping it up to php 5.4 fixed the site.


How can I use two different templates for a single entry? I'm building a coursework site and have a Course section, who's entries will be courses containing work that the client will assign to users. I built in both the work itself (different "lessons" sections), and fields for the info page that will be displayed to all users of the site.

How can I use two different templates with the same entry? I need to be able to link to the course.url for the info page, as well as the course.url for the work page within the different templates that use info from the courses.

Currently I have all of the course entries urls going to the info page template. Do I need to create a separate section for course work?

You do not need to create a separate section for course work.

What you want to have is two different urls, that both display the same entry, but in two different ways, with two different templates. So for example /course-slug will be the url for info page for the entry with slug course-slug, and /course-slug/lesson will be the url for the work page for the entry.

If you think of this as two different routes, you have set one of those routes up in the course section. You can set up a second one as a dynamic route. You want to route [slug]/lesson (uri) to _work (template) (or whatever your template is). Then in the _work.twig template, you will need to fetch the entry from the slug:

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

Check if no entry is set, and if not {% exit 404 %}. And then proceed as normal.


Pull Matrix Block from Multiple Entries without Showing Duplicates I have a set of entries I am pulling ID. (group)
Each of those entries has a Matrix Block. (people)
That Matrix Block Type (person) has an entries field.
In some cases the same entry (person) is assigned to multiple entries in the group via the Matrix Block.
I want to show the matrix blocks from my group only once without duplicates.
Pseudo Code
{% set group = craft.entries.id([100,200,300]) %}

{% for entry in group %}

    {% for person in entry.people %}

        {% if person not already displayed %}

            {% set person_entry = person.entry.one() %}

            {{ person_entry.title }}

        {% endif %}

    {% endfor %}

{% endfor %}

Assuming you can have multiple person blocks per entry (group), you could:
Fetch your entries and eager-load the blocks and their entry to save some overheads
{% set group = craft
    .entries()
    .id([100,200,300])
    .with(['people.person:entry'])
    .all() %}

Then build an array with each person.entry
{# The array to store your person.entry #}
{% set people = [] %}

{# Iterate over your group #}
{% for entry in group %}

    {# Iterate over each blocks and push the person.entry to your people array #}

    {% for person in entry.people %}
        {% set people = people | merge ([person.entry[0]]) %}
    {% endfor %}
{% endfor %}

Then use unique (docs) to dedupe your people as you display:
{% for person in people | unique %}
    {{ person.title }} <br>
{% endfor %}


RelatedTo in plugin based search $criteria = craft()->elements->getCriteria(ElementType::Entry);
$criteria->section = 'stories';
$criteria->search = $this->query;
$criteria->order = $this->orderSql();
$criteria->limit = 1000;
$criteria->visibility = $this->checkVisibility();
$criteria->thisIsAGuide = ':empty:';

I need help adding a relatedTo criteria to help pull related entries.

The current search scope is searching on entries ("Story").
The entry("Story") contains an entry ("channel").
That channel has a category("brandCategories")

I need to return stories that contain entries that have a matching brandCategory.
That brandCategory is getting pulled from a GET var of "&brands%5B%5D={{categoryId}}

You can create 2 queries, first you pull all the channel entries, that are related to your bandCategory

$criteria = craft()->elements->getCriteria(ElementType::Entry);
$criteria->relatedTo = $bandCategory;
$criteria->section = 'channel';
$relatedChannelEntries = $criteria->ids();

// then continue your code
$criteria = craft()->elements->getCriteria(ElementType::Entry);
$criteria->section = 'stories';
$criteria->search = $this->query;
$criteria->order = $this->orderSql();
$criteria->limit = 1000;
$criteria->visibility = $this->checkVisibility();
$criteria->thisIsAGuide = ':empty:';
// and search only for entries that are related to channel entries that are related to your band
$criteria->relatedTo = $relatedChannelEntries;


activateAccountSuccessPath doesn't work as expected I have a bi-lingual site with public registration, the site has a front-end registration form which when completed sends the user to an 'activation pending' page correctly, then sends an email off that the user must click to activate the account.

Clicking that link takes the user to the normal Craft login page, and I am expecting it to instead take the user to the page I've got set up as a welcome message and FAQ.

My code is:

return array(
'*' => array(
    'omitScriptNameInUrls'       => true,          // Whether Craft should omit the script filename currently being used to access Craft (e.g. index.php) when generating URLs
    'cpTrigger'                  => 'secretadmin', // The URI segment Craft should look for when determining if the current request should route to the CP rather than the front-end website
    'maxUploadFileSize'          => '16777216',    // 16Mb
    'phpMaxMemoryLimit'          => '128M',        // The maximum amount of memory Craft will try to reserve during memory intensive operations such as zipping, unzipping and updating
    'enableCsrfProtection'       => true,          // Protect against Cross Site Request Forgery; http://buildwithcraft.com/help/csrf-protection
    'searchIgnoreWords'          => array('a',
                                          'the',
                                          'and',
                                          'as',
                                          'if'),   // Words that should be ignored when indexing search keywords and preparing search terms to be matched against the keyword index
    'purgePendingUsersDuration'  => 'P1W',         // If a user hasn't confirmed their registration in under a week, delete it.
    'activateAccountFailurePath' => array('en_gb' => '/accounts/activation-failed',
                                          'cy_gb' => '/accounts/activation-failed'),   // Where to send failed account activations (only from front-end registration forms)
    'activateAccountSuccessPath' => array('en_gb' => '/accounts/activation-succeeded',
                                          'cy_gb' => '/accounts/activation-succeeded'), // Where to send successful account activations (only from front-end registration forms)
),
...
);

The docs do say the activateAccountSuccessPath only applies to front-end registration, but that's what I'm doing?

What have I misunderstood or didn't set correctly?

If the user account that is registering has access to the control panel, either directly or via a user group, then they'll get redirected to the control panel login page after clicking the activation link in the email.

If they don't have access to the control panel, then they'll get redirected to the 'activateAccountSuccessPath' config setting.


Creating "unlisted" entry I'm trying to figure out the best way to create an entry that is live but "unlisted". I know wordpress has the option, and the same thing you can do when uploading videos to youtube.

What's the best way to have a post be live but not included into the regular posts returned when listing items?

If you create a Lightswitch field and add it to the section's field layout (let's call it unlisted), then you can query for live entries that have unlisted not checked with:

{% set entries = craft.entries.section('sectionHandle').unlisted('not 1').find() %}

{% for entry in entries %}
    ...
{% endfor %}


Craft with ElasticSearch as a backend Hope this kind of question (a pointer request, actually) is alright with StackExchange guidelines. We're looking to store all our data in ES. Is this possible at the moment? Any plugin to do so? We're happy to explore Enterprise pricing if needed.

Thank you!

Although not related specifically to Craft, we built https://www.jelliscraig.com.au website using CraftCMS with all property listing data is stored in ElasticSearch. We use a simple plugin for the ElasticSearch PHP client but the integration is front-end only, for the most part.
We have been testing pushing Craft generated content into ElasticSearch via the element API and in theory its a workable solution however for our specific situation we see no huge benefit in storing all general content externally.


Automated deployment with content migrations and structural changes In the knowledge base article there are 4 steps described to automate the deployment process

Pull updates for git
Run composer install
Run migrate/all
Run project-config/apply

The problem with this is that when you have pushed a structural change (eg. adding a new field) and a content migration (adding new entry using that new field) at the same time, it fails because you cannot migrate into a non existing field of course
You cannot flip step 3 and 4 as in that scenario changes made to Craft CMS are not yet available in the database.

Something I wish I knew a long time ago, as it has bitten us quite a few times already in the past. Nothing as bad as having an automated deployment process that fails once in a while.
I'm sharing it here, but I believe it would be good to add it to the knowledge base too (https://craftcms.com/knowledge-base/deployment-best-practices)
The solution is to split up the structural migration and the content migration.
Pull the update for git and then run
craft update/composer-install --interactive=0
craft migrate/all --no-content --interactive=0
craft project-config/apply --force
craft migrate --track=content --interactive=0

Pretty straightforward and simple, but I was not aware of the --no-content flag until recently as it not in the command help.  I hope it will help somebody in the future!
(The --interactive=0 and --force flags are there because it is an unattended deploy)


Is there a way to ask for an email address before you download a file? For Wordpress there's a plugin called Email before download (https://wordpress.org/plugins/email-before-download/).
I'd like to ask the user's email address before they are able to download a pdf file.
Is there a plugin that can do this in Craft?
Anyone wants to share their experience for best practices?

Thanks in advance!

You can create a simple form and if the user submits he gets redirect to the thank you page with the download link.
Another option is, to send the user the download link via the entered mail. I think that will save you more from trash mails.


Checking for an object, based on a string, using Twig Is there a way—using Twig—to check if a variable is set based on a string of the same name?

In this example below, editLink.object is a string with the value of "entryAlias". I want to check to see if a variable, entryAlias, has been set and to see if it's an entry.

{% if "#{editLink.object}" is same as(editLink.object) %}
    it workes
{% else %}
    not so much
{% endif %}

I've looked into same as and is, but I think the problem I'm having is converting the string to an object. Also, I'm pretty sure this can just be done using PHP, but I'd like to see if it can be done within Craft's Twig so this can be cached as part of a template.

After a Slack conversation with @carlcs, we discovered another slick solution...

{{ 'myVariable' in _context|keys }}

The in operator works much like PHP's in_array function.
The keys filter works just like PHP's array_keys function.


What is $source and $sources in BaseRelationField? I see the following in BaseRelationField:

public function settingsAttributes(): array
{
    $attributes = parent::settingsAttributes();
    $attributes[] = 'sources';
    $attributes[] = 'source';
    $attributes[] = 'targetSiteId';
    $attributes[] = 'viewMode';
    $attributes[] = 'limit';
    $attributes[] = 'selectionLabel';
    $attributes[] = 'localizeRelations';

    return $attributes;
}

The comments for the properties say:

   /**
     * @var string|string[]|null The source keys that this field can relate elements from (used if [[allowMultipleSources]] is set to true)
     */
    public $sources = '*';

    /**
     * @var string|null The source key that this field can relate elements from (used if [[allowMultipleSources]] is set to false)
     */
    public $source;

In the case of the single source. What would that string look like? What is that string referring to? A table name? In ID in a particular table?

Looks like the BaseRelationField is extended by Entries field and Users field and the sources are Entry structures and Administration groups respectively. If I wanted to set one as s static value what would that be?

In case of the element type Asset sources are the available volumes. In case of Category the available sources are the different groups. Sources for Entry are your sections, Tag have the different sets/groups as well.

It contains an array or the type section, folder, group and the ID of the corresponding model, separated with an :

You can just do

$field = Craft::$app->getFields()->getFieldById(>>your field id<<);
Craft::dd($field->sources);

to get to know how it looks like. It could be like this:

array(1) {
  [0]=> string(10) "section:16"
  [1]=> string(10) "section:18"
}

or for an Asset

array(1) {
  [0]=> string(8) "folder:1"
}


Is it possible to add a custom error message when a migration is aborted? I've got a plugin migration that returns false from its safeUp() method when certain conditions aren't met.

Upon returning false from the migration, the update stalls, rolls back changes and gives an error screen with a generic error message – i.e. "There was a problem updating your database".

For many users (i.e. those who aren't tech savvy enough to check logs etc.) this message isn't very helpful at all. Is it possible to add a custom message when a migration is aborted?

I've tried throwing an exception inside safeUp(), which gets logged but does not display on the error screen.

If I use a die() statement for the error message, Craft does display it on the error screen, but that makes the whole update process halt, which seems like an unsafe thing to do.

I've also attempted to use craft()->userSession->setError(), but that doesn't appear to be working, either.

Currently not, but as Lindsey points out, that's not a bad idea for a feature request.


How do you set a user's group on a registration form? In a registration form with public signup possible, how do you set the user group for a particular form?

As a stop-gap I am attempting to use the following code:

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

        if( $user->leader == true )
        {
            craft()->userGroups->assignUserToGroups($user->id, array(2));
        }
    });
}

I created a field (lightswitch) called leader to enable me to check the value after saving the user and adding them to the group. The above code is not working though.

Using var_dump($user->leader) I can see the true and false being returned on user, and also the $user->id is returning the user's id, but the group (2) is not being assigned. Think I am missing something obvious. Thanks.

If you're looking for a Craft 3 solution, I just got this set up on a project and hope it helps someone else out.

First you'll need to add a custom field to your form.  If you want your user to choose which field, then go with a dropdown or radio button and adjust the plugin logic below. My scenario required me using a hidden field and auto-filling in the value based on a query parameter in the url.

<input type="hidden" name="myHiddenInput" value="1">

In your custom plugin's primary class, use this service at the top of your file:

use craft\services\Users;

and then in the init() function add this event and adjust the code to your liking:

Event::on(
    Users::class,
    Users::EVENT_AFTER_ASSIGN_USER_TO_DEFAULT_GROUP,
    function(UserAssignGroupEvent $event) {

        // get your hidden input value, 1 for true, 0 for false
        $isInDifferentGroup = Craft::$app->getRequest()->post('myHiddenInput');

        if ($isInDifferentGroup) {
            $differentGroup = Craft::$app->userGroups->getGroupByHandle('differentGroupHandle');

            $userId = $event->user->id;
            $customGroupId = $differentGroup->id;

            // add more group ids to array in 2nd param to add user to multiple groups
            Craft::$app->getUsers()->assignUserToGroups($userId, [$customGroupId]);
        }
    }
);

Shout-out to Oliver Stark from fortrabbit for helping me with this.


Displaying notification in control panel Is there any js function that will display these notifications that appear on top of the screen? Flash messages use these notifications, but I don't want to use flash messages, I want to display notification without refreshing a page.

Craft's JS/UI framework "Garnish" is largely undocumented, but exploring the Craft object it exposes in the console, I found a few methods that might be useful:
// Good for confirmations (blue banners):
Craft.cp.displayNotice('Your changes have been saved.');

// Good for errors (red banners):
Craft.cp.displayError('Uh oh, something bad happened!');

// Custom flash names (for anything else—requires custom CSS):
Craft.cp.displayNotification('custom-flash-key', 'Something very special happened!');

The last one, displayNotification is the abstract method, called by the first two. custom-flash-key gets added to the notification as a classname.


Plugin Store doesn't get content Craft 3 RC is installed locally. After clicking on the Plugin Store the loading animation stays for 1-3 seconds. After that, the right canvas leaves grey without content.

I've checked the XHR request, it seems working.
reinstalled Craft with composer
tested several browsers

What could be a reason for an empty Plugin Store?

Update

I've tested the issue with a clean install of Craft 3, same problem there. So it should be something global on my machine.

Someone have an idea where the problem could be hidden?

I had to download the CA certificate for curl :

https://curl.haxx.se/docs/caextract.html

And added it to php.ini to get the plugin store working on my local dev env.

curl.cainfo = "x:\path\cacert.pem"

[IIS windows as local dev env]


How to dynamically set an include template I have a series of structure items, each with a product id, based on this id I would like to load in an additional template.

Something like this:

{% set embedAsset = entry.productId %}
{% include "embedAsset" %}

Any ideas?

Your code should work if you remove the quotation marks enclosing your variable.

{% set embedAsset = entry.productId %}
{% include embedAsset %}

If you want, you can use string concatenation to add a path or extension to it.

{% set embedAsset = '_products/' ~ entry.productId ~ '.html' %}
{% include embedAsset %}

You could also add a "fallback template", that loads in case your dynamic template does not exist (read the Twig docs for more info about this).

{% set embedAsset = '_products/' ~ entry.productId ~ '.html' %}
{% set defaultAsset = '_products/default.html' %}
{% include([embedAsset, defaultAsset]) %}


Obtain suspended users in Craft3 Dev Preview So I'm running into an issue where I'm given the users email. From there, I use the following code: Craft::$app->getUsers()->getUserByEmail($email);. This returns a null value, not because the user does not exists, but rather the status is set to suspended.

Is there a way to retrieve the User model from the email address, regardless of status?

Something like this should work:

$user = User::find()
    ->email($email)
    ->status(null)
    ->one();


How can I base64-encode an Image Transform? I'm trying to use this plugin, but it requires an AssetFileModel as input. I can pass it the asset in question, no problem, but I'm wanting to pass it a transformed version of the asset. However, it seems like there's no way in Craft to pass a transformed image as an AssetFileModel.

Here's my code:

{% set image = assetField.first() %}

{# Doesn't work; `image.url({width: 40})` outputs a URL as a string, not an AssetFileModel #}
{% set imageTiny = image.url({ width: 40 }) | image64(true) %}

...

The desired result is for a base64-encoded string of the tiny, 40px-wide image to be output in the HTML.

Any ideas?

It's not going to work with this particular plugin:

        // Require an instance of `AssetFileModel`
        if (! $asset instanceof AssetFileModel)
        {
            // Die quietly.
            return false;
        }

It specifically checks the parameter that you pass in, and expects it to be an AssetFileModel. It wouldn't be too hard to modify the plugin to work for your use-case, though.

I see what you're trying to do; if you're using Craft 3, you could use the ImageOptimize plugin to do it for you.


Auto login user after password reset via a plugin in Craft3 Unlike in Craft2, there doesn't seem to be a way in Craft3 for a plugin to automatically login a user after a password reset.

In Craft2, a plugin could listen for users.onSetPassword to determine when a user had reset their password, and then log the user in.

However, Craft3 doesn't seem to raise any suitable event -- presumably if it did, it would be done in UsersController->actionSetPassword.

The documentation does state:

  The setPasswordSuccessPath config setting designates where the user should be redirected to after they finish resetting their password (and get automatically logged-in).

However, this bit about getting automatically logged in doesn't seem to apply when setPasswordSuccessPath is set.

Is there something that I'm missing here?

Those docs were a bit confusing/misleading, so sorry about that. (I don’t even know what it meant by that!)

They’ve been updated for clarity:

  After the user successfully logs in, they will be redirected to the path defined by the setPasswordSuccessPath config setting.

Additionally, the next Craft release (3.1.8) will start automatically logging users in after resetting their password, if the autoLoginAfterAccountActivation config setting is enabled.

So to answer your question, there won’t be any custom plugin/module code needed for this; just update to 3.1.8 when it comes out and enable that config setting.


How can I set up sources based on my custom field data? Now that I have my custom field displaying in the Entries Table, how can I set up sources to view entries containing specific data from my custom field? My custom field holds several different values, but I want to have a filter set up to view specific things.

Note: this is being copied from the Craft Slack channel so we don't lose it

Setting up the sources in the sidebar isn't difficult. Add the modifyEntrySources()(link) hook to MyCustomPlugin.php.

public function modifyEntrySources(&$sources, $context)
{
    if ($context == 'index')
    {
        $sources[] = array('heading' => 'My Custom Heading');

        $sources['mypluginhandle:mysectionname'] = array(
            'label' => 'My Section Name',
            'criteria' => array('myPluginHandle' => 'SQL CODE HERE')
        );
    }
}

At this point you should have a new entry heading and source in the Entries Listing page. Clicking on it isn't going to filter your data as you may expect... yet.

Notice the 'criteria' => array('myPluginHandle' => 'SQL CODE HERE')? This is what you are going to use to filter the entries according to what you want to see. Everything that is in the value of the 'myPluginHandle' array will be passed over to the modifyElementsQuery() (link) method in the MyCustomFieldtype.php file. This is where you can build out your SQL statement accordingly.

public function modifyElementsQuery(DbCommand $query, $params)
{
    if ($params != null) {

        // whatever you need to do to dig in to your custom fields' data...
        // using the code above, $params would be equal to 'SQL CODE HERE'

        $query->where($params);
    }
}

This should be all you need to filter your entries by your custom field!


Plugin development: how can I check if user has the permissions to delete only his own assets? I have a front-end form where logged users can upload and delete their personal files.

These users have the strict minimum permissions required to interact with this form. They can only Create, Publish and Delete their own entries in the section where the form create these entries.

In order to be able to delete these files from Craft (and not from only the field they're linked in this entry), I have created this custom action in a module:

<?php

namespace modules\mymodule\controllers;

use Craft;
use craft\web\Controller;
use craft\elements\Asset;

class DefaultController extends Controller
{

    public function actionDeleteAsset()
    {
        $this->requireAcceptsJson();
        $this->requireLogin();

        $currentUser = Craft::$app->getUser()->getIdentity();
        if (!$currentUser) {
            return $this->asErrorJson(Craft::t('app', 'Only a logged in user can delete an asset.'));
        } else {
            $request = Craft::$app->getRequest();
            $assetId = $request->getParam('assetId', '');
            $asset = Craft::$app->getAssets()->getAssetById($assetId);

            if (!empty($asset)) {
                if (!Craft::$app->getUser()->checkPermission('deleteFilesAndFoldersInVolume:' . $asset->volumeId)) {
                    return $this->asErrorJson(Craft::t('app', 'You don\'t have permission to delete this asset.'));
                } else {
                    Craft::$app->getElements()->deleteElementById($asset->id, Asset::class);
                    return $this->asJson([
                        'success' => true
                    ]);
                }
            } else {
                return $this->asErrorJson(Craft::t('app', 'This asset does not exists or has already been deleted.'));
            }
        }
    }

}

The problem is that Craft::$app->getUser()->checkPermission('deleteFilesAndFoldersInVolume:' . $asset->volumeId)is not entirely appropriate:

I need to add Volume permissions (View volume + Remove files and
folders) to these users to allow them to delete their assets from the
front-end form ;
By adding these permissions, they have access to
other users' assets, which is a security problem ;

So how can I check if user has the permissions to delete only his own assets? In other words, this user can only interact with the assets he has uploaded.

Thanks to @RobinSchambach advices, I was able to find a safe solution to handle this permission problematic.

This is possible using two main addings:

Register a new custom permission from the plugin

This prevents user from be able to access other users assets

Add a custom User field on the assets to assign ownership ;

Using this we can verify that it was uploaded from user requesting the file deletion

In the plugin Service:

public function linkUserToAssets($assets = [])
{
    $currentUser = Craft::$app->getUser()->getIdentity();
    if ($currentUser && Craft::$app->request->getIsSiteRequest()
        && Craft::$app->request->getIsPost()) {
        foreach ($assets->all() as $asset) {
            if (isset($asset->id) && !empty($asset->id)) {
                $asset = Craft::$app->assets->getAssetById($asset->id);
                if (isset($asset->assetsOwner)) {
                    $asset->assetsOwner = [$currentUser->id];
                    Craft::$app->elements->saveElement($asset);
                }
            }
        }
    }
}

This adds a function to call before each entry is saved, to assign the user's ownership on entry's assets using the assetsOwner custom field.

In the plugin Module:

Event::on(
    UserPermissions::class,
    UserPermissions::EVENT_REGISTER_PERMISSIONS,
    function(RegisterUserPermissionsEvent $event) {
        $event->permissions['User'] = [
            'deleteOwnAssets' => [
                'label' => 'Delete Own Assets'
            ],
        ];
    }
);

This adds a custom permission in the CP which should be added to group of users who will perform this action.

In the plugin Controller:

public function actionDeleteAsset()
{
    $currentUser = Craft::$app->getUser()->getIdentity();
    if ($currentUser) {
        $request = Craft::$app->getRequest();
        $assetId = $request->getBodyParam('assetId');
        $asset = Craft::$app->getAssets()->getAssetById($assetId);

        if ($asset->assetsOwner[0]->id !== $currentUser->id || !Craft::$app->user->checkPermission('deleteOwnAssets')) {
            return $this->asErrorJson(Craft::t('app', self::TEXT_PERMISSION_REQUIRED));
        }
    }
}

This deletes requested assets if the current user was the owner of the asset (using assetsOwner custom field) and has the required custom permission (in this case, deleteOwnAssets).


Redirecting after login based on user group I'd like to control where a user gets re-directed after login based on the user group. That is, userGroupA logs in and is sent to /templateA/_entry, while userGroupB logs in and is sent to /templateB/_entry.

I understand that postLoginRedirect can be used dynamically. Are there any examples of this?

Thanks!

To set the setting dynamically, you’ll need to create a new plugin, which just has an init() method with this:

public function init()
{
    // Is this a login request?
    if (
        craft()->request->getActionSegments() === array('users', 'login') &&
        ($loginName = craft()->request->getPost('loginName')) !== null &&
        ($user = craft()->users->getUserByUsernameOrEmail($loginName)) !== null
    )
    {
        if ($user->isInGroup('userGroupA'))
        {
            craft()->config->set('postLoginRedirect', 'path/a');
        }
        else if ($user->isInGroup('userGroupB'))
        {
            craft()->config->set('postLoginRedirect', 'path/b');
        }
    }
}

If none of the conditions match, then Craft will stick with whatever’s in your craft/config/general.php file.


What are the most common template performance gotchas to avoid? I'm coming to Craft as someone experienced with Expression Engine templates. When I'm coding for EE I try to avoid certain big ticket performance hits, for example:

Using embeds excessively.
Using the disable parameter.
Using simple conditionals vs. advanced conditional when possible.
Keeping a close eye on the query count.
Etc.

In the Craft/Twig world, are there similar common pitfalls to avoid when thinking about template performance? What kind of things should I watch for?

Avoiding unnecessary DB queries is definitely the main thing to watch out for. Here’s an example that came up recently in support: Someone wanted to display the first 6 Matrix blocks in an entry, outputting them 2 at a time. Initially the template code looked something like this:

{% for block in entry.matrixField.limit(2) %}
    <!-- Get the 1st two items -->
{% endfor %}

{% for block in entry.matrixField.offset(2).limit(2) %}
    <!-- Get the next two items -->
{% endfor %}

{% for block in entry.matrixField.offset(4).limit(2) %}
    <!-- Get whatever's left, not more than 2 -->
{% endfor %}

Rather than doing that, we suggested that all 6 blocks be grabbed up front, and output 2 at a time using Twig’s slice filter:

{% set blocks = entry.matrixField.limit(6) %}

{% for block in blocks|slice(0, 2) %}
    <!-- Get the 1st two items -->
{% endfor %}

{% for block in blocks|slice(2, 2) %}
    <!-- Get the next two items -->
{% endfor %}

{% for block in blocks|slice(4, 2) %}
    <!-- Get whatever's left, not more than 2 -->
{% endfor %}

Remember that you can always see what DB queries are happening in a given request by enabling Dev Mode and checking your browser’s Javascript console, where all DB queries for the current request will be logged, ordered by how long they took. It will also show if the exact same query was run multiple times.


Redactor image caption not saved and formatting lost In Craft CMS 2.6.2945, I've added the "image" button in the Redactor config.  In the editor, I've added an image and added a caption (by clicking the inserted image).  When the image is saved, the caption shows up properly and I could see that it was set as figure/figcaption tags after inspecting it.

However, after saving the entry (save and continue editing doesn't even work now as it takes you to the Entries page), the formatting gets lost and the image ends up in a p tag, followed by the caption in a p tag in the editor, but in no tags in templates as raw html.

Is anyone using captions and having troubles?

Added the following comment:

  After some more testing and debugging, I'm noticing that Redactor is
  outputting malformed HTML. The img is going in a p tag, following by a
  figcaption tag. Since my rich text field was set up to clean malformed
  HTML, saving the entry would get rid of the orphaned figcaption tag.
  Any ideas why the img is not going in a figure tag?

Edit: here's the JSON:

I also turned off the HTML purify and clean options but that didn't work out.

{
    "buttons": ["format","bold","lists","link","file","image"],
    "plugins":["video","source"],
    "toolbarFixed": true,
    "formatting": ["p", "blockquote", "h2", "h3", "h4", "h5", "h6"],
    "formattingAdd": {
        "para-intro": {
            "title": "Paragraph Intro",
            "args": ["p", "class", "intro", "toggle"]
        },
        "caption": {
            "title": "Caption",
            "args": ["p", "class", "cap"]
        }
    }
}

I copied your Redactor settings to a clean installation of Craft (2.6.2945) and added an image to a Rich Text field set to use those specific settings. Initially I encountered the same behaviour. However, unlike in your experience, after disabling the Purify HTML? option on the Rich Text field, the figcaption element persisted after saves.


passing product info to another product We have a commerce site that provides samples of its items. We have a single product that handles all the samples and displays them in a dropdown.  What I'd like to do is have a link on the product page that passes this product name/slug to the sample product. What would be the best way to approach this?

The sample product url is /samples/sample. I have tried something like Order a Sample. But that tries to look up a product with that url and it does not exist.

In the dropmenu I am trying

{% set prodSlug = craft.request.getLastSegment() %}
<select>
{% for purchasable in product.variants %}
    {% set current = (product.slug == prodSlug) %}
       {% if current %}
        <option {% if purchasable.stock <= 0 and purchasable.hasUnlimitedStock == false %}disabled {% endif %} value="{{ purchasable.id }}">
        {{ purchasable.description }}  {{ purchasable.price|commerceCurrency(cart.currency) }}</option>
       {% endif %}
{% endfor %}
</select>

Is there a way to setup a product that could accept different slugs to make this work? Maybe with a route?

Yep so can just create a route in the control panel.

See: https://docs.craftcms.com/v3/routing.html

You'll see you can use a slug (using the token slug) so that Craft will validate the url actually has a valid slug on the end, or you can just use * and craft will route to this template no matter what is on the end.

(I personally prefer to create routes in source control managed & commented code using the routes.php config file - https://docs.craftcms.com/v3/routing.html#advanced-routing-with-url-rules  ... so this would be something like:

'samples/sample/<slug>'          => ['template' => '/your/samples/template']

Then, on the main product page, build your url to the samples page as, basically:

/samples/sample/{{ thisProduct.slug }}

Then, in your samples template you just grab the last segment, as you are, and use that in your code to pre-select the particular sample from you list...as you are.

So basically, you're almost there apart from creating the route.


How do you get a section's ID I need to access just the ID of a specific section.

I do that like this and it works:

{% set enroll = craft.sections.getSectionByHandle('workshopEnrollments') %}

But when I do:

{% for e in enroll %} {{ e.sectionId }} {% endfor %}

or:

{% for e in enroll %} {{ e.id }} {% endfor %}

I get the error:

Impossible to access an attribute ("sectionId") on a string variable ("32")

However, when I view the variable inside my form what I actually see is exactly this

32Workshop EnrollmentsworkshopEnrollmentschannel1workshop-enrollments/_entry1

So my question is, how can I just display the id.  Both ".id" and ".sectionId" produce the same error mentioned above.

"32" is the id I want so it is the correct return, but this seems to show everything about it in a string and I need just the id. I am using getSectionByHandle because I have no other data to use in this particular view.

Thanks for any help.

getSectionByHandle doesn't return an array, but a single SectionModel - so you need to remove the for loop:

{% set enroll = craft.sections.getSectionByHandle('workshopEnrollments') %}

{{ enroll.id }}


Problem isolating assets I have a 100 articles, and with for each article 1 author. Authors can only edit their own entries.

Every article as an Asset directory called "Photo Album". This works fine. Beautiful, even.

The problem is that, when I enable

- View source
--- Upload files

.... every author from the group has access to the entire Assets, Photo Album folder, globally, so to all folders. I need to enable the "View source" part because they also need to be able to add a Caption and Copyright notice for each picture.

Under Sections, one could finegrain this

- Edit other authors’ entries
- Edit other authors’ drafts

But not with Assets.

Does anyone have a fix for this?

I don't really expect my editors to start sabotaging each others's articles but it's not very elegant, and mistakes can be made, esp with 100 directories in there.

Thanks

As far as I know you can't restrict users to certain folders in the Assets section of Craft, you can only set their permissions for the whole Asset Source.

The only workaround I can think of for you would be to actually remove Authors permissions on the Assets section of Craft completely.

That way the only way they can make Asset updates is when they are editing an entry, and with the permissions structure you have they can only edit entries they have made themselves anyway.

As they don't have access to other people's entries, they can't edit the assets on them.

It may not be the most elegant approach but it would give you the functionality you are looking for. I'm sure others might have a better way of handling it though. Hope that helps a little.


Craft 3: how to show the Yii Debug Toolbar? I'm using Craft 3 RC and I am logged as an admin. How can I display the Yii debug toolbar on front-end and Control Panel requests? By default, I don't see this toolbar.

There are two checkboxes in your user settings, go to your admin account -> preferences index.php?p=admin/myaccount and check the boxes

This is covered in detail in the Profiling your Website with Craft CMS 3’s Debug Toolbar article


Thumbnails broken in CP after migrating images to AWS S3 I migrated my images over to AWS S3 using the Amazon S3 plugin; however, now I'm seeing that the thumbnails in the CP are broken, and a brokenimage is displayed.

The full size images display fine in the CP and the front end. If I re-upload them, the thumbnails display correctly in the CP, but that's not an ideal solution.

Is there a way to make the images display correctly in the CP without re-uploading them?

My first guess would have been that the asset indexes stored in the database are out of sync since it was recently migrated to a new Volume.

A way to fix that would be to run the "Update Asset Indexes" utility from the Control Panel.


How do I create a list of categories and display the entries relating to each of these categories that have at least one entry? Here's a summary of what I try to do:

I created a Single page with a specific template where I want to display a list of partners
I created a group of Categories to define all types of partners
All my partners are encoded in a 'partners' Channel
In this Channel, each partner has a field that allows to select one partner Category
On the Single page, I display all categories of partners with at least one partner
For each Category of partners, I display the partner(s) related to this Category

And here is my code which attempt to do this, but not correctly:

{% set entryIds = craft.entries.section('partners').ids() %}
  {% set categories = craft.categories.relatedTo({ sourceElement: entryIds }).groupId(2).find() %}
    {% for category in categories %}

{{ category.title }}

        {% set categoryToDisplay = category.slug %}
        {% for entry in craft.entries.section('partners') %}

          {% for category in craft.categories(categoryToDisplay) %}

{{ entry.title }}

          {% endfor %}

        {% endfor %}

    {% endfor %}

This properly displays all Partners categories with at least one partner, but this does not correctly display the partners associated with each category. Each time, all partners of all categories are repeated instead of displaying only those that are related.

Here's the link to relevant the "Get Help" article:

How do I list the entries related to a category?

These really helpful tutorials are somewhat hidden on builtwithcraft.com, I overlooked them more than once while thought the normal Documentation articles!

Just use the provided example code in your category loop:

{% set entryIds = craft.entries.section('partners').ids() %}
{% set categories = craft.categories.relatedTo({ sourceElement: entryIds }).groupId(2).find() %}

{% for category in categories %}

{{ category.title }}

            {# Fetch all of the entries related to this category #}
            {% set entries = craft.entries.relatedTo(category) %}

            {# Output them #}
            {% for entry in entries %}

{{ entry.title }}

            {% endfor %}

{% endfor %}


.limit(1) on asset field loop is effecting another loop on same page I have an Asset field where the first image is used as a big 'feature' and then the rest are used as support further down the page.

When I add .limit(1) to the for loop for the 'feature' image it effects the for loop further down the page by also only showing 1 image.

This is my code...

{% set entries = craft.entries.section('abstractionProjects').type('abstractionProjects').all() %}
{% for projectPanels in entries %}

    Some HTML...

    {% for asset in projectPanels.projectImages.limit(1) %}

    {% endfor %}

    Some HTML.....
    More HTML...

    {% for thumbs in projectPanels.projectImages.offset(1) %}

    {% endfor %}

    More HTML...

{% endfor %}

If I remove the .limit(1) from the first loop all the images are shown in both loops :/

Help appreciated

Thanks

Yes, this is actually the intended behaviour, and one that changed when going from Craft 2 to Craft 3. You can read more about it in the Changes in Craft 3 article in the docs. projectImages in this case which is an Asset field (I assume), returns an ElementQuery when you access it with projectPanels.projectImages. The field doesn't automatically return a new model when accessed, it returns the reference to the one and same query.

The workaround is to either reset the parameters you need to reset, like limit() in this case, or use clone as described in that article.

Ie, either something like this:

{% for asset in projectPanels.projectImages.limit(1).all() %}
   ...
{% endfor %}

{% for thumbs in projectPanels.projectImages.limit(null).offset(1).all() %}
   ...
{% endfor %}

Or,

{% for asset in clone(projectPanels.projectImages).limit(1).all() %}
   ...
{% endfor %}

{% for thumbs in clone(projectPanels.projectImages).offset(1).all() %}
   ...
{% endfor %}

Have stumbled on this quite a few times myself alreadym, it's a bit confusing. Hope this helps though. :)