Display nested category I have an entry which has a category field 'productCategory' and the chosen category is a nested one (3 deep), I want to output the category and where it's nested, but by using the field's name: {{ entry.productCategory.one() }} I get the parent category only, because of .one() I guess, if I use without .one(), then I get a tonne of queries displayed, so I'm not sure how to even approach displaying the actual nested category like a breadcrumb trail. I can display the categories as a nested navigation, but for now I want the entry's chosen category, any ideas? Thanks

aah

{% for category in entry.productCategory %}
    {{ category.title }} >
{% endfor %}


How/Can does Craft handle ancestor/grandparent relationships? I spent a fair amount of time yesterday searching for any other questions that may have been answered that are similar to this one, but haven't had much luck.

Essentially what I want to do is to daisy chain some Entry relationship fields. I'm not entirely sure if this is possible in Craft, as I haven't seen any documentation or commentary on it.

Here is how I would like to have my content organized. The site will have a few channels: States, Regions, Counties, Towns and Inductees. As you can tell, the relationships basically drill-down in an obvious way: States have Regions; Regions have Counties; while Counties have both Towns and Inductees. I like the idea of being able to add a new Town, select which County it belongs to, and automatically inherit the Region and State data. Ideally, I'd like to be able to do something in a Town template like {{entry.state.title}} which would pull the title of the state to which this town's county belongs.

Now, the States and Regions basically are just lists... The States page will be an archive listing all the Regions in the State. The Regions page will be an archive listing all the Counties in the Region. Would these be better suited as Categories, than Channels? If so, would there still be a way to access the State from, say, a Town or Inductee entry? I'd rather not have to rely on the content editor to have to select states and regions for each new Town/Inductee entry. I'd rather they only have to select its County.

I'm new to Craft, so I apologize if this question seems either absurd or incoherent :)

Thanks!

As usual with Craft you have several options. If you didn't need the index pages, I would suggest a location category, which you organized into 3 levels: states, regions, counties. Selecting a county location would also select its containing region and state. In your town entry you would do:

{% for l in entry.locations %}
  {{l.title}}
{% endfor %}

You would go through the loop three times, and {{l.title}} would be the state, region, and then county.

Disadvantages:

nothing in Craft enforces that you properly organize your location categories
nothing in Craft keeps you from choosing 3 states instead of one county for your town entry
the location index pages will be hard to write

Another option is to use a channel (or category) for states, a channel for regions, a channel for counties. The county entry would have a field for the containing region, the region entry would have a field for the containing state. And the town entry would have a field for the region. Then in the town template:

{{ entry.region.first.county.first.state.title }}

This will fail badly if any of the required relationships aren't there. You can test everything before you use it:

{% if entry.region.first %} ...

Or you can use the ?? (null-coalescing) operator:

{{ entry.region.first.county.first.state.title ?? 'no state' }}

Fetching the state title will be fairly expensive. You could use the preparse plugin to save the state name on the town entry.

For the index pages, you will want the regions that are related to the state, etc.


Get radio button's value from a form by PHP I have been developing validations for my form.

It is possible to get text input's value like;

$password = Craft::$app->request->getBodyParam('password');

However, this function seems not to work for radio button fields. Could you tell me how to get radio button's value?

I have solved it by myself;

$application = $event->sender->application;

Even for radio button's value, no suffix is necessary like value.


URLs other than index cause 500 internal server error I installed craft according to the documentation but when I tried to access example.com/admin I get following error

  Request exceeded the limit of 10 internal redirects due to probable
  configuration error. Use 'LimitInternalRecursion' to increase the
  limit if necessary. Use 'LogLevel debug' to get a backtrace., referer:
  http://example.com/

Every subpage like example.com/about has the same behavior. Only example.com works

I changed the .htaccess-file after I found the same problem with cakePHP. I just had to add RewriteBase / after RewriteEngine On so the .htaccess looks like:

<IfModule mod_rewrite.c>
    RewriteBase /
    # Send would be 404 requests to Craft
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} !^/(favicon\.ico|apple-touch-icon.*\.png)$ [NC]
    RewriteRule (.+) index.php?p=$1 [QSA,L]
</IfModule>

If your site is multilingual (i.e., if you have subfolders like /en/, /de/, etc. with indivudual .htaccess files in them), this fix requires you to add you the locale's segment to the specific rewrite base of each .htaccess file (e.g. RewriteBase /en/ and so on.)

Now it works fine.


Required alt text field in assets I'm not sure if this is a bug or I'm doing this wrong. I have an asset volume called "Blog Images" and there is a custom required field called Image alt text. If I add images in assets section I'm required to put something in for the alt text, that's good because that what I intended. But I'm using a matrix block that has an asset field that looks to this volume and I am able to upload and image to this volume and not add anything in this field. What am I doing wrong?

Also I should mention this is using Craft 3.

This is available as of 3.2. Make the alt text field required for the asset volume, then check the "related element validation" box when creating an asset field.


Exporting site configurations for import to new websites - separate databases Is it possible to export the site configurations of one website in terms of the site structures, not the content, for importing into another site with a separate database, essentially creating a site installation profile, similar to that possible in Drupal?

Sounds like a great use-case for Project Config!
It's a file-based representation of your site's settings and information architecture without the content.
Get your boilerplate Project Config setup, then copy the config/project files to a new site and use it as a starting point.


How can I add and replace classes in the markdown field? Prompt please both in the field {{ block.markdownContent|markdown|raw }} replace and add classes?

For example:

 replace by:

 replace by:

 replace by:

 replace by:
 and add within the tag
 тег

Provided that the listed list of
 is to be replaced:

 replace by:

 replace by:

add:

1

Html code:

Numbered list:

Simple text

                    1

Simple text

                    2

Simple text

Simple list:

Simple text

Simple text

Simple text

I would rather recommend to stick to semantic HTML and <ol> elements for your ordered lists. This not only makes showing the numbers easier, it really should be done out of accessibility reasons and it’s good practice in general.

There are a couple of good solutions to modify the way numbers are displayed in ordered lists using CSS, if you are not happy with the way they are shown by default.

I can recommend using display: table-row; for it. Example code: https://stackoverflow.com/a/23947414

And here’s some example code for formatting content: counter(); in pseudo elements: https://craftcms.stackexchange.com/a/1781/125


SEOmatic - Set the SEO tags based off of a category? I was wondering if there was a way I could set the SEO tags based off of a category?

eg. If a entry is in the "Marketing" category, use marketing Meta data?

Any help would be greatly appreciated.

Thanks in advance!

Absolutely. Just create those fields then assign them to your categories under field layout just like you would do for your sections.


Excluding fields in a search Is it possible to exclude some fields when using search?

Getting the q parameter from the URL and searching the products section including all fields:

{% set query = craft.request.getParam('q') %}
{% set results = craft.entries.section('products').search(query) %}

This searches all fields, included those for "related articles" and "related products". I need to exclude those from the search.

Some experiments

Works to exclude hits in relatedProducts but limits the fields being searched to the productName field:

craft.entries.section('products').search('productName:'~ query ~' -relatedProducts:'~ query)

Does not work - also returns the product with a related product matching the query:

craft.entries.section('products').search(query ~' -relatedProducts:'~ query)

and the same when trying to include several fields before excluding relatedProducts:

craft.entries.section('products').search('productName:'~ query ~' OR productDescription:'~ query ~' -relatedProducts:'~ query)

Also tried searching just in the relatedProducts, confirming that it returns only the product with a related entry matching the query:

craft.entries.section('products').search('relatedProducts:'~ query)

What about including only those fields you want to search:

craft.entries.section('products').search('productName:'~ query ~' OR productDescription:'~ query ~' OR productFoo:'~ query ~' OR productBar:'~ query)

Yep I know, it's not very nice (but hey, it was you who started listing them). What you could do to make this ugly thing of a search parameter more manageable is to add the conditions programmatically:

{% set searchFields = [
    'productName',
    'productDescription',
    'productFoo',
    'productBar',
] %}

{% set searchParam %}

    {% for searchField in searchFields %}
        {{- not loop.first ? 'OR' -}}
        {{- searchField ~ ':' ~ query -}}
    {% endfor %}

{% endset %}

{% set results = craft.entries.section('products').search(searchParam|trim) %}


Craft Commerce 2.0.2 Shipping Rule Names & Descriptions—How? I just noticed I had to update some templates after updating to the latest Commerce version (2.0.2).

How does one go about getting the method's rule description?

I currently have it set up to apply a rule based on geographical shipping location (like contiguous US states / Hawaii and Alaska / Russia / World / Oceania)—but {{ method.description }} from within {% for handle, method in cart.availableShippingMethods %} breaks the site.

Cheers!

You have to can loop through the shipping rules for the shipping method.

{% for handle, method in cart.availableShippingMethods %}

  {% if method.shippingRules|length %}
    {% for shippingRule in method.shippingRules %}
      {{ shippingRule.name }}
      {{ shippingRule.description }}
    {% endfor %}
  {% endif %}

{% endfor %}


Update record definition after plugin is already installed If you have a plugin installed with its own tables and you want to add some extra database fields to the table, what is the process for this?

Craft will create database tables using records on installation of the plugin, but what about after this? If I update the record definitions is there a way to trigger Craft to update the table or do I have to add the fields through MySQL?

If you want to change database schema after a plugin has already been installed, the way to do it is through migrations.

You can see many examples of this in Craft in the craft/app/migrations folder.


How can I get unpublished drafts in my plugin? I'm building a widget where I want to list all unpublished drafts (made by users without access to publish live changes), but I can't figure out how to get them.

Depending on what you want to do with them you can request them using craft.entries but you'll probably need to change the status to pending.

If this doesn't work you'll need to fetch them through a custom controller/service that goes in and fetches the entry model and returns a list that fit you criteria.

Hope this helps.


Can I use a Global Variable for date/time? The goal is to create a global variable that allows the user to specify how they would like to format the date/time for entries. I'd like to remove the hardcoded format shown here:

{{ entry.postDate|date('Y.d.m') }}

And replace it with something like this:

{{ entry.postDate|date('{{ global__siteMetadata.gv__articleDateFormat }}') }}

The user would add "Y.d.m" or any other PHP date format they like through the global field in the CMS. But when I use this, I get the following output:

{{ 1THURSDAY2019PMTHURSDAY__001631AMERICA/CHICAGOMARAMERICA/CHICAGO31PM21PM31PM.1CT__PMTHU, 21 MAR 2019 13:16:00 -050031162019-03-21T13:16:00-05:00THURSDAYAMERICA/CHICAGOTHUPM31AMERICA/CHICAGOMARCH2019THU, 21 MAR 2019 13:16:00 -050003PM31 }}

Obviously, the global variable is not being parsed. Any thoughts on how to make this happen?

You never need to nest Twig tags. It looks like {{ glo... is all being processed (or attempted to process) as PHP date shorthand codes.

Try this instead...

{{ entry.postDate|date(global__siteMetadata.gv__articleDateFormat) }}


P&T Contact Form v1 adding code for 'beforeSend' Very new to Craft, so can someone please tell me what class/file I'd add the 'The beforeSend event' code, as I'd like to filter out some spam?

The documentation https://docs.craftcms.com/v2/plugins/hooks-and-events.html#hooks says that the new event listener should be added to the plugins 'init' function, but there isn't one.

many thanks

Correct way is little bit longer.

Firstly, you need your own plugin, you can generate skeleton here: https://pluginfactory.io/ It's better than rewrite someone else's, because you can't update it after you add a code.

Secondly, you add your beforeSend Event to Plugin class by add it to init function - the function will be generated in step one - it is in the YourNamePlugin php file in root.

If you start a new project, it is better to work in Craft 3.


Creating a new locale on craft 2, entries won't load I have an issue creating a new locale, I already have two set up 'en_gb' and 'en_au' and have added a third 'fr'.

I have added the new locale to general.php:

'.com' => array(
  'siteUrl' => array(
        'en_gb' => 'https://xxx.com/',
        'en_au' => 'https://xxx.com/au/',
        'fr' => 'https://xxx.com/fr/'
    ),
  'devMode' => false
),

And have enabled that locale on the section with exactly the same settings as the others:

When I switch to the French locale on an entry I can live preview without a problem, but when I actually publish the page and visit it the entry cannot be found.

I'm well and truly stumped on why I can't access the published page.

When creating the locale I did have issues with failed tasks re-saving entries which I spent a lot of time trying to debug with this guide without luck and with less hair. Would this be a cause considering the locale shows on an entry and the live preview works?

Is the /fr/ directory created in your public_html folder?
https://docs.craftcms.com/v2/localization-guide.html#step-5-set-up-the-files

Maybe try making a totally new entry for all 3 locales.


Element API: List entries related by tag How can I pull out other entries related by tag using the Element API?

In this particular case I'm using the Similar plugin to pull out related entries by tag in the templates. I'm not sure if this would help to provide an easier way or not...?

The Similar plugin allows access to its find() method from PHP and your Element API configuration file.

$similarEntriesByTags = craft()->similar->find([
    element => $entry,
    context => $entry->tags,
    criteria => $limitCriteria,
]);

This is the equivalent to the Twig example code in the readme.

{% set similarEntriesByTags = craft.similar.find({ element: entry, context: entry.tags }) %}

If you’re inexperienced in quering for entries or using element criteria in PHP in general have a look a the Working with Elements article in the Craft documentation.


Can't update or install plugins locally, macOS I'm unable to update or install plugins in Craft 3.
Running in macOS via MAMP, PHP 7.0.8.

Error: The "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8" file could not be downloaded: SSL operation failed with code 1. OpenSSL Error messages:
error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version
Failed to enable crypto
failed to open stream: operation failed

Output: Loading composer repositories with package information
Updating dependencies
Package operations: 8 installs, 0 updates, 0 removals
  - Installing psr/cache (1.0.1): Downloading (connecting...)Downloading (failed)
Downloading (connecting...)
Downloading (failed)
Downloading (connecting...)
Downloading (failed)

Has anyone run into this situation or have any suggestions as to how to get around it?

error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version

Your local OpenSSL setup is trying to connect with an older, insecure TLS 1.0 protocol, which Github (and a lot of other sites) no longer support.

If you update it to use TLS 1.2, the error will go away.

If you're on something like MAMP/WAMP, etc. then updating to the latest version of their software is usually enough to resolve this issue.


Added custom entry details fields I'm looking to add a custom fields to the details section on the right of the entry edit view in the control panel.

I know that in Craft 2 this was possible using:
Craft::$app->view->hook('cp.entries.edit.details', functionThatReturnsView())

Is there a more Craft 3 way to do this or do we still use the Craft 2 API for this kind of extension to control panel?

$view->hook('cp.entries.edit.details', functionThatReturnsView())

Is the correct way in Craft 3.


Setting Up Multiple Sites I've tried following the guide for setting up multiple sites (https://docs.craftcms.com/v3/sites.html#creating-a-site) but I think i'm missing something.

Firstly the craft install on your beta site, does it share the same database?

Secondly I'm not exactly sure what 'step 7' is asking me to do, could you please explain?

FYI, I'm using plesk on my hosting.

Thanks in advance

Firstly the Craft install on your beta site, does it share the same database?

Craft's multi-site setup is meant to use a single database.

  Secondly I'm not exactly sure what 'step 7' is asking me to do, could you please explain?

Step 7: Configure Your Web Server and DNS

1.  Configure your web server so the domain (e.g. beta.craftcms.com) points at the web directory. Craft will automatically detect which site the browser is requesting.
2.  Update your DNS records so the domain points at the web server.

It will be difficult to answer without knowing your specific environment and where your domain name is registered or if you're just doing this locally with a development domain.


Normalize then validate unique entry in a custom module field I have a custom text field in a module that the user populate with an EAN number. The input is getting validate by a "match" validator and then is serialized before save to get ride of the various variants of the EAN the user can enter ("978-2-87929-647-0", "978.2.87929.647.0" and so on…).
But now I need to validate is uniqueness and I can't find the right way to do this.

How can I normalize the input and then test if this normalized value already exists in the DB?

I cannot find a way to get the actual user input and normalize it before validate it.

I guess I have to use the "UniqueValidator" to test if the entry already exists in the DB.

Thanks for your help.

If your model somehow extends Yii's Model (which I think it does because you use Yii validators), you can use the beforeValidate() method to change attribute values before validation:

public function beforeValidate()
{
    // Before validation, normalize the attribute's value with whatever method you create for it
    $this->attribute = $this->yourNormalizationMethod($this->attribute);
}

After that, you can use Yii's UniqueValidator to make sure the attribute's value is unique. The docs show you some of it's possible configs, I'm sure you can make it work!


List all entries in a section filtered by given category groups I've got a section called 'web development' and there are four categories that apply to it. Three can be grouped together as 'original content' and one is a link list to external third party content.

I'd like to split my entry archive list such that one group lists all entries in the Web Development section that don't belong to the 'external' category (there will only ever be one 'external' category, but I may make new categories that apply to 'original content' so I don't want to hard-code the categories).

After that I'd like to loop through again but only listing entries that do belong to that 'external' category. For each entry I'd like to spit out which category it belongs to as well as the entry title and date.

I can't wrap my head around how to do this, I'm not sure I'm groking how to use relations. Or would I be better to create nested categories rather than trying to group them 'after the fact'?

EDIT: went with making categories and sub categories, but still can't get things to work!

{% set originalContentCategory   = craft.categories.slug('original-content').find() %}
{% set originalContentCategories = craft.categories.group('global').descendantOf(originalContentCategory).find() %}
{% for category in originalContentCategories %}
    <h3>{{ category.title }}</h3>
    {{ category.synopsis }}
{% endfor %}

This is listing all categories in the group, including the 'original-content' category itself. What am I doing wrong?

I would use the without filter and relations for this.

{# starting points #}
{% set externalCategory = craft.categories.slug('external-content').first() %}
{% set allEntries = craft.entries.section('webdev').find() %}

{# create the arrays/objects you need #}
{% set externalEntries = craft.entries.section('webdev').relatedTo(externalCategory).find() %}
{% set otherEntries = allEntries|without(externalEntries) %}

then use the usual for loops for each array / object you have created. That way, you only need to define your external-content category, all the rest is automatic, regardless of the number of other categories you have.

Depending on the number of entries you have and the kind of traffic you get, you might want to consider caching this.


Change admin CP default locale I have 3 locales set up - en_ca, fr_ca, en_us.  en_us was selected when installing Craft.  I had to change the default locale to en_ca and did so in general.php after installation.  I'd like this change in the CP as well.  Currently, entering new entries and the CP site link all still point to en_us.

How can I change the default locale?

You need to update the locale to en_ca in the control panel under Settings->Locales.


Text-Slider within Matrix field in my Sections (Single, Channel and Structure) I am using one matrix field containing typical content elements, Text (One Column), Text (Two Columns), Image, Text-Slider, Image-Slider and so on...

My problem is the Text-Slider with Richtext and links in it. Unfortunately Matrix field doesn't offer any Repeater or Nested Matrix functionality so I don't know how to build the structure for this element.

My Image-Slider is a no brainer , I take an asset field and with every image there is another Richtext field I can use.

The Table field has no Richtext field in it...
There are a lot of small Text-Sliders all over the website.
Any Ideas? Thanks much!

You could use your default "Text" Matrix block type for the sliders and have an extra block type as a "Slide Wrapper", to wrap "Text" (and "Image") blocks which should go into your slider.

See this question for an alternative approach, to recognize succeeding blocks of the same type automatically in the template Solution for Matrix-within-Matrix (or More Robust Table Fields).


Sprout Forms issue: form not submitting After recently discovering the logs for Sprout forms (located in craft/storage/runtime/logs) I have been studying them and debugging new forms that I am creating.

After integrating a form hook in my templates, I am getting an error: A form submission failed because the user did not have Javascript enabled. - but I do have Javascript enabled... it is prevalent on other parts of the page.

Any tips... ?

Probably you are caching the page. You can't cache a sprout form. The csrf will be the same for all the users.


Using Craft with CloudFront, and handling invalidation I'm trying to get static assets set up to be served by CloudFront. I know how to get the S3 bucket working, and have CloudFront serving from that bucket, but what I can't wrap my head around is how to handle the invalidation. In non-Craft instances I know about hashing to trigger the invalidation, but not sure how to do that in Craft.

Specifically, if an editor uploads a photo named "photo.jpg", and then publishes, and decides to make a change and uploads the revised photo with the same name, how does CloudFront know that it has changed since the editors don't know about content hashing?

Hopefully this makes sense. While not at all new to Craft, I'm very new to running it with performance in mind and even newer to using a CDN. Any help is greatly appreciated!

One way would be to manually clear it with Cloudfront Invalidation.


Making the Smart Map field translatable I have a multi-language site. So I need to show slightly different address data, depending on the current locale. Especially regarding the city and country field.

Example:

DE

Venloer Str. 200
Köln
Deutschland

EN

Venloer Str. 200
Cologne
Germany

I tried marking the field as translatable, but it hasn’t worked out of the box and I couldn’t find any documentation on this.

Any hint would be much appreciated.

Smart Map doesn't currently support the ability to make an Address translatable. It's on the feature request list, and will be implemented at some point in the future.

In the meantime, you can use static translations to handle names of common cities & countries:

City: {{ entry.location.city | t }}
Country: {{ entry.location.country | t }}

* The t filter is an alias for translate.


Log exception to a third party service like Sentry or Rollbar? Are their any best practices?

Maybe some way to hook to craft native exception / log handler with a third party implantation so I could track my errors on services like Sentry or Rollbar?

Update:

Went to look into this and while it's true that Craft doesn't have any explicit events around error handling, Yii already does in the form of onException and onError.

You can listen for them like so:

craft()->onException = array($this, 'myCustomExceptionMethodHandler');

and

craft()->onError = array($this, 'myCustomErrorMethodHandler');

Original answer:

Craft currently doesn't have any events around error handling, but it's probably not a bad idea to do so for situations like this.

I've added it to our list.

In the meantime, the only workaround I can think of is pretty sketchy, but a plugin could monitor the craft/storage/runtime/logs folder for [error] entries and parse the text for the information it needs.


Updating private repo plugins from admin? Is it possible to update private repo plugins from the admin at all? Would love that functionality, especially for smaller projects that are installed where there's no CLI access.

It's not currently possible. The CP update process involves interactions with the Craft web services and the Craft web services don't have any information about the private repository for your site.


Can you hook into a Redactor field before render? I am wanting to hook into all my redactor fields content before rendering and update any inline links to include a title="" attribute. Is it possible to hook into the field before it is rendered? With PHP or in settings (https://imperavi.com/redactor/docs/settings/)?

Thanks

Probably several ways you could tackle this like with a custom Redactor JS plugin, but I'd probably write a Craft plugin that forked and extended the original Craft Redactor plugin.

From there, you could overwrite the Redactor field's normalizeValue() method and parse the input to make whatever changes you want before returning.


LastPass causing incorrect email address to be saved in new user form I already have a solution for this but am writing it up in case others run into the same issue.

After creating a new user, my email address was showing up as the username of the new user. (This site uses email addresses as the username; useEmailAsUsername is set to true in config/general.php.) Here's how it looked in the user list:

It also showed up on the user page:

I thought I might have stumbled on a bug related to useEmailAsUsername. Silly me. Craft support (hi, Brad) wasn't able to reproduce it and suggested it might be an auto-fill browser extension misbehaving. Sure enough…

After disabling my password manager, LastPass, my email address stopped sneaking into the username field. Upon closer inspection, here's what was happening:

After filling out the new user form and clicking 'Save,' you are prompted for your password. THIS is where LastPass misbehaves, or more accurately, does what it's designed to do but not very helpfully. It not only fills the password input, but replaces the contents of the 'Username' field with your email address (that is, the email address associated with the login stored in LastPass). It's easy to miss this, however, with the transluscent white overlay.

Solution

Configuring LastPass to not fill forms on the new user page isn't helpful, as you likely want it to enter your password when prompted. The best solution I could come up with is to clone my Craft login, delete the email address / username, and add 'New User' to the label. Now when prompted for the password I can select that login and avoid overwriting the new user's email address.


Can a plugin fire a custom event? I have PluginA that needs to listen for something that PluginB does. PluginB needs to include a Model in the event data so that PluginA (and any other subscriber) can pick that up and do whatever it needs to do.

Is that possible right now?

Yes. It's not documented, but by instantiating the Event class (which is just a thin wrapper for Yii's CEvent) you can create and raise your own events. Obviously you could extend the Event class as well, if need be.

See Aaron Berkowitz' excellent answer to a similar question for details -> https://craftcms.stackexchange.com/a/6941/1098

Also, check out the following resource for more on events in Yii: http://www.yiiframework.com/wiki/327/events-explained


Licensing a multi-lingual site using different domains I'm building a site for a multinational company. I have domains set up like this example:

acme.com (main portal site, different from local sites, but also aggregating content from all local sites)
acmelocal.de,
acmelocal.fr,
acmelocal.co.uk ... (local sites using the same structure only different languages)

I want to run everything on the same Craft install to be able to share data. I'll enable certain sections for main portal site only and certain for local sites only - from what I've read, Craft could handle that ... right?

How do I license this? I'm thinking I can buy a pro license for "acme.com" and use all local sites as locales of the main site. Would this work?

I'm running everything on a custom server, so I can set up all the domain stuff etc.

I got the official answer from Brandon, here is the summary for anyone that might be in a similar situation:

I can do what I need with only one pro license

the main portal (acme.com) will run the "en" locale
additional domains will use different locales (en-uk for UK, etc.)
this will work even better in Craft 3.0 - there will be a support for "portal" and separate locales
each domain can have its own URL, templates and content
I will need to access all administration from a single domain (content editors in another country will have to use acme.com for editing their acmelocal.co.uk)

I will develop on a separate domain first (acme.com is currently live with old site), but I can transfer the license easily to acme.com when site is finished.

I can use any local domain for development with this license - more detail here.

Thanks, Brandon!


Update existing entry from plugin not saving and producing no error So, i'm trying to set a fields content via a plugin on an entry which already exists.

My code is like this:

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

$entry->setContentFromPost(array(
    'subTitle' => 'woot'
));

$success = craft()->entries->saveEntry($entry);

I get no error and the value of $success is false but it tells me nothing else.
If I var_dump($entry) after the first line, the output shows it has pulled the correct entry.

I haven't used getEntryById, and this isn't exactly an answer, but here is how I am doing pretty much the same thing:

$criteria = craft()->elements->getCriteria(ElementType::Entry);
$criteria->section = 'mySection');
$criteria->status = null;
$criteria->limit = 1;
$entry = $criteria->first();

...
if ($entry) {
    $entry->setContentFromPost([
        'myFieldHandle' => 'woot'
    ]);

    craft()->entries->saveEntry($entry);
} else {
    // Could not find entry.
}

...

Unless you are setting the page title, then I think you need to use something like:

...
$entry->getContent()->title = 'woot';


proc_open() has been disabled for security reasons I'm trying to update Craft, but I'm getting an error while updating stating it's not possible to make a database backup. I've therefore gone for the suggestion to revert back instead of pushing on. Back in the CMS now when I try to create a backup manually (utilities-> database backup) it tells me:

there was a problem backing up your database. Please check the logs.

I've done that and it says this:

2018-11-30 11:43:57 [-][1][-][error][yii\base\Exception] yii\base\Exception: Could not create backup: proc_open() has been disabled for security reasons in /home/bxrdvjsx/xxxxxx.de/vendor/craftcms/cms/src/controllers/UtilitiesController.php:382

Does anyone know how I can fix this? If it isn't obvious from my question I'm not very familiar with Craft and have inherited this website from the person who built it.

Your host has likely added proc_open to your php.ini file's disable_functions setting.

If you have access to it, you can remove it from that setting.  If not, you'll have to ask your host to do it.

You can see that Craft requires the PHP proc_* methods to be able to perform some operations here: https://docs.craftcms.com/v3/requirements.html#optional-php-methods-and-configurations


Globally share variables I have 4 main template folders, under the /craft folder (each template folder works for another subdomain).

Now, I do have per template folder, a few variables that I like to have shared amongst all templates underneath it.

Such as:

{% set projectsGroup = "projectsWeather" %}
{% set sliderGroup = "sliderWeather" %}
{% set faqGroup = "faqWeather" %}

Which are different, per template folder.

Now, when I put these variables at the top of my master _layout file, this gets shared nicely amongst all templates -- that is, for those that use this _layout file.

I do however also have a few json generation template files, that can not use the _layout file.

My question now is how to elegantly assign these variables, and do this only once.

I can not stick them in a separate file and just use "include" or "extend", because then _layout either ignores or complains.

Any ideas?

Add them to the environmentVariables section of your config file, and retrieve them using the craft.config template tag.

For example:

// Config
"environmentVariables" => [
    "color" => "blue",
]

// Template
{{ craft.config.environmentVariables.color }}


Is there a way of displaying an entry twice depending on postDate and a custom field date in one loop? I'm building a feed of entries mixed from different sections. One section contains a postDate and a custom field date called releaseDate. I want these entries to appear in the feed with their postDate, but when the releaseDate is equivalent to now I want the entry to return in the feed in order of appearance. Is this possible?

I've tried something like this:

craft.entries.section(['section1', 'section2']).order(['postDate DESC', 'releaseDate DESC'])

You could probably do this purely from Twig / a template, but I think it's going to end up being much easier from a plugin / PHP.

The general gist of it would be:

Grab all entries from section 1 ordered by postDate.
Grab all entries from section 2 ordered by releaseDate.
Create a new array and loop through 1 and 2 inserting via your custom sorting logic.
Return that new sorted array back to the template.


How can I export or move content from one Craft system into another? (with exactly the same configuration) Our client build tons of articles in our craft system instead of his. Both systems have the identical structure, in fact, his system is a exact copy of ours, which runs on a different server. How can we move all his content on  our  craft system into  his  system?

(As you may have noticed I am not a developer. I hope you get what my problem is here ... )

This can be a challenging situation. Craft content can be highly relational which makes it tricky to just export and re-import.

Scenario 1: If your database and your clients database were both equivalent before your client added the additional content, you may be able to just migrate over your clients database and replace your own database and get both back in sync.

To migrate the database would involve exporting the database from the clients environment (either via Settings->Tools->Backup Database or via a database admin tool) and reimporting it into your environment with a database admin tool.

Scenario 2: If both you and your client made changes to your respective databases (for example, you both added articles in each environment) and you need to preserve the new articles added in your environment while also migrating over the articles added in the clients environment you have two options.

First, you could just manually re-enter the articles in the right environment. As it sounds like your client added a lot of articles, that might be time consuming, but sometimes these types of mistakes take time to correct manually. It's often the best option.

In the case you added a few articles and your client added many articles, it may make more sense to add your articles to your clients database and then migrate the full database back to your servers as described in Scenario 1.

Your second option would be to migrate the content programmatically. This becomes a bit more technical so you'd probably need to get a front-end or back-end developer involved. And it will only make sense to go this route if the quantity of data you need to migrate is too much to just do manually.

A plugin like Sprout Import can help with this scenario. (Full disclosure, my team developed Sprout Import.) The steps would involve outputting the content from your clients database into a template into an importable JSON format. And then importing the content defined in the JSON file into your database.  The docs and examples within the plugin would go into more detail about the exact format you would need. For this answer, the more general point is that if your data was complex enough, a database migration plugin like Sprout Import could help provide a path toward a solution.


Cache Records Being Stored for Invalid Paths I'm trying to figure out why I have so many template cache records and when I look in my database templatecaches table I'm seeing a bunch of rows being stored for paths that don't exist. For example there are a bunch of Wordpress-like paths that presumably a bot tried to hit:

site:wp-admin
site:wp-content/themes/konzept/includes/uploadify/uploads/cache.php

Also a bunch of records with file extensions like .zip and .rar:

site:website.tar.gz
site:website.rar
site:webroot.zip

All of these paths should have thrown a 404 when the bogus URL was hit, and therefore should not have triggered a cache record to be created. Why am I getting all these junk cache records?

I had a {% cache %} tag around my global header template, which is pulled in by every template, including the 404 template. Apparently Craft doesn't care if the 404 template is being called or not, if it sees a {% cache %} tag it's caching that content.

The Wordpress and file extension paths are all from bots pinging the site trying to find a vulnerability, and every time those URLs threw a 404 a new cache record was created for the header.

So I set a variable in my 404 template and checked if it was set in my header template before caching:

404.html

{% extends 'layouts/_global' %}

{% set fourohfour = true %}

{% block pageBody %}

404, yo.

{% endblock %}

header.html (included in layouts/_global)

{% cache if fourohfour is not defined %}

        {% for page in craft.entries.section('Pages').level(1).all %}
            {{ page.title }}
        {% endfor %}

{% endcache %}


Get the Top Authors based on entry counts? I want to be able to display Top Authors based on their entry counts. Has anyone ever tried doing this?

Here's how I did something similar, now edited the code for you to work with users. The better approach would be to do this with a plugin, because, as you can imagine, the only way doing this with Twig will end up in lots of DB queries, so please make sure to use the cache tag with this:

{# Build an associative array of user IDs to the count of related entries #}
{% set userData = [] %}

{# Go through all users #}
{% for user in craft.users %}

    {# Get total number of entries this user is the author of #}
    {% set count = craft.entries.authorId(user.id).total() %}

    {# Save entries count and ALL user data we gonna use to our array #}
    {% set userData = userData|merge([{ entriesCount: count, userId: user.id, userName: user.name }]) %}

{% endfor %}

{# Sort, reverse-sort and pick top 10 elements of the array #}
{% set userData = userData|sort|reverse|slice(0, 10) %}

{# Print the top 10 list #}
{% for element in userData %}
    <li>{{ element.userName }} ({{ element.entriesCount }} entries)</li>
{% endfor %}


Structuring products variants I'm in the process of setting up a shop with Craft Commerce but I'm having some trouble organising the data structure of the products. The problem is that some products have both sizing and color options but I'm not sure how to enter these in the back-end. The most obvious way of doing this would be to make a product variant for each possible combination, like so:

Variant 1: color 1 - size 1
Variant 2: color 2 - size 1
Variant 3: color 1 - size 2
Variant 4: color 2 - size 2

I guess this would work but there will be a lot of variants. And if another one or two options are added in the future the amount of variants will grow exponentially.

Any insight on this would be much appreciated, thanks!

Usually when selling goods that come in different size and colors, a different SKU would exist for any size/color combination that you sell, thus a unique variant.

So, when that item becomes low in stock (Stock Keeping Unit), you can order more in.

In Craft Commerce we do not automatically generate every combination of variant based on the custom fields attached to a variant because most stores never sell every combination, and also, not every custom field is a unique aspect of that variant.

If more options are added to a product, there is nothing forcing you to make every combination as a variant, you could just use an option/custom field to mark a single variant as special (i.e Gold Edition).

If you are selling something that has a lot of options that customers are required to choose from each option, there is nothing stopping you from building a UI on the front end that makes selecting a variant possible based on all the options of all the custom fields. In that case your front end UI is building a query for a variant (craft.commerce.variants).

If you're not really selling a unique single SKU, but rather wanting a 'build a product' type product based on smaller SKUs, like building a bike, then you would need to build that yourself on the front end and/or with a plugin. That plugin would track what is in the cart, and which items still need to be added to complete the bike.

An alternative to that is building a custom purchasable with a plugin. This would allow you to expose the building of the purchasable element to the customer (with any options or custom logic you wish), and once the purchasable is saved by the customer, they can add it to the cart.

Lastly, if you don't want to go down the custom purchasable route, you can always just use generic options params when adding a product to the cart to allow any extra customer choices for the product. If the price is the same for the product regardless of the option chosen, this might be the easiest path if you have a huge amount of options and don't want to have a variant for each. The only thing you would want to do is validate the options for the current product.

The line item options feature mentioned above can also be combined with a small plugin that modifies the price of the line item based on the options choosen with the onPopulateLineItem event. The plugin would use the event something like this:

craft()->on('commerce_lineItems.onPopulateLineItem', function($event){

$purchasable= $event->params['purchasable'];
$lineItem = $event->params['lineItem'];

  if(isset($lineItem->options['giftWrapped']) && $lineItem->options['giftWrapped'] == 1){
    $lineItem->price = $lineItem->price + 1;
    $lineItem->salePrice = $lineItem->price;
    $lineItem->saleAmount = 0;
  }
}

Remembering that using the options method above will not be compatible with any stock tracking or inventory features now or in the future.

Let me know if you have any follow up questions and I can improve the answer above.


What needs updating when moving site from subdomain to root? I've just moved a site from a subdomain to the root.

So far I've updated the index.php file and also the settings in the control panel

System->General->SiteUrl

to reflect the new path of the craft directory and index.php relationship.

I can access the admin backend but the actual site gives the craft styled "Page not found."

Ok so on this site:

1) I forgot that I'd moved the templates folder outside the craft folder for easy access with this in the index.php

define('CRAFT_PLUGINS_PATH',realpath(dirname(__FILE__)./../plugins").'/');

2) Making sure the assets sources url are changed is essential to get the site working.


Use Matrix Block as footer My site is built on a single structure section called "pages". It includes 4 entryTypes.

One of the entryTypes is called "contact" and includes a MatrixBlock that displays fine on the contact page.

I'd like to make use of those entries (from the Matrix Block) in the site's footer (on all pages). Here is where I started:

{% for block in entry.MatrixFieldHandle.type('MatrixBlockHandle') %}
{{ block.title }} etc...
{% endfor %}

This only works on the contact page itself. How can I display a Matrix Field elsewhere than on its own page?

First you need to get the correct entry. Based on what you write, you can get it in any template by doing:

{% set contactPageEntry = craft.entries.section('pages').type('contact').first() %}

Then you should be able to loop over your matrix field like this:

{% for block in contactPageEntry.MatrixFieldHandle.type('MatrixBlockHandle') %}
    {{ block.title }} etc...
{% endfor %}


Categories field is losing its value when programmatically updating other fields’ values I have a webhook that fetches an entry, modifies one of its custom Text field values and then saves the entry. However there is also a required Categories field on the entry, and when I do this, I get a validation error because the Categories field is blank.

Here’s the relevant code:

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

$entry->setContentFromPost(array(
    'myTextField' => $newValue
));

// Let's see if it saves.
// Field validation should happen automatically
if (craft()->entries->saveEntry($entry))
{
    Craft::log('Entry updated (ID: '.$entry->id.').');
}
else
{
    Craft::log('Could not updated the entry (ID: '.$entry->id.').', LogLevel::Error);
}

How do I save the entry without losing its existing Categories field value?

Update - Craft 3

This has been fixed. The relational field will retain its original value when an entry is re-saved.

You no longer need to apply a workaround to get the expected behavior.

Original - Craft 2

This is, admittedly, super awkward right now. To prevent a relational field from losing its value when saving an entry, you have to explicitly set its value to null:

$entry->setContentFromPost(array(
    'myCategoriesField' => null
    // ...
));

However, this only works for relational fields that aren’t set to Required. If the field is set to Required, you have to just re-set the field to the same element IDs that were already selected:

$entry->setContentFromPost(array(
    'myCategoriesField' => $entry->myCategoriesField->ids()
    // ...
));

(FWIW, this will stop being awkward in Craft 3, as we done some major refactoring under the hood to fix issues like this.)


Missing buttons in settings after update to 2.5 beta After updating my dev install to 2.5 beta, I seem to be missing the action buttons for each tool category in the settings page, see attached image.

Anyone else experienced this?

This applies to all 5 categories ("update asset indexes", "clear caches" etc)

I tried to re-install the app folder (by completely replacing, not merging) but with no luck, so I'm beginning to think that this all comes down to some database issue...

Turns out that it was a badly written menu plugin, overriding styling of DOM elements outside of its own view scope. Sure it's related to Kyles suggestion above.


Ignore fields in search index I have a site with ~ 25000 entries, and around 130 custom fields.  I tried to rebuild the search index and it didn't complete after 8hours of running none stop.  Whilst I'm sure this can be blamed on the Server to some degree, I can see that my search index table has just short of 700000 rows, which I'm guessing takes a reasonable amount of time to create.

I was sure that I'd seen somewhere a way to hide/exclude fields from the search index, but looking now I can't find anyway to omit specific fields from the search index, which I would expect to reduce the load on the search indexer, there are only very few fields that I actually want the system to actually search by so I'm hoping there's a solution to this, I can reduce the load on the server and speed up the reindexing.

Thanks

As of Craft 3, there is a RegisterElementSearchableAttributesEvent event that a plugin can listen to to change modify what gets saved to the search index table when an element is saved.


HTML Email Template setting not working for reset password email I'm on the latest release of craft 3.x. I have the HTML Email Template setting set to a twig file in my templates folder. When sending a test email the template is used to send the test email, however when I call Craft::$app->users->sendPasswordResetEmail($user); from my plugin code, the template file is not used and the default system email template is used.

Is there something I'm missing here?

I've had issues in the past where Twig template errors would cause the HTML email template to fallback to using the default plain text system email template. It's definitely worth checking your web.log file for any errors when the sendPasswordResetEmail method is invoked, if you haven't already.


Using basic markup in field names I'm creating field names for electrical components and I'd like to use subscripts like this:

Vmin
Vmax
Vadj

Vmin
Vmax
Vadj

Note that this is just for the field name, not the handle. Is there a way to do this in Craft, or a plugin that provides this functionality?

Notes:

Unfortunately, Unicode characters won't work in this case because the latin characters I need are not all available as subscripts in Unicode.
I've tried the Relabel plugin without success.

Do you only need the HTML labels in the Control Panel?
I tried it for a short time and found this solution.

Install doublesecretagency/craft-cpjs.

Add this snippet into the editor of the plugin.

var $labelReplaceElements = document.querySelectorAll('#fields th > a, #fieldlayoutform .fld-field > span, .field > .heading > label[id]');
Array.from($labelReplaceElements).forEach($element => {
    $element.innerHTML = $element.innerText;
});

You should be careful, that only admins can change fields because this solution brings an XSS risk with it.

The disadvantage is that the labels are first displayed as HTML and only after the JavaScript has been executed are they displayed correctly.


What is the correct way to attribute FreeForm submissions to users? I'm using FreeForm and need to create a relationship between form submissions and the user that submitted them.

To accomplish this I created a hidden variable on my form called userId.  I use overridesValues to set the userId to the current user before I render the form.  This "feels" wrong to me .. is there a better way to do this?   Is this userId already stored in the freemform data model (making what I'm doing uncessesary)?

{% requireLogin %} {#check user is logged in #}
{{ craft.freeform.form("employeeForm", {
  overrideValues: {
    userId: currentUser.id,
  }
}).render() }}

Actually saw that earlier in the documents: https://docs.solspace.com/craft/freeform/v3/overview/relations.html#overview


Can I set shipping rates based on order value? I'm trying to set up shipping rates the client used prior to Craft Commerce. So for example, if the cart total dollar amount is:

0.01-$35.00 rate = $21
35.01- $170 rate = $39
170.01-$275 rate = $44, etc.

is this currently doable or will the client have to switch to a per item or weight-based shipping cost?

Within Commerce Settings -> Shipping Methods you can set within the rules of that method, the 'Minimum Order Total Price' and the 'Maximum Order Total Price' and an associated shipping cost, so yes, it is possible to recreate your clients existing setup.


How do I retain form input values after submitting error-ridden form containing matrix field inputs I'm looking for a way to keep matrix inputs populated with their original value after submitting a form containing errors. I'm already aware of the method of doing this for general inputs as shown here but I can't find an example of doing the same thing with a matrix field input. Here is my form currently without such functionality.

<form method="post" accept-charset="UTF-8">
    {{ getCsrfInput() }}
    <input type="hidden" name="action" value="entries/saveEntry">
    <input type="hidden" name="redirect" value="/">
    <input type="hidden" name="sectionId" value="5">
    <input type="hidden" name="enabled" value="1">

    // First goal
    <input type="hidden" name="fields[year][new1][type]" value="goal">
    <input type="hidden" name="fields[year][new1][enabled]" value="1">
    <input type="text" id="descriptionMeasurement" rows="4" name="fields[year][new1][fields][descriptionMeasurement]">
    <input class="dueDate" name="fields[year][new1][fields][dueDate][date]" type="text" placeholder="mm/dd/yyyy">
    <input id="weight" type="number" min="1" max="100" name="fields[year][new1][fields][weight]">

    // Second Goal
    <input type="hidden" name="fields[year][new2][type]" value="goal">
    <input type="hidden" name="fields[year][new2][enabled]" value="1">
    <input type="text" id="descriptionMeasurement" rows="4" name="fields[year][new2][fields][descriptionMeasurement]">
    <input class="dueDate" name="fields[year][new2][fields][dueDate][date]" type="text" placeholder="mm/dd/yyyy">
    <input id="weight" type="number" min="1" max="100" name="fields[year][new2][fields][weight]">

    <input type="submit" value="Save Goals">
</form>

As you've discovered, your Matrix Field behaves a bit differently than many other basic fields.  The Matrix Field, is composed of several Matrix Blocks and, like Entries themselves, Matrix Blocks are Element Types.  So, you can access your value on a MatrixBlockModel in the familiar way that you know how to do already on your EntryModel, however, first you need to get to it which requires an extra step from the EntryModel you get back when a form fails validation.

When your Form fails validation, an EntryModel gets returned to the page.  This EntryModel can't be accessed directly but its attributes and methods can be accessed.  From the example you referenced:

{# Our Title can be found on our EntryModel like so #}
{% if entry is defined %}{{ entry.title }}{% endif -%}

{# And errors can be accessed on our EntryModel too #}
{{ entry.getErrors('title') }}

We can access our Matrix Field like our Title field or any other custom field, but since it's not a simple field, it gives us back an ElementCriteriaModel and we can't output anything without navigating further into the model and accessing each block and the fields within each block directly.

{# This will thrown an error "Object of class Craft\ElementCriteriaModel could not be converted to string" #}
{{ entry.matrixFieldHandle }}

{# But we can access the first block just like we would a single Entry or Asset #}
{{ entry.matrixFieldHandle.first() }}

{# And a field within that block in the same way we would a field on the EntryModel #}
{{ entry.matrixFieldHandle.first().fieldHandle }}

{# And the errors of those fields within a block in the same way we would a field on the EntryModel #}
{{ entry.matrixFieldHandle.first().getError('fieldHandle') }}

To navigate all the retained values and errors for your Matrix Field, you'll need to loop through the MatrixBlockModels of your matrix entry.matrixFieldHandle and output the values and errors within your template.


Email Messages – If current email type is html or text I am adding a logo to the email message When someone creates an account: via Settings > Email > Messages, the image displays/renders correctly when viewed as "HTML / Rich Text" within mail clients, however; when the email is viewed as plain text, the html for the image is still output (as plain text) rather than being omitted.

Is it possible to conditionally render output from within the message depending on if the type of email content currently being parsed by Craft is for text/html or text/plain?

Desired usage:

{% if html %}
    This is the text/html part of the email message.
{% elseif plain %}
    This is the text/plain part of the email message.
{% endif %}

For reference the message content is currently:

<img src="http://www.example.org/admin/resources/rebrand/logo/logo.svg" width="200">

Dear X,

As a registered user...

Aside: This assumes that Craft processes the email message twice within the same process, once as plain and again as html. If this is not the case then this would likely be unachievable. I do not have an HTML Email Template set within Settings > Email.

There currently isn't a native way to do what you're looking for, but you should consider voting for this feature request which would make it natively possible.

As a workaround, you could write a plugin that listened to the onBeforeSendEmail event, checked to make sure it's the email you're looking for, then modify the body property of the EmailModel however you wish.


Handling Endless Recursion in saveEntry? I'm trying to update the expiry field—based on an existing field (in this case classDate)—as soon as an entry is saved.

Based on this answer, the clever recursion code in carlcs' Auto Expire plugin, I was able to get it working with this code:

public function init()
{

craft()->on('entries.saveEntry', function(Event $event) {
    $entry = $event->params['entry'];

    if ($entry->section->name == "Classes")
    {
        $entry->expiryDate = $entry->classDate;

        static $recursionLevel = 0;

        if ($recursionLevel == 0)
         {
            $recursionLevel++;
            $saved =   craft()->entries->saveEntry($entry);
         }

    }

});
}

But that seems a little dirty because saveEntry is going to get called again.

Is there a better way to handle the recursion? Bob's answer here leads me to believe you can call craft()->elements->saveElement which won't raise the saveEntry event again but I couldn't get that to work.

What am I missing? Seems too easy...

I'm not sure if it's any less dirty, but another way you could do it would be:

public function init()
{
    craft()->on('entries.saveEntry', function(Event $event)
    {
        $entry = $event->params['entry'];

        if ($entry->section->name == "Classes")
        {
            craft()->db->createCommand()->update(
                'entries',
                array('expiryDate' => DateTimeHelper::formatTimeForDb($entry->classDate)),
                array('id' => $entry->id)
            );
        }
    });
}


Your usual setup for blogpost matrix field What is your usual setup for blogpost/article matrix field?

I can think of:

Header
Richfield
Quote (author+text)
Image(asset, position, description)

Something like this: Creating a Content Builder in Craft CMS


How do I dynamically define Asset Volumes' Base URLs for different Sites? There must be a simple answer for this but despite fumbling around with it for quite a while and searching the web for answers I am stymied. Perhaps I'm approaching it in the wrong way. I'm feeling like a beginner and I may not be using the right terminology here.

Here is a similar question without an accepted answer: How to setup assets in multi site where each site should have its own assets?

I have two sites, Site A and Site B, with their own assets folder. They are separate sites that will not share assets at all and it would be ideal to keep them completely separate (just as the templates are separated in folders with each Site's handle).

/site-a
-- /site-a-assets
/site-b
-- /site-b-assets

I've seen that @web should not be used for Asset Volumes' Base URL when using multi-site, but I haven't been able to understand how to set up config/default.php to accommodate different addresses for different sites AND different environments (e.g. dev, production). I've seen mention of using aliases but don't understand yet how to make them work with multiple sites to have different values for the same alias depending on which site you're viewing and in which environment.

I have this for my local siteUrl in config/default.php inside of environment settings:

'siteUrl' => [
  'site-a' => 'https://org.site-a',
  'site-b' => 'https://org.site-b',
]

It feels like I'm close but I can't get the pieces put together.

Any pointers you can give will be greatly appreciated!

As happens, I had to "give up" and ask for help in order to figure out a solution myself.

My issue was I was trying to dynamically set @web and @webroot for the sake of Asset Volumes. That would presumably mess up other things that use those aliases.

A way to make it work is to create new aliases per-site for Asset Volumes. If someone else wants to do this, here is a way.

In config/general.php create aliases in the appropriate spot (e.g. in the * global, dev or whatever environment section you want) replacing <site-handle> with the handle of your site (set in the Settings > Sites panel):

'aliases' => [
  '@<site-handle>AssetsBaseUrl' => 'https://site-a.dev',
  '@<site-handle>AssetsBasePath' => '/Users/kirkroberts/Work/Codebases/site-a.com/online/public_html',
]

Now for the Asset Volumes for that particular site you can use @<site-handle>AssetsBaseUrl or @<site-handle>AssetsBaseUrl to start your Base URL and File System Path fields, respectively. (replacing <site-handle> with that Site's handle)

This should enable assets to be stored in whatever domain/folder you specify.

Of course, the aliases will need to be set manually for each environment in config/general.php, which is kind of a pain but not a huge deal.

Maybe that will help someone get it done or find an even better way to do it.


Get and Set existing matrix field data in a plugin I have a plugin that wants to set a value for an entries existing matrix field, from a form on the font-end. Think of it like:

Movies (matrix field)
  Movie (matrix block)
    Name (plain text)
    votes (plain text)
  Movie (matrix block)
    Name (plain text)
    votes (plain text)

I want to increment the number of votes when a user clicks a button for a particular movie.

I've tried all sorts of methods, but being pretty novice at php I just can't seem to grasp how Craft is storing everything. I just keep getting back an array of ALL fields on the site, rather than the specific ones I'm searching for.

All existing SE questions seem to relate to adding new rows, I just want to edit an existing one.

Normal fields are (from existing code) along the lines of

$currentVotes = $movie->getFieldValue('votes');
$movie->setFieldValue('votes', $currentVotes + 1);

Which seems rather simple. What is the Matrix version of that?

Since Matrix blocks are elements as well it's the same

Your form could be

{% set movies = entry.movies.all() %}
{% for movie in movies %}
    <form>
        <input type="hidden" value="plugin/controller/function" name="action">
        {{ csrfInput() }}
        <input type="hidden" value="{{ movie.id }}" name="id">
        <input type="submit" value="Vote this movie">
    </form>
{% endfor %}

And your controller

public function actionSave(){
    $id = Craft::$app->getRequest()->getRequiredBodyParam('id');
    $matrixBlock = Craft::$app->getMatrix()->getBlockById($id);
    if($matrixBlock === null){
        throw new ElementNotFoundException('Could not find Block with ID ' . $id);
    }

    $matrixBlock->setFieldValue('votes', $matrixBlock->getFieldValue('votes')+1);
    if(!Craft::$app->getElements()->saveElement($matrixBlock)){
        // handle an error
    }

    return $this->redirectToPostedUrl();
}


Checking value of customer order field within Plugin I am trying to check the value of a checkbox as part of my checkout process. I have the following function at the moment.

public function init()
{
    craft()->on('commerce_orders.onBeforeOrderComplete', function (Event $event) {
        $order = $event->params['order'];
        echo '<pre>';
        print_r($order->signupForNewsletter);
            if ($order->signupForNewsletter == 'joinMailingList') {
                echo "I've opted in";
            } else {
                echo 'Not matched';
            }
        echo '</pre>';
        exit;
    });
}

But whether the checkbox is ticked or not I am only get the 'Not matched' statement, so my if statement is always false. I have tried several things, but cannot see how to resolve this.
I think perhaps because the signupForNewsletter is a checkbox in the custom order fields, so it is an array rather than an object.

Here is the print_r results of this code;

    Craft\MultiOptionsFieldData Object
(
    [_options:Craft\MultiOptionsFieldData:private] => Array
        (
            [0] => Craft\OptionData Object
                (
                    [label] => Join Mailing List
                    [value] => joinMailingList
                    [selected] => 1
                )

        )

    [storage:ArrayObject:private] => Array
        (
        )

    )
    Not matched

The checkbox was ticked in this instance as you can see in the value field.

What you're probably looking for is:

if ($order->signupForNewsletter->contains('joinMailingList'))
{
    echo "I've opted in";
}
else
{
    echo 'Not matched';
}


How to prevent "Failed" errors in job scheduler in Craft CMS 3? I have an instance of BaseJob that I occasionally queue up that involves reading an XML file from another server. However, requests to that server are rate-limited, and so occasionally it will fail with an error.

I don't mind this in terms of the logic of how it works - I just request another version a while later. However, every time it fails, it adds a message into the "Failed" tab in the CMS.

Is there any way to prevent/override this? I have tried suppressing errors in the job, I've tried wrapping the whole thing within a try/catch block, but nothing seems to work - whenever it fails, it is logged in the CMS.

Thanks.

I currently cannot see any way of achieving this as the only appropriate event that exists in the yii\queue\Queue class, afterError, is called before the job is failed in the handleError method of craft\queue\Queue. So your best bet would be to convince P&T that there is a valid use case for adding an extra event that will let you prevent the fail error from appearing.


Is it possible to add an entry value to the entry URL Format? I try to compose an entry URL for artists pages with first name and last name.

Now:

artists/{slug} = artists/{{ entry.title }}
{slug} = {{ entry.title }} = Artist Last Name

What I want to add at the end of the URL (Plain Text field):

{{ entry.artistFirstName }} = Artist First Name

What I try to get, but which is not working:

artists/{slug}-{{ entry.artistFirstName }}

Is it possible? how? And how can I add a conditional to check if entry.artistFirstName is defined?

Thanks!

For this a good approach is to use the excellent Dynamic Routing system in Craft.

Solution #1

First create your section "Artists":

After that go to "Settings" -> "Routes" and add this Dynamic Route:

When this is done all URL requests made to "/artists/-whatever-" will be routed to the "artists/_entry" template. Now you can create your link like this:

Artist Name

Your URL might now look like this rendered:

Lady Gaga

In your "_entry" template you can now fetch your entry using this code:

{% set entry = craft.entries.section('artists').slug(craft.request.getSegment(2)).first() %}
Artist Name: {{ entry.title }}

Solution #2

If you instead want the URL to be "/artists/firstname-slug" you can alter the Dynamic Route slightly and use the Twig filter "split" to get the slug from the URL.

First change the Dynamic Route to:

artists/*

Then update your template "_entry" with the following code:

{% set segments = craft.request.getSegment(2)|split('-') %}
{% set entry = craft.entries.section('pages').slug(segments[1]).first() %}
Title: {{ entry.title }}

Your URL can now be "/artists/{{ entry.firstNameField }}-{{ entry.slug }}" and the template code will take care of the rest.


How to make next/prev links for entries circular/endless I've got a basic portfolio website which has "prev project" and "next project" buttons on each entry detail page. I'd like for when you reach the last project for the "next project" button to simply link back to the first instead of simply not appearing at all.

Any idea how this could be done? Thanks so much in advance.

 {% if nextEntry %}Prev Project{% endif %}
 {% if prevEntry %}Next Project{% endif %}

I'm going to assume your project section is named projects, if it's not, replace it with you section name.

Try this:

{% if nextEntry %}
    {% set nextEntryUrl = nextEntry.url %}
{% else %}
    {% set firstEntry = craft.entries.section('projects').first() %}
    {% set nextEntryUrl = firstEntry.url %}
{% endif %}

{% if prevEntry %}
    {% set prevEntryUrl = prevEntry.url %}
{% else %}
    {% set lastEntry = craft.entries.section('projects').last() %}
    {% set prevEntryUrl = lastEntry.url %}
{% endif %}

Prev Project
Next Project


Plugin DB migration add "not null" column I set up a quick and dirty migration to add two columns to two tables. These are to be basically boolean 1/0 switch fields. I'm setting up my migration file as follows, note the 'not null' in both column types:

<?php
namespace Craft;

class m171020_153541_grnplatform_AutoRenewUpdate extends BaseMigration
{
    public function safeUp()
    {

        $this->addColumn('grnplatform_payment_cards', 'expireNotified', "tinyint(1) unsigned NOT NULL DEFAULT '0'");
        $this->addColumn('grnplatform_subscriptions', 'failedRenew', "tinyint(1) unsigned NOT NULL DEFAULT '0'");

        return true;
    }

    public function safeDown() {

        $this->dropColumn('grnplatform_payment_cards', 'expireNotified');
        $this->dropColumn('grnplatform_subscriptions', 'failedRenew');

        return true;
    }
}

However after the migration runs, my show create table command yields this:

CREATE TABLE `craft_grnplatform_subscriptions` (
  `id` int(11) NOT NULL,
  `userId` int(11) NOT NULL,
  `active` tinyint(1) unsigned NOT NULL DEFAULT '1',
  `renewalPeriod` enum('monthly','quarterly','yearly') COLLATE utf8_unicode_ci DEFAULT NULL,
  `autoRenew` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `lastRenewal` date NOT NULL,
  `nextRenewal` date NOT NULL,
  `failedRenew` tinyint(1) unsigned DEFAULT '0',
  `dateCreated` datetime NOT NULL,
  `dateUpdated` datetime NOT NULL,
  `uid` char(36) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `craft_grnplatform_subscriptions_userId_unq_idx` (`userId`),
  KEY `craft_grnplatform_subscriptions_nextRenewal_idx` (`nextRenewal`),
  CONSTRAINT `FK_craft_grnplatform_subscriptions_craft_grnplatform_vendor_data` FOREIGN KEY (`userId`) REFERENCES `craft_grnplatform_vendor_data` (`userId`) ON DELETE CASCADE,
  CONSTRAINT `craft_grnplatform_subscriptions_id_fk` FOREIGN KEY (`id`) REFERENCES `craft_elements` (`id`) ON DELETE CASCADE,
  CONSTRAINT `craft_grnplatform_subscriptions_userId_fk` FOREIGN KEY (`userId`) REFERENCES `craft_users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

Notice how the "not null" is missing from the column declaration for 'failedRenew`...

How can get set the NOT NULL value on a column I'm adding from a migration?

This looks like it could be a Yii issue, if a rather occluded one.

Here's how it is in another framework: MySQL TINYINT(1) columns are always interpreted as booleans #3506

And here is a pointedly uncertain link with link, on a quite confused Yii discussion circling around this: It is already documented that tinyint(1) would be treated as boolean

I don't think it is actually doc'd so -- actually it's the other way around. But doc may not tell all.

I would suggest making the def tinyint(2) and see if it cures...good fortune...


How to duplicate a matrix field programatically in Craft 3.1? Is there a "Craft" way of handling this in 3.1?

Solutions to this question on SE are based on older versions of Craft which no longer work.

Custom fields can be duplicated by doing:

$newEntry->customFieldHandle = $masterEntry->customFieldHandle;

I've tested this with Plain Text, Number, Dropdown, Date/Time, Entries fields and it works fine.

I tried this with a matrix field (not expecting it to work, as I appreciate Matrix fields are handled differently). The matrix content and fields are copied across when a newEntry is generated, but is deleted from the masterEntry.

Example module:

protected static function createNewEntry()
{
    Event::on(
        Elements::class,
        Elements::EVENT_AFTER_SAVE_ELEMENT,
        function(ElementEvent $event) {

            // Ignore any element that is not an entry
            if (!($event->element instanceof Entry)) {
                return;
            }

            // Ignore entries that are new because they have no id
            if ($event->isNew) {
                return;
            }

            // Fetch current entry and assign siteid
            $masterEntry = Craft::$app->entries->getEntryById($event->element->id, $event->element->siteId);

            // Basic Craft fields...
            $newEntry = new Entry();
            $newEntry->sectionId = $masterEntry->sectionId;
            // more craft fields here...

            // Custom fields
            $newEntry->contentLongtitle = $masterEntry->contentLongtitle;
            // more craft custom fields here...

            // Matrix field
            // Doing this copies the matrix content to newEntry
            // but deletes it from masterEntry
            $newEntry->matrixSchedule = $masterEntry->matrixSchedule;

            $success = Craft::$app->elements->saveElement($newEntry);
            // Handle success/failure logic here...

        }
    );
}

I've just found this: https://craftcms.stackexchange.com/a/25961/9344.

After reading Robins comments, I can see where I was going wrong.

My working example code now looks like this:

protected static function createNewEntry()
{
    Event::on(
        Elements::class,
        Elements::EVENT_AFTER_SAVE_ELEMENT,
        function(ElementEvent $event) {

            // Ignore any element that is not an entry
            if (!($event->element instanceof Entry)) {
                return;
            }

            // Ignore entries that are new because they have no id
            if ($event->isNew) {
                return;
            }

            // Fetch current entry and assign siteid
            $masterEntry = Craft::$app->entries->getEntryById($event->element->id, $event->element->siteId);

            // Fetch matrix fields by handle
            $masterEntryMatrix = Craft::$app->getFields()->getFieldByHandle('matrixSchedule');
            // get the existing matrixSchedule value
            $masterEntryMatrixQuery = $masterEntry->getFieldValue('matrixSchedule');
            // serialize the data in order to get an array
            $masterEntryMatrixSerialized = $masterEntryMatrix->serializeValue($masterEntryMatrixQuery, $masterEntry);

            // Basic Craft fields...
            $newEntry = new Entry();
            $newEntry->sectionId = $masterEntry->sectionId;
            // more craft fields here...

            // Custom fields
            $newEntry->contentLongtitle = $masterEntry->contentLongtitle;
            // more craft custom fields here...

            // Matrix field
            $newEntry->setFieldValue('matrixSchedule', $masterEntryMatrixSerialized);

            $success = Craft::$app->elements->saveElement($newEntry);
            // Handle success/failure logic here...

        }
    );
}


Get entries filtered by date fields, one of which might be empty (and paginate them) I have an 'whatsOn' structure whose entries include two date fields: 'dateStart' and 'dateEnd'. The end date will be empty if the event is a one-dayer. I want to get all future events using the dateEnd (if not empty) or dateStart if not. So basically, all events that haven't ended yet. How do I add the 'dateEnd' bit to the following query:

{% set allEvents = craft.entries({
    section: 'whatsOn',
    limit: 20,
    level: 2,
    order: 'dateStart',
    dateStart: '>= ' ~ now
}) %}

Crucially, I'll be paginating them so I need this to return an ElementCriteriaModel, I can't just do the check inside a loop.

EDIT: I just noticed that date fields are just empty (not null/undefined) if you don't select or enter a date. Amended question to reflect this.

There's no way to simply add it to that query you have. But you can use the following workaround.

You first do all the logic within such conditionals you actually wanted to avoid, but instead of getting the ElementModels directly, you do an extra step and get / collect the entries' IDs using the ids() method.

When you're ready, use these IDs with the id property in a second ElementCriteriaModel. This you can paginate like you want!

Edit:Here's some example code!

{% set cutoffDate = 'now'|date_modify('- ' ~ '7' ~ ' days') %}
{% set events = craft.entries.section('events').order('dateStart desc') %}

{% set eventIds = [] %}
{% for event in events if (event.dateEnd ? event.dateEnd : event.dateStart) >= cutoffDate %}
    {% set eventIds = eventIds|merge([event.id]) %}
{% endfor %}

{% set filteredEvents = craft.entries.id(eventIds).fixedOrder('true') %}


Select dropdown linking to Matrix blocks I need a little bit of assitance. I have a matrix block that outputs floorplans diagrams and it's specification per floor in a property (see screenshot attached below.

I've got the matrix fields outputting correctly on the page, but I need to get it to only output a single floor at a time, and not all floors, and then let the user click on a select dropdown to change to the floor level that they want to view.

Here's the current markup:

Hopefully someone can help to get it working?

<form class="form" action="{{ entry.url }}" method="get">

      <select id="development1" class="form-control" name="floor" onchange="this.form.submit()">
        <option value="">Select Floor</option>
        {% for block in entry.floorplans %}
        <option value="{{ block.floorplanlevel }}">{{ block.floorplanlevel.label }}</option>
        {% endfor %}
      </select>

</form>

{# Start Matrix Block #}
{% for block in entry.floorplans %} {# Main Field Handle #}
{% switch block.type %}

{% case "floorplan" %} {# Matrix Block Type Handle #}
......

Two options:

Physically output all the floorplans in the markup {% for block in entry.matrixHandle%} then hide them using CSS, and only display the one from the dropdown using JavaScript. Something like $('#dropdown-id).on('change', function(){});
Change the field from a Matrix so that each floorplan is a separate Entry and you visit each one with an href="{{ entry.url }}.


Recommended High Availablity Architecture We are looking to use Craft CMS for an upcoming web project, are there any documented architectures or topologies for deploying into a production setup. This would be for high availablity across multiple regions including both front end assets and databases.

We are likely to consider AWS or Azure for hosting. If there is nothing specific about Craft architectures then would a standard PHP site topology be appropriate?

Thanks

Luke

There's nothing particularly necessary for Craft here, other than what would be needed for a standard PHP application.

https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/php-ha-tutorial.html

And some potentially useful Craft related information around this topic here:

Load balancing nodes configuration (3 Craft nodes behind F5 Big-IP)

Craft admin login infinite redirect in cluster


Possible to reflect changes to a plugin's composer.json without uninstall/reinstall? When working on a plugin recently, I decided to add a CP section:

composer.json

{
    ...
    "extra":
        ...
        "hasCpSection": true
    ...
}

The only way that I was able to have this change reflected in Craft was to uninstall/reinstall the plugin. Is there an alternate way (e.g. CLI) to do so?

You can uninstall the plugin via CLI but I guess that's not really what you are after. The only other way is to change the auto generated config file in vendor/craftcms/plugins.php and change the parameter for your plugin there. But you can't ever change this attribute for others that downloaded your plugin remotely without doing things you should not do.


List elements grouped by multiple dates The company whose website I'm building host events, and for each of these events, it is only possible to purchase tickets on certain days, but for each event, there can be more than 1 such day.

They want a view which shows:

Event A

Ticket sale days: day 1, day 2

Event B

Ticket sale days: day 1

Event C

Ticket sale days: day 2

And another view which shows:

Ticket sale day 1

Event A
Event B

Ticket sale day 2

Event A
Event C

I'm currently using a single-column table field for the ticket sale days which allows me to add as many dates as I wish. With this, I can implement the first view above really easily by looping over the rows in the table.

However, the second view has me stumped since I am unable to group the entries by the rows in the table field. Note that I want to keep this data within a single 'events' section.

You could loop through all your events, and create a date-keyed hash with the ids of the entries - assuming your  (e.g

    {% set saleDays = {} %}
    {% for entry in events %}
       {% for row in table %}
          {% if attribute(saleDays, row.date|date(mdY)) is defined %}
               {% set saleDays = saleDays|merge({
                  (row.date|date(mdY)): attribute(saleDays, row.date|date(mdY)) | merge([entry.id])
               }) %}
          {% else %}
               {% set saleDays = saleDays|merge({ (row.date|date(mdY)): [entry.id]})
          {% endif %}
     {% endfor %}

This would result in a hash, whose keys were dates of ticket sales, and whose values were arrays of entry id's (which could then be queried), or you could set the values inside the array to the entry models themselves.

You would then need to sort this hash to get the dates in order (think you'd need to write a custom extension), and then you could loop through it.

But to be honest, by that point you'd be better off just writing a module and doing the whole thing in php where it'd be easier.


Is there a fallback when a Twig extension is not enabled? So I have a plugin with a Twig Extension that does it's thing. If I were to disable my plugin I will get the expected error:

  The "extensionName" extension is not enabled.

Whenever I'm referring to anything to do with my plugin, I rely on the usual "is defined", "is not empty", "|default()", etc... options as appropriate fallbacks/checks incase my plugin fails.

However, short of doing a full check on wether my plugin is installed everytime, there doesn't seem to be an elegant solution for checking extensions are enabled until it's too late (and the page fails to load).

Any ideas on a workaround?

I can't think of a native way to do this, but Twig does have a hasExtension method, which you could make use of in a Craft plugin.  But if you wrote a plugin to utilize it (similar to this) obviously that wouldn't help if the plugin was disabled or uninstalled.

Alternatively, you can add this as a feature request to add to Craft natively. If you go that route, be sure and go into details on your specific use-case as I'm not sure I'm following it 100% here.


How to setup the structure in Craft for a Property Development site I'm building I've been designing and building a property developers website for the last 9 months (client delayed building it due to economic reasons), but now we are at the point where it's ready to be built into Craft, as I've finished the HTML/CSS/JS build after the 3 months of hard work.

But due to the client budget I'm attempting to do the Craft build myself (I'm a designer with good CSS etc, but this is my first Craft build), so I'd appreciate a bit of advice on the best approach, as I've tried a number of different approaches so far, but am really struggling, as the development section of the site has multiple levels, and I need to set it up in a way that is going to be idiot proof for the client to be able to add new developments, and all the associated properties with each development.

The Development section structure is as follows:

**All Developments** (listing page per development)

--**County** (for example "Berkshire") - A similar listing page for properties only in Berkshire.

----**Development Overview** - Information about the whole development)

------**Available Properties** - A listing page for all of the individual plots in the development

--------**Plot Page** - individual plot information including floorplans, specifications, images etc

These are all linked via breadcrumbs as well.

There are around 15 developments currently, and some have 50+ plots in them. So there could be hundreds of plot pages.

So the client is going to need to be able to create the Development Overview page in the CMS each time, and all the individual Plot pages per development, and also add the Available Properties page once there are plots, and each development will need to be related to the county they are in, and pulled into the listing page at the top level too.

So would appreciate some experienced advice on the simplest approach that is going to be easy for the client to manage.

And also any other recommended resources for learning as I've already been through all of the mijingo video courses, and still struggling as this developers site is much more complicated than any of the tutorial sites, which only have basic relationships.

One final thought I had, is would this be easier to do with Craft commerce? As the development section is very much like a ecommerce site in structure, but without the need for the payment stuff. Or can standard Craft handle it?

Kind regards

I think I follow where you are going. It sounds like you have a few levels so you will indeed be working with a structure (instead of a channel or single).

I'm not sure what "All Developments" means - is that "show me every single entry in this section"?

There are most likely a handful of ways you could go about doing this, here is one way that I would try.

1.) I would make your "County" a category. Call the group "Counties" and then create each county you need (ex: Berkshire). By creating your counties as categories, you can assign (relate) anything back to it. This will allow you to do things like: Get all properties from Berkshire Country. It will be up to you to decide where this relationship lives though. I would put it on the actual property though, that way you have access to it at any level.

2.) Create a few entry types:
        1) Development
        2) Property
        3) Plot
     By doing this, we can have a clean authoring experience by having each entry type have it's own fields. Meaning your Properties entry won't have inputs for the images like your Plots entry would. It just keeps things a lot cleaner for the author.

3.) Build out the structure for each Development. This is what that might look like. Let's say you have a section (of type structure) called "Developments"

 - Entry: (Type Development)
     |- Entry: (Type Property) <-- This is where I would assign County
         |- Entry: (Type Plot)

 - Entry: (Type Development)
     |- Entry: (Type Property)
         |- Entry: (Type Plot)

By setting things up this way, now we can easily do things like, "Get all plots from foo property" or, "get all properties from bar development".

Or, another completely different way would be create categories for each County, Development and Property. You would then have a single section called "Plots" (of type channel). Each entry would then have category inputs to relate the plot back to the correct County and property.

The problem with this approach is that it isn't quite as straight forward to create higher-level things like Properties. I've found that having people go back and forth from categories and entires can be confusing.

Hope this helps get you going!


Checking if a value is in an array of IDS - using a filter? Is there an easier way to achieve this?
Perhaps using a |filter function?
{% set variantIds = [] %}
{% for variant in product.variants %}
  {% set variantId = variant.id %}
  {% set variantIds = variantIds|merge([variant.id]) %}
{% endfor %}

{% if wheelSpacer in variantIds %}
  this spacer
{% endif %}

You could use filter to get only the variant that matches the wheelSpacer variable. This will return an array, so you can use first to get the first matching variant (since presumably product.variants won't contain duplicate variants). This has the added benefit of getting you the matching variant directly, instead of it's ID:
{% set variant = product.variants|filter(variant => variant.id == wheelSpacer)|first %}
{% if variant %}
    {# ... #}
{% endif %}

As a sidenote, if your variants field is an Entries field, you'll want to use product.variants.all() instead.


Is there any alternative for the Redactor plugin in Craft 3? The Redactor plugin not working well for me. So I am in need of another good plugin for an editor that supports HTML, image, video, etc.

Please advise me about this.

Rakhi, based on your comment on the question, you're missing the video and imagemanager in the plugins array.

Example:

"plugins": [
    "alignment",
    "fontcolor",
    "video",
    "imagemanager",
    "table",
    "fullscreen"
],

Read more about it Here and Here


How can I add custom field to order by on the admin CP in Craft 3? I would like to add a custom field for order by on the drop down menu on admin CP. How can I do it, is there any event for this in Craft 3?

It is a plugin related question, we are using custom plugin to customize our application

You can take a look at this question Set custom sort order for index pages in the CP? for Craft 2 and see updating Plugins for Craft 3 in the docs

// Old:
public function modifyEntrySortableAttributes(&$attributes)
{
    $attributes['id'] = Craft::t('ID');
}

// New:
use craft\base\Element;
use craft\elements\Entry;
use craft\events\RegisterElementSortOptionsEvent;
use yii\base\Event;

Event::on(Entry::class, Element::EVENT_REGISTER_SORT_OPTIONS, function(RegisterElementSortOptionsEvent $event) {
    $event->sortOptions['id'] = \Craft::t('app', 'ID');
});

Keep in mind your field must be sortable via SQL


Disabling unused PHP functions and Craft The php.ini file has an option called disable_functions. You can add unsafe functions to it so that they cannot be run. Does anyone have any guidelines for a good setup with Craft for this. I realize that this is somewhat sort of subjective, but I'm sure there are some features that could without a doubt be turned off that Craft will never use.

OWASP suggests the below settings. Any thoughts on this?

  disable_functions = "system, exec, shell_exec, passthru, phpinfo, show_source, popen, proc_open, fopen_with_path, dbmopen, dbase_open, putenv, move_uploaded_file, chdir, mkdir, rmdir, chmod, rename, filepro, filepro_rowcount, filepro_retrieve, posix_mkfifo"

I'm not aware of a definitive list, but Craft have problems running with some of the methods in your list disabled.

phpinfo, move_uploaded_file, chdir, mkdir, rmdir, chmod, rename

Any plugins you have installed might have additional function requirements as well.


where command sql query not working My problem is that I can't update the specific $userId. have the values I've selected. I also get an error:

  Call to a member function where() on integer

I just started to learn Craft. It can be simple for you but not for me.

public function updateUserName($userId, $updateUser)
{

    $update = craft()->db->createCommand()->update('contest_user', array(
        'name' => $updateUser))->where('id', $userId);

    return $update;
}

When you take a look at the documentation the interface for the update function is the following.

public integer update(string $table, array $columns, mixed $conditions='', array $params=array ())

Thus your code should look like:

public function updateUserName($userId, $updateUser)
{
    $update = craft()->db->createCommand()->update(
        'contest_user',
        array('name' => $updateUser),
        'id=:id',
        array(':id', $userId)
    );

    return $update;
}

You can see a working example here:

// build and execute the following SQL:
// UPDATE `tbl_user` SET `name`=:name WHERE id=:id
$command->update('tbl_user', array(
    'name'=>'Tester',
), 'id=:id', array(':id'=>1));


How to create a new subfolder to an asset with plugin I have a plugin that uploads and associates files/assets to users and entries. I would like it to store these files under a user ID subfolder of the asset folder. I know how to do this within the controlpanel, working with assets there. But how would I create and register subfolders within my plugin? If I just create the folder with my php script, it doesnt show up in the assets folder until i do a "Update Asset Indexes".

The way my plugin works now, It creates an asset with the craft()->assets->insertFileByLocalPath, and then associates the fileId with my entry after its created/uploaded. Maybe this is not the right way to do it?

Any help much appreciated.

I’ve had the problem with storeFolder() that it doesn’t create a folder that is accessible via the control panel or the file system.

I find createFolder() much easier to use and it did what I expected:

$response = craft()->assets->createFolder($parentFolderId, $folderName);
$folderId = $response->getResponseData()['folderId'];


Restrict control panel by ip address? Has anyone ever limited access to Craft's back-end via ip address? What paths should I limit if I'm wanting to ensure only certain ip addresses can post updates? Thanks!

If you're looking to only allow certain IP addresses to access the entire control panel, you're best bet would be to whitelist them via an .htaccess file.

Something like:

<Limit GET POST>
    order deny,allow
    deny from all
    allow from 1.2.3.4
</Limit>

If you're looking to just allow certain Craft-specific actions by IP address, then you'd have to write a plugin.  For example, if you only want certain IP addresses to be able to create an entry, the plugin would listen for the onSaveEntry event, make sure it's a new entry, check if the user has createEntry permissions, if so grab their IP address and make sure it falls in your whitelist.

Just be aware that IP address detection can be spoofed, so you shouldn't be relying on this for any critically sensitive data.