Omitting current entry from list of entries generated from a relationship

You probably kind of confuse your "apprenticeships categories" with your "apprenticeships" section. Try something like this instead:

{% set relatedEntries = craft.entries({
    section: apprenticeships,
    id: 'not ' ~ entry.id,
    relatedTo: {
        targetElement: entry.apprenticeshipsCatsField
    }
}) %}

You now want to order them by your categories? Have a look at my answer I just posted here: "How do I display entries, grouped by distinct categories...".


Embedding a podcast player onto my Craft site

Not having access to the templates could make this hard to troubleshoot. Are you pasting into the HTML editor of a Redactor field? If so try editing the field's advanced options. Uncheck Remove inline styles, Remove empty tags, and Purify HTML.


Marcus, I think you can use anything as an url for a user's page. In your case, I'd make a dynamic route like:

user/*

and assign it a template, for instance:

user/_entry

Within that template, all you'd have to do is lookup the user by the match found in the url. The variable matches contains the first-last part (the * part of your route). With that, the user should be easy to find:

{% set names = matches[1] | split('-') %}
{% set user = craft.users.firstName(names[0]).lastName(names[1]).first() %}

{% if user %}
    // Do with it what you want
{% endif %}

You'd have to be careful, however, that no two identical first- and last name combinations exists. The advantage of using the username is that it's always unique.


Craft requires Imagick to do any SVG transforms, so that fact that it is working on your MAMP install without it is the only weird thing I see here. :)

Craft will attempt to use Imagick if it is available on the server and fallback to GD otherwise or you can explicitly tell it which one to use via the imageDriver config setting.

As to why it's all black on production, make sure you're using a recent version of both the Imagick PHP extension as well as the underlying Imagick library.


Markdown filter, how to add HTML attributes?

This is what I currently do to add a class Bibliography to all generated <ul> tags from my articleBibliography field. Because there's never other content in this field, I'm save to replace them all.

{{ entry.articleBibliography|markdown|replace({'<ul>': '<ul class="Bibliography">'})|raw }}

--

Update:
You can also use the awesome Retcon plugin for this, it’s using DomDocument internally so the result should be more reliable.

{{ entry.articleBibliography|markdown|retconAttr('ul', {'class': 'Bibliography'}) }}


You won't be able to collect all information and take payment using one form/action. The customer information is added to the cart using commerce/cart/updateCart and payment is processed using commerce/payments/pay.

To show the available shipping methods to a customer, Craft Commerce needs to know the shipping address to match against the rules you've set up. For this reason you'll need to split your checkout into separate forms.

I have built a "one-page" checkout with 'Customer Details', 'Delivery Method' and 'Payment' sections. Each section has a 'Continue' button that submits the information and updates the cart. Each section has the same redirection URL (/checkout) so the user lands back on the "one-page".

If you wanted to do away with 'Continue' buttons, you could use Javascript to submit the customer's information and receive a JSON response. This would include the available shipping methods and the cart totals which you'd use to update the page without the need for a refresh.


How to create custom option/action for backend entry lists

Indeed there is! You're looking for Element Actions...

  When you select elements on their index pages, the regular toolbar options get replaced with new ones that will trigger various actions, like batch-deleting all of the selected elements.

  That functionality is provided by Element Action classes.


Getting started with Craft Commerce 2 subscriptions using Stripe

So I had trouble with the demo's also. But if you head into the Craft Demo Store Templates you will find the following demo page you can use. Here's what I used to get it working.

{% set plans = craft.commerce.getPlans().getAllPlans() %}

{% if currentUser and plans|length%}
    {% for plan in plans %}

{{ plan.name }}

                {% set information = plan.getInformation() %}

                {% set subscriptions = plan.getActiveUserSubscriptions(currentUser.id) %}
                {% if subscriptions|length == 0 %}

You have no subscriptions to this plan.

                {% endif %}
                {% if subscriptions|length > 1 %}

You have multiple subscriptions to this plan.

                {% endif %}
                {% for subscription in subscriptions %}
                    {% if subscription.isCanceled %}
                        Your subscription was canceled on {{ subscription.dateCanceled|date('Y-m-d') }} and will expire on {{ subscription.nextPaymentDate|date('Y-m-d') }}
                    {% else %}
                        You are subscribed and your next payment is due on {{ subscription.nextPaymentDate|date('Y-m-d') }}
                    {% endif %}

                    Manage subscription

                {% endfor %}

        {% if loop.last %}

        {% endif %}
    {% endfor %}

Available plans

            {% for plan in plans %}
                <form method="POST">
                    <input type="hidden" name="action" value="commerce/subscriptions/subscribe">
                    <input type="hidden" name="planUid" value="{{ plan.uid|hash }}">
                    {{ redirectInput('shop/services') }}
                    {{ csrfInput() }}

{{ plan.name }}

                    <select name="trialDays" data-plan="{{ plan.id }}">
                        {% for i in [0, 3, 7, 14] %}
                            {% if i == 0 %}
                                <option value="{{ (plan.uid~':0')|hash}}">No trial period.</option>
                            {% else %}
                                <option value="{{ (plan.uid~':'~i)|hash  }}">Trial for {{ i }} days</option>
                            {% endif %}
                        {% endfor %}
                    </select>

                    {% set paymentSources = craft.commerce.getPaymentSources().getAllGatewayPaymentSourcesByUserId(plan.gateway.id, currentUser.id ?? null) %}

                    {% if not paymentSources|length %}

                            {{ plan.gateway.getPaymentFormHtml({})|raw }}

                    {% endif %}

                    <button type="submit">{{ "Subscribe"|t }}</button>
                </form>
            {% endfor %}

{% endif %}


best approach to call entries from two different sections

You can create one section called events with the different entryTypes small and big.

You'll fetch all events with

craft.entries.section('events')

when you want to fetch only small events you'll do

craft.entries.section('events').type('small')

and the same for the big one.

However you can call entries from multiple sections too

craft.entries.section(['smallEvents', 'bigEvents'])

I suggest you to read the docs these Cheet Sheets are a good place to learn templating too


Use a single template to display entries from different channels?

Personally, in situations like this (particularly if there's only a few items, and no easy way to automate it with inflection or similar,) I just use an array as a kind of dictionary.

{% set sectionHandlesBySegment = {
    'hotel-california': 'hotelsCalifornia',
    'hotel-florida':    'hotelsFlorida' }
%}

{% if not craft.request.getSegment(2) in sectionHandlesBySegment|keys %}
    {% exit 404 %}
{% endif %}

{% set sectionHandle = sectionHandlesBySegment[craft.request.getSegment(2)] %}
{% set hotels = craft.entries.section(sectionHandle) %}


I wouldn't be too worried about MySQL using 100% CPU, especially on a multi-core VPS. But a good way to track down things like this is to enable the MySQL slow query log

You can set whatever thresholds you want, and then peruse the log at your convenience to see the queries that are actually slowing things down.


How can I handle exceptions on the front end

If you put a 500.html template in your craft/templates folder, then that will get displayed on the front end when a exception bubbles all the way to the top.  The same will work for other HTTP status codes as well (404.html, 503.html, etc.).


What is the handle of a section’s default entry type?

The default entry type created for a Section (all types) gets the same name and handle as the Section itself.

You can change the default entry type's name and/or handle by visiting Settings -> Sections, clicking on "Edit Entry Types" next to the appropriate Section, and finally by clicking the relevant entry type's name.


How can I make the broadest search possible?

I gave this a quick test, and it seems that Craft only searches the title field of related assets, when searching entries. What you could do, is first search for all entries with your search term, then all assets with the search term AND related to an entry. Combine those to sets of ids, and you're all set (not really one line of code, though):

{# your search term #}
{% set searchTerm = craft.request.param('q') %}

{# first, search all entries with given search term #}
{% set entries = craft.entries.search(searchTerm).ids() %}

{# first, search all assets with given search term and their relations #}
{% set assets = craft.assets.search(searchTerm).ids() %}
{% set entriesRelatedToAssets = craft.entries.relatedTo(assets).ids() %}

{# Combine those ids to one array #}
{% set allIds = entries | merge(entriesRelatedToAssets) %}

{# Then get all the entries with the given IDs #}
{% for entry in craft.entries.id(allIds) %}
    {{ entry.title }}<br />
{% endfor %}


How to transfer Craft configuration changes separate from web conent

What you need to do is develop some kind of workflow; the workflow that I use is described in the Database & Asset Syncing Between Environments in Craft CMS

But there are other equally valid ways to do it as well. It's really a combination of some tooling, and then just a workflow methodology that you're going to use to keep things in sync.

Writing things down and trying to then re-apply the changes is problematic, because it human error often rears its ugly head when you try to do things that way.


There is no real difference between a "normal" ajax request and an ajax request in Craft. You just leave the url blank, and insert an action parameter for your route. Craft 3 uses snake-case routes instead of camelCase. So when you want to access your plugin you would do.

action = "plugin-handle/controller-name/function-name

I full example as a CP request is

var data = {
    action: 'form-builder/setting/get-settings-for-site',
    siteId: siteId
};
// include csrf token
var tokenName = Craft.csrfTokenName;
data[tokenName] = Craft.csrfTokenValue;
$.ajax({
    type: "post",
    url: '',
    data: data,
    success: function (response) {
            ...
    },
    error: function (XMLHttpRequest, textStatus) {
        console.log("Status: " + textStatus);
    }
});

This would access my Form-builder plugin. The controller is SettingsController and the action actionGetSettingsForSite.


Adding alt text/title attributes to assets in a rich text field

Nothing native that I can think of other than ditch the rich text field and use Matrix with a proper Assets field.


The part that I'm not "getting" is I think there should be one Element in craft_elements and then for each localization, there should be one record in craft_elements_i18n yes?

Yes.

  And then for each localization, there should be a separate record in my craft_seomatic_meta table for each localized version yes?

Only if the values in that table need to be stored on a per-locale basis, which to my knowledge has never been the case until now with SEOmatic.

  When I call $result = craft()->elements->getElementById($metaId, 'Seomatic_Meta', $locale); how does it "know" which record to look up to load the returned Seomatic_MetaElementType with data?

Any time you run an element query (which getElementById() does), the element type’s modifyElementsQuery() method will be called, which gives the element type a chance to make changes to the query. In yours, you have the following code:

$query
    ->addSelect('seomatic_meta.*')
    ->join('seomatic_meta seomatic_meta', 'seomatic_meta.elementid = elements.id')
    ->andWhere(DbHelper::parseParam('seomatic_meta.locale', $criteria->locale, $query->params));

That condition you’re adding to the query on the seomatic_meta.locale column is what is ensuring that the correct localized row in your seomatic_meta table will be included in the result.

That’s just for selecting elements though. I might be missing something, but looking at your SeomaticService::saveMeta() method, it doesn’t look like you’re ever ensuring that all of the localized rows are actually getting saved. If you take a look through ElementsService::saveElement(), you will see that one of the things it does is loop through the locales returned by $element->getLocales() and make sure that there’s an ElementLocaleRecord (elements_i18n row) for the element in each of those locales. If you want to store rows for each locale in your own craft_seomatic_meta table, it’s the plugin’s responsibility to do that, not Craft’s.


I would look to the feed-me plugin itself:
The controller:
https://github.com/craftcms/feed-me/blob/develop/src/controllers/LogsController.php
Which calls the service: https://github.com/craftcms/feed-me/blob/3a8246c055240c46ae9307f3ccada3c42d296920/src/services/Logs.php#L71
Essentially it just reads the log file and presents it to a template, so you could use JS/AJAX to return the results every x seconds.


Have you tried it? I'm pretty sure that's not going to work as Active Record is tied to the db connection in craft/config/db.php hanging off of craft()->db.

You should be able to pull it off with a plugin that instantiates its own DbConnection, though and uses Query Builder instead of Active Record.  Completely untested, but something like this:

$otherDbConnection =  Craft::createComponent(array(
    'emulatePrepare'    => true,
    'charset'           => 'utf8',
    'tablePrefix'       => 'craft_',
    'class'             => 'Craft\DbConnection',
    'autoConnect'       => true,
));

$otherDbConnection->connectionString = 'mysql:host=localhost;dbname=otherdb;port=3306';
$otherDbConnection->username = $userName;
$otherDbConnection->password = $password;

From there you can do $otherDbConnection->createCommand() and grab all of the data you want from the other database.  It's up to you how you'd merge that in with data from your existing database.

If you wanted to get fancy and access the database in other files/places in code, you could do:

craft()->setComponent('otherDb', $otherDbConnection);

And access the other database via: craft()->otherDb->createCommand().


Is it possible to import orders into Craft Commerce?

Sprout Import supports importing Craft Commerce Orders on Craft 3.

I can't speak to which fields are required off of the top of my head but importing to custom fields, variants, and even custom fields like Matrix on Variant Layouts is possible. Any required fields you don't include will throw an error and tell you they are needed.

An example of the Orders import format is available in the Element Importer docs.

Sprout Import uses a specific JSON format to perform imports so you will need to convert your CSV export into the appropriate JSON format to import if you choose to go this route.


Dukt Twitter Plugin, problem with "search/tweets" or request to Twitter REST API

I've tested this works on my install:

{% set tweets = craft.twitter.get('search/tweets', {q:'#craftcms'}) %}

{% for tweet in tweets.statuses %}
    {{ tweet.text }}
{% endfor %}

Have you tried running a {{ dump(tweets) }} to see what you get?


__home__ is the pretend URI that Craft uses to identify an element that should be treated as the homepage. The only way you would be able to output it is if you were doing something like this:

You should be doing this instead, though, which will remove it:

or,


Is it possible to extend the lifetime of the CraftSessionId cookie?

Craft's CraftSessionId cookie is simply the PHP Session cookie renamed and by default it's terminated when the browser is closed.

Craft currently doesn't expose any way to alter that behavior, but you can always make a feature request!


Plugin function throwing error for missing argument

Ok so worked this out whilst working on another plugin. I had to add a variable to the CurateVariable file in order for the template to be able to use the function. Problem solved.


Not currently... we've discussed adding a sort of audit or activity trail to Craft itself, but haven't flushed out any details or timeline.

In the meantime, you could probably get 95% there with a custom plugin that listens to the various Craft events you're interested in.


Do you know which image driver your instance is using? GD or Imagick? If GD, try changing the driver first to see if that's the case.


Multiple instances of Craft in same hosting account root

There are various ways to go about this. Here’s a few:
Option 1: Keep each site completely contained
If you are able to keep each installation in its own directory, each with its own web root, that would be ideal, since you could develop and deploy each site individually:
site-a/
  .env
  composer.json
  composer.lock
  craft
  config/...
  modules/...
  templates/...
  vendor/...
  web/...
site-b/
  .env
  composer.json
  composer.lock
  craft
  config/...
  modules/...
  templates/...
  vendor/...
  web/...

Option 2: Share the web root
If the sites must share the same web root, but you still want to generally think of them as separate installations, then you can at least keep each of the project’s non-web files together:
site-a/
  .env
  composer.json
  composer.lock
  craft
  config/...
  modules/...
  templates/...
  vendor/...
site-b/
  .env
  composer.json
  composer.lock
  craft
  config/...
  modules/...
  templates/...
  vendor/...
web/
  index.php <-- for site-a
  ...
  b/
    index.php <-- for site-b
    ...

Then the only constant you’ll need to worry about is CRAFT_BASE_PATH. For example with the above structure, here’s what those would look like:
web/index.php:
define('CRAFT_BASE_PATH', dirname(__DIR__) . '/site-a');

web/b/index.php:
define('CRAFT_BASE_PATH', dirname(__DIR__, 2) . '/site-b');

Option 3: Share the Composer and module files, too
If it makes sense to just have one shared set of Composer files (composer.json, composer.lock, and vendor/) and modules/ folder (they are inseparable since the module files get autoloaded by composer.json), you can use this structure:
composer.json
composer.lock
modules/
vendor/
site-a/
  .env
  craft
  config/...
  templates/...
site-b/
  .env
  craft
  config/...
  templates/...
web/
  index.php <-- for site-a
  ...
  b/
    index.php <-- for site-b
    ...

You’ll need to make the same changes to CRAFT_BASE_PATH as in option 2, as well as CRAFT_VENDOR_PATH (since vendor/ no longer lives alongside the site-specific files).
web/index.php:
define('CRAFT_BASE_PATH', dirname(__DIR__) . '/site-a');
define('CRAFT_VENDOR_PATH', dirname(__DIR__) . '/vendor');

web/b/index.php:
define('CRAFT_BASE_PATH', dirname(__DIR__, 2) . '/site-b');
define('CRAFT_VENDOR_PATH', dirname(__DIR__, 2) . '/vendor');

You’ll also need to update CRAFT_VENDOR_PATH within both of your craft executables, in this case (not CRAFT_BASE_PATH, though).
site-a/craft & site-b/craft:
define('CRAFT_VENDOR_PATH', dirname(__DIR__) . '/vendor');


Failed to instantiate component or class error

Make sure you have the correct namespace in your modules/exifreturn/src/ExifReturn.php class. From what you show, you should have:
namespace modules\exifreturn;

Right at the top, after <?php. With that in place, run composer dumpautoload.


Craft 3 action routes are snake-case rather than camelCase thus your route needs to be plugin-handle/controller-handle/function-name instead of pluginHandle/controllerName/functionName

Your Controllers namespace must be prefix\namespace\name\controllers so in your case it could be kr37\drycalendar\controllers not Craft.

Furthermore you don't need to include your plugin name to the classname anymore. Your controller name can be

CalendarOccurrencesFieldController

Your action will then be drycalendar/calendar-occurrences-field/generate-ajax-mini-calendar

Keep in mind you'll have to create routes of you don't use the action parameter in your request. Currently you are trying to access your controller via cp/site route, not via action route


update existing cart via ajax creates new cart

Craft 3
This is how Craft Commerce fetches the Card
if ($orderNumber = $request->getBodyParam('orderNumber')) {
    // Get the cart from the order number
    $cart = Order::find()->number($orderNumber)->isCompleted(false)->one();
}

You have to include an orderNumber parameter
Craft 2
$number = $this->_getSessionCartNumber();

if ($cart = $this->_getCartRecordByNumber($number))
{
    $this->_cart = Commerce_OrderModel::populateModel($cart);
}
else
{
    $this->_cart = new Commerce_OrderModel;
    $this->_cart->number = $number;
}

/**
 * @return mixed|string
 */
private function _getSessionCartNumber()
{
    $cookieId = $this->cookieCartId;
    $cartNumber = craft()->userSession->getStateCookieValue($cookieId);

    if (!$cartNumber)
    {
        $cartNumber = md5(uniqid(mt_rand(), true));
        $configInterval = craft()->config->get('cartCookieDuration', 'commerce');
        $interval = new DateInterval($configInterval);
        $cartExpiry = date_create('@0')->add($interval)->getTimestamp();
        craft()->userSession->saveCookie($cookieId, $cartNumber, $cartExpiry);
    }

    return $cartNumber;
}

I think you'll have to change the cookie


What you can do is to create additional entry sources using the modifyEntrySources hook.

This is a very simple example, that will add a heading and two sources to the end of the
sources list (below your default Singles, Channels and Structure sources).

public function modifyEntrySources(&$sources, $context)
{
    $newSources = array(
        array('heading' => 'Post Formats'),
        array(
            'label' => 'Articles',
            'criteria' => array('section' => 'blog', 'type' => 'article')
        ),
        array(
            'label' => 'Links',
            'criteria' => array('section' => 'blog', 'type' => 'link')
        )
    );

    $sources = array_merge($sources, $newSources);
}

You could also get all entry types of a section with getEntryTypes() and add them programatically if you want.


You can use MySQL's RAND() function inside your order method:

{% for entry in craft.entries.section('team').type('mainPeople').order('RAND()') %}


Disable or hide an entry field in the CMS

You could inject some CSS to hide it specifically.


Displaying one site's content in another site (Craft 3 Multi-site)

I figured it out!

In Section settings > Propagation Method, you must choose 'Save entries to all sites enabled for this section', or, if you're using site groups, choose the appropriate group. This basically shares entries across multiple sites.
If your entries were previously only associated with one site, doing this will cause entries to be resaved.
When calling entries, do not specific the site from which they are pulled, in other words, do not include site or siteId in the entries query.

{% set entries = craft.entries.section('sectionName').limit(10) %}

Now your urls will be correct, relative to the current site.

On Site 1: firstSiteDomain.com/news/president-elect-not-sociopath-at-all-billions-rejoice

On Site 1: SecondSiteDomain.com/news/post/president-elect-not-sociopath-at-all-billions-rejoice

Even though the entries, news in this case, may be intended as "tied to" Site 1, you must 'share' them with Site 2 in order for Site 2 to load them and create entry.url correctly.


URLs other than index cause 500 internal server error

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.


Dynamically update line item in Commerce 2

Turns out I just replace:

$cart->addLineItem($lineItem);

With:

CommercePlugin::getInstance()->getLineItems()->saveLineItem($lineItem, false);


Show only and merge the entries from multiple categories related to that entry

You can collect the id from the previous entry, then you can exclude id in the next query.
For example like this :

 {% set subcategories = craft.categories.group('prodCat').descendantOf(prodCat).level(2).relatedto(entry) %}
 {% set listId = ['not', entry.id ] %} //get id from current page id.
        {% if subcategories | length %}
            {% for subcategory in subcategories %}
                {% set relatedentry = craft.entries.section('products').relatedTo(subcategories).id(listId).all() %}
                {% set relatedentry_count = relatedentry|length %}
                {% if relatedentry_count > 0 %}

Recommended Product

                        {% for entry in relatedentry %}
                        {% set listId = listId|merge([entry.id]) %} // append the id from current category to the array.

{{entry.title}}

                        {% endfor %}

                {% endif %}
            {% endfor %}
        {% endif %}


At a basic level, front-end user account management is definitely something that can be done with Craft Pro natively.
There's a knowledge base article on the subject here: https://craftcms.com/knowledge-base/front-end-user-accounts

Each account should get approval by admin before going live.

Also possible natively, with the "Suspect users by default" user setting (Settings->Users->Settings in the Craft control panel.

I'm not saying you won't have to use a plugin (whether it's already built or a custom-built one), but depending on your specific requirements, you might be able to get way with natively functionality.


Redactor settings: stripping out classes from table

If all your tables should use the same class or classes, you could use my Retcon plugin to add the classname(s) in Twig:

{{ entry.body|retconAttr('table', {class: 'someclassname'}) }}

If different tables need different classes, this isn't a solution, though.


FeedMe error with iTunes RSS compatible feed

You can’t use dot syntax because of the colon, try this instead

{{ feed[0]['itunes:image']['@href'] }}


When you do your export in phpmyadmin, make sure you check the 'disable foreign key checks' box in the custom options:


Can I auto-update to a specific version of Craft?

It's not possible to auto-update to a specific version of Craft, but it is possible to manually update to one.

You'd just need to download the version you want to update to, then perform a manual update.


I don't think there is at the moment, but you could probably build a easy plugin to do that with Javascript.


I think your output is wrong. Looking at your code and explanation, {% for row in entry.hgroup %} should be: {% for row in block.hgroup %}

Same goes for {{ entry.heading }} This should be: {{ row.heading }}

Copy should be: {{ block.copy }}

Working with matrixes and Supertable's is hard in the beginning but you'll get the hang of it.

When you loop over you {{ entry.about }} you loop using a variable named block. that should be used for every field you want to output that's in the matrix field. Same goes for SuperTable's only just one layer deeper.


404 – Not Found from _entry.twig templates in subdirectories

Templates or folder with an underscore are private and can't be accessed via template routing. Remove the _ and you are fine. For example news/entry instead of news/_entry


This isn't something that is built-in, probably the cleanest way would be to write a plugin that adds a Utility that lists your installed modules.

You can see all of the installed modules via:

Craft::$app->modules

...but keep in mind that this will include all of your plugins as well, since they are modules, too. You can exclude them by doing something like:

foreach (Craft::$app->modules as $module) {
    if (!is_a($module, Plugin::class)) {
    // This is some kind of module that isn't a plugin
    }
}

Note that this will include other things like the yii\debug\Module and even craft\web\Application too.

If you want to see just the loaded modules for the current request, you can do:

Craft::$app->loadedModules

(not all modules may be loaded for each request)


Getting invalid verification code for email confirmation

Those codes are valid for as long as whatever verificationCodeDuration is set to (24 hours by default).

Guessing the users that are getting that are hitting expired verification codes.


White blank page when accessing website and Craft login page

That error indicates that Craft is missing some files (or they are corrupted) in its craft/app folder.  You can download a fresh copy of Craft from https://craftcms.com and replace your existing craft/app folder with the new one and that should resolve it.


Why is the parent slug showing up in the child slug in a structure?

Pretty sure that the ResaveElements task just hadn't run yet - my queue was stuck. Once I cleared the stuck items, the issue resolved on its own!


Where exactly to update the composer.json to install an older version of Craft 3?

Make sure you have Composer installed from the command line.
Open your project's composer.json file.
Find the "require" section. Similar to:

"require": {
    "craftcms/cms": "^3.6.4",
    "vlucas/phpdotenv": "^3.4.0"
},

Edit the craftcms/cms line to the exact version you want. For example 3.5.0.

"craftcms/cms": "3.5.0",

Run composer update from the CLI from your project's root and that will pull in a fresh set to Craft 3.5.0 files.


Commerce: How can I check order history to see if a product type has been purchased?

First you'll need to grab the user's orders. Then you can check the product type through the purchasable.

Something like this:

{% set orders = craft.commerce.orders.user(currentUser).find() %}

{% if orders|length %}
    {% for order in orders %}
        {% for lineItem in order.lineItems %}
            {{ lineItem.purchasable.product.typeId }} {# 1 #}
        {% endfor %}
    {% endfor %}
{% endif %}

You can take that a step further by doing something like this:

{% set orders = craft.commerce.orders.user(currentUser).find() %}
{% set product = false %} {# set the condition to false by default. #}

{% if orders|length %}
    {% for order in orders %}
        {% for lineItem in order.lineItems if lineItem.purchasable.product.typeId == 1 %}
            {% set product = true %}
        {% endfor %}
    {% endfor %}
{% else %}
    {# No orders found. #}
{% endif %}

...

{% if product %}
    {# User has purchased product type at some point in time. #}
{% else %}
    {# User has never purchased product type. #}
{% endif %}


Search query for entries doesn't seem to work correctly

I think the problem originates from the space in your search term: test ticket.
To verify that, could you try altering your query to either

.search('"' ~ searchQuery ~ '"')

Or

.search('fieldHandle:"' ~ searchQuery ~ '"')

The latter can be used if the search has to take place in only one field.


A Streamlined Way to Export / Import Entries Between Environments?

This is what I do to solve this problem: Database & Asset Syncing Between Environments in Craft CMS

I've found that this KISS technique of just declaring one environment as the "gold master" and pulling changes to all other environments works really well for me.


Using user-selectable timezones to show localized post dates

It is possible to work around the Craft 2 bug, by converting the Craft DateTime object to a native one.

{% set craftDateTime = entry.postDate %}
{% set nativeDateTime = date(craftDateTime|date('c')) %}

{{ nativeDateTime|date('n/d/Y g:ia', 'America/Los_Angeles') }}


Matrix Issues After Upgrading from 2.4 to 2.6

Craft 2.5 changed from Redactor I to Redactor II, so chances are you just need to update your Redactor configs to the new format: https://craftcms.com/support/redactor-ii-configs


Single type for entry submission on error loses entry object

From the README:

  If there is a validation error on the entry, then the page will be reloaded with an entry variable available to it...

You can override the variable name by setting the entryVariable config setting, as follows:

<?php

// File: craft/config/guestentries.php

return [
    'entryVariable' => 'guestEntry',
];

Once you've done that, the page entry model will be available as entry, and the "guest entries" entry model (containing any validation errors) will be available as guestEntry.


Maintaining order when getting by entries by ID

You can do this by adding .fixedOrder(true) to your query:

{% set ids = ['4213','3200','4204','4212'].fixedOrder(true) %}

https://docs.craftcms.com/v3/dev/element-queries/tag-queries.html#fixedorder


The relatedTo parameter can take an object, an element ID or an array of element objects and/or IDs. In your case the latter is expected so wrapping your arguments in an array should fix the issue:

{% set entries = craft
                .entries
                .section('survivorAssets')
                .relatedTo(['and',{ targetElement: theme },{ targetElement: survivor }])
                .orderBy('assetOrder')
                .all() %}


AWS EC2, Load Balancer and Updating Craft

Don't update Craft or any plugins on production.

Do the updates on a local/dev version of the site, commit the resulting changes, then deploy those to all of your EC2 instances with a CI/CD service like https://buddy.works or whatever your favorite service is.

Some related blog posts worth reading:

https://nystudio107.com/blog/database-asset-syncing-between-environments-in-craft-cms

https://nystudio107.com/blog/local-development-with-vagrant-homestead


Just want to mention for anyone running across this thread that if you're having a problem with live preview and seeing dev tools errors of the nature of cors or crossdomain then the first thing you might want to check is that if the domain you're serving the site (the control panel, specifically), is the same as what you have set in "Settings | General Settings" (or in Craft 3, "Settings | Sites")

For example, if you serving the site from a example.com (root domain), and you have www.example.com in the settings, your live preview will bonk because it's a different domain and the live preview is being served via ajax.


Sprout Forms - Redirect to same page with query string

Sure, in the control panel for sprout forms you can set the redirect page. Say the url for your contact page is /contact you can do something like redirect to:

contact?message=thank-you

and then in your contact template do something like:

{# Check if we should display a thank you message #}
{% if craft.request.getParam('message') == 'thank-you' %}
 //do stuff here
{% endif %}

Sprout installs a bunch of sample forms, there's plenty to gleam and learn from these.


Solspace Calendar: custom fields not outputting

This is a known issue and will be resolved in Beta 4 coming out today (March 15, 2018). Sorry for the trouble.


Can I have multiple comparisons within a single criteria parameter?

You can combine multiple criteria like so:

{% set entries = craft.entries.width('and, >= 100, <= 200') %}

but what you can't do is to combine multiple statements to build more complex criteria:

{% set entries = craft.entries.width('and, >= 100, <= 200' or '>= 500') %}

in your case this wouldn't make much sense. But see my still unanswered question on this topic for another example: "Complex logic on a ElementCriteriaModel parameter?"


There isn't a way baked in to Craft - probably something to do with the fact that users can be in multiple groups. Although I totally agree it would be useful, like at least listing the first group they're in, like the Entries view does with Categories.


Element API: List entries related by tag

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.


Additional Fields aren't sent within Contact Form email

This issue is this:

<textarea name="message" name="message[body]" cols="30" rows="6" placeholder="Questions or Comments"></textarea>

You're defining name twice.  The 2nd one gets ignored (which is required if you're using the array syntax for other fields).


I don't think there is a way without hacking Redactor or adding a custom Redactor plugin that mimics/extends the existing Image/Link button functionality.


First, I don’t think your pagination navigation is limiting to just 6 pages like you think it is. The number you pass into paginate.getPrevUrls() and paginate.getNextUrls() will determine how many URLs the functions should return. So if you’re on the 3rd page, calling paginate.getNextUrls(5) will give you the URLs for pages 4-8.

Navigation-wise, what you’re probably better off using is paginate.getRangeUrls(), which accepts explicit first/last page numbers:

{% for page, url in paginate.getRangeUrls(1, 6) %}
    {% if page == paginate.currentPage %}
        {{ page }}
    {% else %}
        {{ page }}
    {% endif %}
{% endfor %}

(You can get a full breakdown of the properties/functions within that paginate variable here: http://buildwithcraft.com/docs/templating/paginate#the-paginate-variable)

Even with that change, you’re not actually enforcing that only 6 pages are available; there’s still nothing stopping a user from manually going to http://example.com/news/p7. The would still get the 7th page of news entries, with an awkward pagination navigation, because it would just show links to pages 1-6 with no indication that you’re on the 7th page.

So back to your original question: how do we enforce a 6-page maximum?

Your idea of setting a route in craft/config/routes.php was a good one, but unfortunately Craft actually removes the “page” part of the URL from its internal path before it starts checking the path for matching routes.

Because of that, your best option here is to enforce it from the actual News template.

You can access the current page using craft.request.getPageNum(), so let’s just check for that at the very top of your template, and if it’s greater than 6, return a 404 using the {% exit %} tag:

{% if craft.request.getPageNum() > 6 %}
    {% exit 404 %}
{% endif %}

To clean things up a bit, you might want to store that maximum number of News entries as a variable within your template, or maybe even as a 'maxNewsPages' custom config variable in craft/config/general.php.

{# Pull the custom 'maxNewsPages' config setting from craft/config/general.php #}
{% set maxPages = craft.config.maxNewsPages %}

{# Enforce it #}
{% if craft.request.getPageNum() > maxPages %}
    {% exit 404 %}
{% endif %}

...

{% for page, url in paginate.getRangeUrls(1, maxPages) %}
    {% if page == paginate.currentPage %}
        {{ page }}
    {% else %}
        {{ page }}
    {% endif %}
{% endfor %}


Utime failed: Operation not permitted in craft/app/helpers/IOHelper.php on line 1540

It looks like a permissions related issue.  Check the user:group owner of all of the files in the craft/app folder and make sure that they are set to the same user:group that your PHP installation is running as.


Set OrderBy on Grouped Query Involving Smart Map Field

Okay, I've been able to solve this and figured I'd share what I discovered. The key for each group is the value of the state so really what I was after here was a key sort and just didn't realize it. So I found Michael Rog's "SuperSort" plugin which has a twig filter for running a ksort and that addressed it perfectly for me making a small adjustment to use supersort in the response.

{% set resorts = craft.entries().section('resortsList').orderBy('title ASC').all() %}
{% set resortsByState = resorts|group('address.state')|supersort('ksort') %}

I'm interested in other solutions too that don't require a plugin, but this particular direction got things going for me.


How to get an input value without refreshing with twig?

Yes you would do this using Javascript so is a little outside the scope of this StackExchange site but here's something that might help get you moving...
Getting the value is easy with something like
document.querySelector('#myElement').value

You'd need to trigger that in some way, perhaps when the user leaves the field. So (assuming your input has an ID of "myElement"...
document.querySelector('#myElement').addEventListener('blur', function(el){
    let theValue = el.value
})

What this is doing...
Get the element on the page with an ID of 'myElement'
Adds a listener to it for the 'blur' event (when the user leaves the element)
When that happens it executes a function and passes the element into it
Sets a variable (theValue) with the value of the element.
You now have a javascript variable named 'theValue', but what do you want to do with it?


SEO titles/descriptions for tags and archive pages

The better approach in my opinion is to declare the variables in the corresponding templates rather than in the _layout template

Layout.twig

{% if seoTitle is not defined or seoTitle is empty %}
    {% set seoTitle = config.seoTitle %} <-- fallback if nothing is defined
{% endif %}
<title>{{ siteName }} - {{ seoTitle }}</title>

_entry.twig

{% extends 'layout' %}
{% set seoTitle = entry.seoTitle %}

_category.twig

{% extends 'layout' %}
{% set seoTitle = category.seoTitle %}

_someOtherTemplates.twig

{% extends 'layout' %}
{% set seoTitle = 'Some hard coded strings....' %}

and so on. That way you can define seoTags based on custom properties as well otherwise it would be difficult to determinate tags on dynamic template routes without an entry or a category


Close entry editing after set time (for certain a user group)

Seems like you'd just need to write a plugin that listened to the onBeforeSaveEntry event.

That plugin would check if the entry author is in the user group you're interested in, and if it's a new entry just let it save.

If it's an existing entry, check the dateCreated date against your window and see if it's still valid.  If it's outside of your window you can set performAction to false so the entry won't save and set a flash message indicating they're outside the window.


How to store json data in a custom fieldtype so that it's data can be referenced as attribute values?

You already mentioned the related answer in custom fieldtype with multiple values, and it actually is perfect for your usecase.

It doesn't really matter how you transport the data over to Craft, because if the content is set to AttributeType::Mixed, then Craft will JSON encode/decode it when requested.

So, to save your data, send over an array, like so:

$stripeCustomer = [
    "id" => "cus_4v68MrZu3za8XT",
    "object" => "customer",
    "etc" => "etcetcetc"
    ];

// Transport $stripeCustomer to Craft with Ajax

In the context of Javascript, you'd send over the serialized form data.


The reason why you only see the top level category in your category field and why your URL's only use the top level category is probably because you set the "limit (number of selectable categories)" to 1 in your category field settings.
That way you can only choose one category but it also only selects the top category.

Pretty irritating, I know. I had some issues with it too.

So clear that limit and it should work.
I don't have a solution to limit the amount of categories selected without that option being checked. I guess you can adopt your templates for multiple categories or write a plugin to do some extra checking when selecting categories.


What are the consequences of choosing structures over channels?

All entries are equal. The only differences between Structures and Channels are that Structures are manually sortable and can be nested under other entries. Channels sort by Post Date by default. Unless you are doing a stream of entries (like blog posts or events), structures are a good way to go.


If you are using something like this to get your entry:

{% set entry = craft.entries.section('blog').limit(1).id('21') %}

then Craft is actually returning an ElementCriteriaModel object, even if it is only one entry.

To ensure that the entry variable only contains a single entry, you need to call the first() method like this:

{% set entry = craft.entries.section('blog').limit(1).id('21').first() %}

More details in the docs here: https://craftcms.com/docs/templating/elementcriteriamodel


How do I query two relationship fields where one relationship MUST be empty?

Splitting the criteria up like this works.

$criteria->relatedTo = [
    'targetElement' => anElementId,
    'field'         => 'relationshipOne'
];
$criteria->relationshipTwo = ':empty:';

Thanks to @carlcs in the Craft Slack channel.


Where to place Webmaster Tools HTML File on CRAFT CMS?

You should place it in your public folder (may have a different name on your server) not in your Craft folder. The folder where all files (such as your assets) live in that are publicly accessible on your webserver.


Ok, so @brad-bell suggested a fix and the solution was to disable ::1 localhost rule from my /etc/hosts file. After that change all images are working properly.

More details can be found here https://stackoverflow.com/questions/22952676/curl-failed-to-connect-to-localhost-port-80

Thanks!


You can't use FocusPoint like that with the default Craft Transforms, which might be your problem. You'll need to use Imager to get the position as a percentage supported.

Something like:

{% set image   = entry.listingImage.first() %}
{% set max479  = craft.imager.transformImage(image, {width: 480, height: 200, mode: 'crop', jpegQuality: 70, position: image.focusPctX ~ '% ' ~ image.focusPctY ~ '%'   }) %}
{% set min480  = craft.imager.transformImage(image, {width: 700, height: 250, mode: 'crop', jpegQuality: 70, position: image.focusPctX ~ '% ' ~ image.focusPctY ~ '%'   }) %}
{% set min700  = craft.imager.transformImage(image, {width: 960, height: 250, mode: 'crop', jpegQuality: 70, position: image.focusPctX ~ '% ' ~ image.focusPctY ~ '%'   }) %}
{% set min960  = craft.imager.transformImage(image, {width: 1280, height: 250, mode: 'crop', jpegQuality: 70, position: image.focusPctX ~ '% ' ~ image.focusPctY ~ '%'  }) %}
{% set min1280 = craft.imager.transformImage(image, {width: 1500, height: 250, mode: 'crop', jpegQuality: 70, position: image.focusPctX ~ '% ' ~ image.focusPctY ~ '%'  }) %}

{# standard Imager image transforms #}
<picture>
    <source media="(max-width: 479px)" srcset="{{ max479.url }}">
    <source media="(min-width: 1280px)" srcset="{{ min1280.url }}">
    <source media="(min-width: 960px)" srcset="{{ min960.url }}">
    <source media="(min-width: 700px)" srcset="{{ min700.url }}">
    <source media="(min-width: 480px)" srcset="{{ min480.url }}">
    <img src="{{ min1280.url }}" alt="" />
</picture>


Output section and tag name with URL in entry loop

Sections do not have URLs, so section.getUrl will not return anything for you.

If you are trying to get an index page of all the entries in a section that has a URI, try slicing off the last segment of the entry's URL, and make sure you have an index.html template in that section's template folder. Take a look at the templating documentation for more information on this.

For the tags, make sure you call  {% for tag in craft.tags.relatedTo(entry) %} instead of {% for tag in craft.tags.relatedTo(entries) %} to get the tags related to the entry you are currently focused on in the for loop.

Hope this helps!


Issue with rendering a template from a plugin function

I haven't tried calling a template from a controller, so I may not be any help but I've used

...->render(...)

in my main plugin Class like this:

MyPlugin.php

return craft()->templates->render('myplugin/_settings', array('settings' => $this->getSettings()));

Maybe the render method is looking for another argument?

EDIT

Oh wait... I was wrong, it looks like I do...

MyPluginController.php

$vars['users'] = craft()->user->listUsers();
$this->renderTemplate('myplugin/index', $vars);

Then I have a variables file:

MyPluginVariable.php

public function getContacts()
{
    return craft()->contactForm->getUsers();
}

Which ties back to the my service:

MyPluginService.php

public function getUsers()
{
    return MyPluginRecord::model()->findAll(array('order' => 'dateCreated desc'));
}

Maybe that will help get you rolling?


Thanks to billythekid's suggestion, adding a password field creates an elevated session, and solves my problem.

<input type="password" name="password" placeholder="Password" required>

Actually I don't think it's that intrusive, and it's a good security step that should generally be encouraged when making any profile edits.


Dropdown that shows all months and when selected displays all entries from that month

The following three resources should be substantial enough to find a solution:

Linking and showing archive by month
Craft Docs: Grouping by year and then month
Need to access year/month variable when grouping entries by date


Multiple Entries fields, show empty ones?

It's probably not possible to get the sections you limited your entries field to from the template and if there's no entry selected that's what you'd need to do.

But what I'd recommend and what's generally a pretty solid approach in such situations is to build an associative array with all the necessary data and loop through that array:

{% set relationsGroups = {
    'entriesField1': 'Heading Relations 1',
    'entriesField2': 'Heading Relations 2',
    'entriesField3': 'Heading Relations 3',
} %}

{% for fieldHandle, heading in relationsGroups %}
    {% set assets = craft.entries({
        relatedTo: {
            sourceElement: entry,
            field: fieldHandle
        }
        order: 'sortOrder',
        limit: null
    }) %}

    {% for asset in assets %}

{{ heading }}: {{ asset.title }}

    {% else %}

{{ heading }}: Empty

    {% endfor %}
{% endfor %}


Can we use Active Directory for Access Control?

No, this is not available in the core. There is a plugin which deals with OAuth which may help you.

You can view a list of hooks which Craft provides out of the box here.

I imagine you will need to develop your own plugin for this. There is some guidance in the Craft documentation on creating hooks/events in a custom plugin if that's the route you'd prefer go down.


Foreign key constraint sql error from entries/saveEntry when adding entry

I think the problem is this:

<option value="v.id">{{v.title}}</option>

Try changing it to:

<option value="{{ v.id }}">{{v.title}}</option>


Trying to set an image URL using a global asset field

I don't know if it's the cause of the error you're receiving or not, but this is incorrect Twig syntax:

{% set image = '{{ websitePreferences.placeholderImage.first.url }}' %}

You'll never need to nest {{ }} inside of {% %} or visa versa in Twig.

This is correct:

{% set image = websitePreferences.placeholderImage.first() %}


{% if json %}

Replaced with.

{% if asset.kind == 'json' %}


How do I access current site handle in Craft 3 controllers

I know this is a year and a half late, but maybe it helps someone else. I have a large multi-site project with a site for country and language. For example:

Germany (German) - default - /de-de/
Germany (English) - /en-de/
Austria (German) - /de-at/
Austria (English) - /en-at/
etc

I was having this problem when posting to one of my controller's actions and then trying to get the currentSite within my plugin via Craft::$app->getSites()->currentSite.

In my JavaScript, my action looked like:

/actions/pluginHandle/controllerClass/actionMethod

Whenever I'd fetch the current site with Craft::$app->getSites()->currentSite it would always return the default site (Germany/German), and that's because I wasn't including the first URL segment (to get Austria/English, for example):

/en-at/actions/pluginHandle/controllerClass/actionMethod

Without that, Craft was resorting to getting the default site.


Element API - UrlHelper generates backwards slashes (regex)

The plugin probably uses PHP's json_encode to serialize the data and it automatically escapes quotation marks and backslashes to conform to the JSON format. Escaping the forward slashes of your URL wouldn't actually be necessary, but I just found this on StackOverflow which might give an idea why the function escapes them anyways:

https://stackoverflow.com/questions/1580647/json-why-are-forward-slashes-escaped

But you shouldn't really care about this behavior at all, because however you gonna consume that JSON string, the parser (e.g., jQuery.parseJSON()) should handle the unescaping for you.


You need to create a 'Manual' payment gateway in Commerce > Setting > Payment Methods This will allow the customer to choose the manual payment method on checkout.

The 'Manual' payment method is the only way a customer can submit an order without payment information. This gateway does not place any validation on the payment form action, so you should not see any errors. If you are seeing errors, make sure you are sending the ID of the manual payment method with the paymentMethodId param to the payment form action.

The 'Mark as Complete' is a separate action that allows those that have the 'Manage Orders' permission, to turn a Cart that is in progress into an Order:


Create a link list with URLs, categories, entries

Michael,

This is a very common development pattern that we run into. Our primary method of resolving is to create a global set with matrix of links. Using a matrix allows you to extend the matrix item with whatever fields you would need based on the ui, such as an icon class.

Matrix Layout:

Links (link)
│
└─ Link Text (text)
└─ Link Entry (entries, limit 1)
└─ Link URL (text)
└─ Link Target (dropdown, [_self, _blank])

I think that a matrix is a good choice, even considering user experience, because they can be collapsed after setup and it is likely that your user is familiar with them from other areas of the site.

If you don't want to use vanilla Craft you could use the Linkit plugin to add a more robust link field-type. It even lets you set the title, click behavior and works with various types of entries, assets, and custom URLs.

Linkit plugin for Craft https://github.com/fruitstudios/LinkIt

Good luck!


You have to select the "level" in the structure. Im guessing the products are always on level 4? If so you could try this:

{% set entries = craft.entries.section('yourSectionHandle').level(4) %}

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


Is there a way to clear old database files from storage|backups folder automatically?

******* Updated based on khalwat's comment ************

I don't think Craft does that automatically. Not sure about 3rd party plugins.

I wrote a custom controller function to run by Cron Job, and it's been working fine.

It does nothing but looping through the .sql files in the craft/storage/backups folder and delete the ones which are older than X days, based on the modified time of the file.

public function actionDeleteOldBackups() {
    try {
        $dir = 'craft/storage/backups';
        $backupFiles = scandir($dir);

        $x = craft()->config->get('xDaysToDeleteDbBackup') ?: 30; // Keep the backup files for 30 days by default. Change it to w/e you like.

        // Craft uses Greenwitch time to name the backup files
        $xDaysAgo = strtotime('-' . $x . ' days', time());
        // depending on how you want to compare the dates, the following line can be ignored.
        $xDaysAgo = strtotime(date('y-m-d', $xDaysAgo));

        // loop through files, find the file's last modified time and compare with $xDaysAgo
        if(!empty($backupFiles)) {
            foreach ($backupFiles as $fileName) {
                if(strpos($fileName, '.sql') !== false) {
                    $filepath = $dir . '/' . $fileName;
                    if(file_exists($filepath)) {
                        $created = filemtime($filepath);
                        if($created && $created < $xDaysAgo) {
                            // print_r($filepath . ' '); // for testing
                            unlink($filepath); //delete the file
                        }
                    }
                }
            }
        }

        \Yii::app()->end(); // no render
    } catch (\Exception $e) {
        die('Failed to delete dated backup files.');
    }
}

Put the function in one of the controller of your custom plugin, kindly reminder to allow anonymous access for this function. The URL for the Cron Job will be something like:

[host]/actions/pluginhandle/deleteOldBackups


There are several plugins that can dynamically generate XML sitemaps for you on your Craft CMS site.

Note that Google XML Sitemaps are not specific to Google. Google supports the standard sitemap protocol which is also used by Microsoft and Yahoo!.

I'll highlight two commercial plugins that support generating sitemaps for all native URL-Enabled Content which includes Entries, Categories, and Commerce Products:

Sitemap - Sitemap by Put Your Lights on is a plugin that focuses specifically on creating an XML sitemap for you.
Sprout SEO - Sprout SEO is a more comprehensive plugin for a wider range of SEO needs, which includes search, social sharing, sitemaps, and redirects. Sprout SEO will generate an XML sitemap for you and supports large sitemaps, multi-regional, and multilingual sitemaps. Sprout is also extensible and developers can add Sitemap support for custom URL-Enabled Sections.

There are also several other free sitemap plugins. A search for 'sitemap' in the plugin directory on Straight Up Craft can point you in the direction of several of those.


When I'm logged into the backend using my admin account and I use the form to sign up a new fake user, it works as desired and assigns the new user to the 'trade' group. If I'm logged out it just assigns the user to the default 'customers' group rather than 'trade'.

When you're logged in as an admin and test this for a new fake user, you have all of the necessary permission to perform the operation (including assigning new user/group permissions).

When you're not logged in as any user, you're missing the necessary "Assign user to groups" permissions a logged in user would need.  New users are going into the "customers" group, because presumably in Settings->Users->Settings in the Control Panel, you've got front-end registration enabled and you've assigned the default group to be "customers".

Having said that, here's a Craft 2 plugin that probably does what you're looking for. https://github.com/lindseydiloreto/craft-autoassignusergroup


How to sort entries by their descendant count?

You could use a Preparse field to calculate and store that value.

The only downside is, it'll only do that calculation when you save the parent entry. So if you add another descendant, you'd have to go back and re-save the parent entry to re-calculate the value.

  Pro tip: You can go to Settings > Sections and re-save the section you are working with. This will automatically trigger a re-save of every single entry in that section.


Get the type of action and the elements(s) that an action is being performed on when listening to elements.onPerformAction?

It appears that when one or more Entries (or other types of content) are manipulated (in my case, deleted) from the bulk edit view in the Control Panel, the type of events that are triggered are Element events, rather than Entry events.

If you refer to the Craft documentation on elements.onPerformAction, you can see that the 2 params you get access to are action and criteria.

Here's how I was able to find what I needed when working with elements.onPerformAction and the elements.onBeforePerformAction events.

What type of action is being performed on the element? (Save, Delete, etc.)

I was able to capture what type of action was being being performed on my element by looking at the "action" param of the event. Using the following code output the action that was taking place:

$action = $event->params['action']->classHandle; //Outputs 'Delete'

This bit of code will output the string 'Delete'. Perfect.

How do I identify the entry that the action is being performed on.

What I learned is that, when working with an Element event, you're not just working with a single entry. You're potentially working with many Entries, or rather "Elements" to be more specific. Because of this, you can't just grab the Element being manipulated in the same style you can with an Entries event.

$entry = $event->params['entry']; won't work here.

This brings us to the "criteria" param of the Element event.

You can simply grab the "criteria" param of the event to get the elements that the event is taking place on. This param contains an ElementCriteriaModel object.

Viewing the class reference for ElementCriteriaModel shows us what we can do with this object. In my case, I can simply use ->find() on it to get all of the elements I'm working with.

$elements = $event->params['criteria']->find();

I now have an array of the Elements having actions taken on them, stored in $entries.

Now that I have both the type of action and the elements, I have what I was needing to move forward.

Here are my event listeners in full context. One handles a entries.deleteEntry event, and the other handles a elements.onBeforePerformAction event.

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

craft()->on('elements.onBeforePerformAction', function(Event $event) {
    $action = $event->params['action']->classHandle; //Outputs 'Delete'
    $elements = $event->params['criteria']->find(); //Array of Elements
    if ($action == 'Delete') {
        foreach ($elements as $element) {
            craft()->lhngroups_organizations->deleteOrgCustomerAddress($element);
        }
    }
});

It's important to remember that since we could theoretically be working with many types of Elements (Entries, Assets, Categories, Users, etc.), rather than just Entries, we may need to validate that we're working on the right types of Elements before we operate on them. Since I have multiple event handlers calling my deleteOrgCustomerAddress() service, I did this validation check there.

public function deleteOrgCustomerAddress($element)
{

    // Make sure we're working with an entry in the organizations section
    if ($element->elementType !== 'Entry' && $element->section->handle !== 'organizations') {
        return;
    }

    // We are good to go! Do stuff with the Entry...
}

Thanks to @lindsey-d for some help along the way!


Issue Devmode is still enable in multi-environment config file

You’re setting the devMode setting to the string 'false', which seems to be evaluated to true. You need to set it to a boolean.

'*' => array(
    'devMode' => false,
),
'.dev' => array(
    'devMode' => true,
),


Extend a Commerce (v2) order template in the control panel

I found the solution in another question:
Writing a plugin that triggers on a Control Panel Template Hook?

Based on this answer we can use:

Craft::$app->getView()->hook('cp.commerce.order.edit.main-pane', function(array &$context) {
    return 'My Button!';
});


Randomly listing entries in more then one section

I would just slice the randomized array of categories:

{% set ads = craft.categories({
    group: 'ads',
    order: 'RAND()'
}) %}

{% for ad in ads|slice(0, 2) %}
    {{ ad.externalUrl }}
{% endfor %}

{% for ad in ads|slice(2, 4) %}
    {{ ad.externalUrl }}
{% endfor %}


I suggest changing the couple places where you use getParam('offerCategory') to getParam('category') so it matches the parameter set in the URL.


Best practice on importing Craft db backups?

The short answer is, yes, definitely drop your existing tables before importing a new .sql file. If you don't, you run a high risk of Craft developing weird issues of data mismatching. This is especially true if the two versions of the database were not created with the same exact version number of Craft.

NOTE: You may want to back up your existing database before dropping all of the tables.

The longer answer is, database syncing is an interesting and complicated problem, with many possible solutions. Check out this thread discussing the "holy grail" of database syncing across multiple environments...

What's the best practice for handling data migration and organization across development environments?

Ultimately, your data migration patterns are up to you. There are varying approaches, some involving Shell scripts and Git hooks. It's an evolving challenge for the Craft community (and other CMS's as well), so keep your eyes peeled for the next brilliant "eureka" moment!


One solution that comes to mind is to add your own custom status field (i.e. 'reviewStatus') as a drop-down menu (or checkbox perhaps if only one status). Then add this field as a column in your list view via a small plugin, using the new modifyEntryTableAttributes hook.

public function modifyEntryTableAttributes(&$attributes, $source)
{
    if ($source == 'section:3')
    {
        {# remove a column if desired #}
        //unset($attributes['expiryDate']);

        {# add a column #}
        $attributes['reviewStatus'] = Craft::t('Review Status');
    }
}

Might also check out getEntryTableAttributeHtml hook (to customize the HTML display of that column), and modifyEntrySortableAttributes hook (to add this field to the sort options).

To change the column display order you would need to unset all of the native attributes and reset them in the desired order (see this answer for example).


Nested objects for geoJSON using Element API

I figured this out myself. I needed to use PHP array syntax.

In answer to my own question, the updated code for a nested object would be:

...

return [
    "type" => "Feature",
    "geometry" => array("type" => "Point", "coordinates" => [125.6, 10.1]),
    "properties" => array("name" => "Dinagat Islands")
];

...

Hope this helps someone else as it wasn't immediately apparent to me.


You call globals with the globals' handle, and the field name of a field in it. So, if you have a text input field named "myField" inside a global with handle "myGlobal", you can call it anywhere in your templates with:

{{ myGlobal.myField }}


In a matrix, link to entries with a future date

Forgot to take the entry status into account + an additional check for the sectionhandle to cover the custom route.
This solved it for me, if you have a better suggestion I'm all ears.

{% for link in block.slideLink.status('live', 'pending') %}
    {% if link.section.handle == 'events' %}
        lees verder...
    {% else %}
        lees verder...
    {% endif %}
{% endfor %}


How to dynamically set an include template

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

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

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

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

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

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


Sprout Forms notification test giving me an internal server error

As the original poster reported in the comment above. This "looks like a completely different issue unrelated to email."


Looks like this will do the trick

select * from craft_users
left join craft_entries on craft_users.id = craft_entries.authorId
where craft_entries.authorId is null and craft_users.id <> "1"


Is it possible to manage language translations without directly accessing the PHP translations file?

I'm pretty new to Craft but I'll tell you what I'd try anyway for fun.

I'd create a "Translations" assets source that would hold files like es.csv.

Then, in craft/translations/es.php, I'd run through that CSV and build the array Craft wants and return it.

<?php

$path = $_SERVER['DOCUMENT_ROOT'].'/../craft/translations/translations.csv';
$delimiter = ';';
$skip_rows = 1; //skip header rows
$primary_locale_column = 0; //set primary locale column
$this_locale_column = 1; //set secondary locale column for this `lang.php` file

$translations = array();
$translations_file = fopen($path, 'r');

if ($translations_file) {
    while(($translation = fgetcsv($translations_file, 1024, $delimiter)) !== FALSE) {
        if($skip_rows != 0) { $skip_rows--; continue; }
        $translations[$translation[$primary_locale_column]] = $translation[$this_locale_column];
    }
}

return $translations;

This is totally untested.


Linking a page in a multilanguage website

You could write a Twig macro to get the localized URL from an entry ID. Import it from _layout.html for example.

{% macro urlFromId(entryId) %}
    {%- set entry = craft.entries.id(entryId).first() %}
    {{- entry ? entry.url -}}
{% endmacro %}

{% import _self as macros %}

Then use this more concise code everywhere in your templates.

{{ "Neem contact op"|t }}


How can I notify store owners when stock levels are getting low?

Just like Craft itself, Craft Commerce has lots of events you can have a plugin respond to. In this case, you'd want to write a plugin that waits for commerce_orders.onOrderComplete (per documentation) that evaluates the stock levels and probably uses Craft's Email Service to fire off notifications to the store owners.

Here's some quick pseudocode:

<?php

namespace Craft;

class StockNotificationPlugin extends BasePlugin
{
    ...

    function init()
    {
        craft()->on('commerce_orders.onOrderComplete', function($event)
        {
            // loop through the items in the order ($order->lineItems),
            // checking the remaining stock ($item->purchasable->stock)
            // against your desired threshold, firing off notifications
            // if applicable
        }
    }

    ...
}


Can Craft convert requested url to lowercase?

Depending on how your server is configured, this may be a non-issue. I just checked several sites, and they all work fine with mixed-case URLs.

If that's not the case with your site, you'll need to implement a simple plugin, which checks the URL, converts it to lowercase, and redirects the visitor (if required).

Hopefully it goes without saying that it would be much better to do this in your .htaccess or httpd.conf.

Caveats dispensed with, here's an example plugin.

<?php namespace Craft;

class PoorMansRedirectPlugin extends BasePlugin
{
    // Standard plugin methods omitted for brevity.

    /**
     * Runs before *every* request.
     */
    public function init()
    {
        $path = craft()->request->getPath();
        $lowerPath = strtolower($path);

        // If the current path is not lowercased, redirect.
        if ($path !== $lowerPath) {
            $url = UrlHelper::getUrl($lowerPath);
            craft()->request->redirect($url);
        }
    }
}

Just to be clear, this is a really bare-bones solution.

It doesn't make any distinction between site URLs and admin URLs, doesn't account for query strings, will break POST requests, and a lot more besides.

As such it is in no way production ready, and would require quite a bit of additional work to account for all of these edge (and not so edge) cases. See my previous note regarding .htaccess being vastly preferable.


Adjust all date/times to users current timezone

I wouldn’t recommend hacking the MySQL timezone, since that would happen on a global basis, not just for the current user, and will result in some weird race conditions whenever two people are using the site/CP at the same time.

Getting an entry to go live at a certain time based on their time zone is a fundamentally different thing than the feature provided by entry Post Dates. So your best bet here is to code the functionality yourself, rather than trying to bend Post Dates into something they’re not.

To do that, you should create your own custom Date/Time field to hold the post dates. Give it a handle that’s different than “postDate” so you don’t run into naming conflicts in your templates - let’s say it’s “liveDate”.

In your templates, you can query against your liveDate field like so:

{% set timezone = 'Europe/Paris' %}
{% set localDate = date()|date('Y-m-d H:i:s', timezone) %}
{% set entries = craft.entries.section('blog').liveDate('<=' ~ localDate) %}

That will only give you entries where the liveDate field is before or equal to the current date/time, in whatever timezone you’ve passed in.

Couple gotcha’s:

Twig’s |date filter requires that you specify the timezone using one of PHP’s supported timezone names, not a numeric offset. Your script that is determining the timezone based on the user’s IP will need to account for that.
The built-in Post Date fields will still be enforced, so you will need to get in the habit of making sure those are set at least 14 hours before the custom liveDate field is set (UTC-14 is the earliest time that “tomorrow” begins). That’s assuming you’ve left your system timezone set to UTC in Settings > General.


There is nothing stopping you from shipping what you want whenever, but there is no way to mark a line item as shipped. You would need to have custom fields on your order to track partial shipments, and if you wanted, a 'partially shipped' order status to be able to easily see partially shipped orders in the order index page.


Create an alternate Stripe Payment Method

A no one has come forward and told me it's a bad idea, I've ploughed on and done the following:

Created my own Controller action in my plugin to bypass the payment side of things altogether. I didn't use Omnipay to create my Customer on Stripe in the end. I went with using Stripes own API directly as documented here.

To complete the Order (as-in convert a Cart into a full-blown Order, it was as simple as calling the following Commerce method:
craft()->commerce_orders->completeOrder($cart);


You use q instead of order for the query param in the pagination URLs.


Query Craft CMS Table Field for exact values

Yeah because the data is stored as a JSON blob, I'm not sure this is going to be possible without retrieving all the data, parsing the JSON, then doing the exact match search via PHP, and then returning the results subset.


Craft Commerce - Create Order from Control Panel

There is currently no way to do this from the control panel unfortunately.

It's been said a bunch of times it's on the roadmap, and it's an oft requested feature.

I don't think there is a generalised plugin for this yet.  I doubt there will be as it's coming to core.

It's certainly possible to write one, but it would be a fair bit of work.

(You could quickly hack something together in the form of a form that lists all your products and uses MultiAdd to add them to a cart in one go...but you'd still have to go through checkout to complete the order.  https://github.com/engram-design/MultiAdd


So the solution for me in the end was to add the profile field name as a class name eg 'address1';

 <input id="address1" name="fields[address1]" type="text"
placeholder="address line 1" class="form-control input-md address1" req="">

Then in my ajax post when looping through the errors looking for the element class name

$("." + field).after($errorList);

as opposed to the input field name.

$.post('/', data, function(response) {

    //reset error messages
    $(this).find( '.errors' ).remove();

    if (response.success) {
        console.log("success")
    } else {
        event.preventDefault();
        console.log(response.errors);
        //reset errors
        $('.submit').append('Please check the form for errors');
        appendErrors(response.errors);
    }

});

function appendErrors(errors) {

$.each(errors, function(field, errors) {
    appendError(field, errors);
});
}

function appendError(field, errors) {
   var $errorList = $( '
' );
   $.each(errors, function(index, error) {
     $errorList.append($('
').html(error));
   });
   $("." + field).after($errorList);
   // $('input[name='+ field +']').after($errorList);
}


How to get Structure parent title and ancestors on search results page

Found the answer in getSection()

{{ entry.title }}

  {% set section = entry.getSection() %}
   {{ section }}
  {% set parent1 = entry.getAncestors() %}
   {% for item in parent1 %}
    / {{ item }}
   {% endear %}


Structure a site with multiple locations with categories, structure or "multisite"?

Another idea (since that's what you asked for) might be to leverage Relationships more than Entry Types.

I find that those administering the content have a better time with relationships than Entry Types.

Given that your

e.g. if Golf Pros' was a Structure (instead), which contained a required field called 'Related Location' your content admin person could logically click Entries > New Entry > Golf Pro

One area this could really help would be News. If a News article (Entry) contained the same 'Related Location' field, I think you'd have a better time templating and also provide a more logical publishing experience for your client.

The same could apply to the other Entry Types you have beneath your Locations; About, How to find us, etc.

As I said - it's only another approach - happy to discuss, Tom.

Matt


Sprout Forms: empty phone field still shows error "Phone is invalid. Example format: (506) 234-5678"

This was a bug and is resolved in release Sprout Forms v3.6.9 and Sprout Fields v3.5.2.

Original Reply:

  This is a bug in Sprout Forms v3.6.8. We'll have a fix out for this shortly and I'll update this comment when we do.


The catch is, it sounds like you need to add an entry and modify user data all in a single shot. That's not really possible out-of-the-box... different forms will submit to different places.

Edit an Entry
Edit a User Profile

One possible solution is to create an intermediary page:

First page is your test form,
Upon submission, creates a new entry. (Edit an Entry)
Then redirects to a second page,
Second page displays the results, and offers to "Save the results",
Upon submission, saves results to user profile. (Edit a User Profile)

It's pretty rough, to be honest. If you know any PHP at all, I'd highly recommend using this project to teach yourself a little plugin development. If you build some customized Controllers and Services to handle this data, you'll end up with a far more robust solution.

Sounds like a fun project... Let us know what architecture you decide to go with!


I'd initially forgotten to turn on devMode to find out what the 500 error was. Once I did that I found out it was exhausting the memory. I had to raise it up to 1024M!


I resolved this by adding a new 'home' template which extends my main layout. This resolved the 404 in the header status code.


Should the initial selected subfolder in the 'Add an asset' dialog be the Default Upload Location?

The Default Upload Location setting only applies to drag-n-drop uploads, as well as front-end uploads if you are creating your own entry form with a <input type="file"/> input.

If you want to force files to be placed in a {slug} subfolder, you should check the "Restrict uploads to a single folder" checkbox in your Assets field's settings.


This line:

$this->arraySearch($value,$needle,$depth);

should be

return $this->arraySearch($value,$needle,$depth);


You can actually use a bit of Twig logic to determine your category URI formats.

Try this for your Category URI Format:

{parent ? parent.uri : 'what-we-do'}/{slug}

Which is just a more compact way of doing this:

{% if parent %}{{ parent.uri }}{% else %}what-we-do{% endif %}/{slug}

That will only add 'what-we-do' to the URI if the current category doesn't have a parent (top-level category). Otherwise, it prepends the entire parent's URI.

That should give you URIs like:

/what-we-do/service-one
/what-we-do/service-one/subservice-one-a
/what-we-do/service-one/subservice-one-b
/what-we-do/service-one/subservice-one-b/subservice-one-b-nested


If this is still required I've built a plugin, Redactor Extras: https://github.com/elliotlewis/Redactor-Extras

You can add your own JS via the Plugin. I'm updating and adding plugins from Redactors site regularly. I'll add font-color next. But good use with your own code.


File input attachment format for Contact Form plugin

No, currently it will allow all file types and does not take the extraAllowedFileExtensions config setting into account.


Cannot update Craft, only download the zip

You can do manual installs easily, all you really need it's the app folder inside the craft folder. but don't just replace it.
I would rename the new app folder to something like "app-new" move it next to the other app folder inside craft folder. once it is all done copying. I would rename the "app" folder into "app-old" and rename the "app-new" into "app" again. Then go to your admin URL and craft should do the automatic database changes that it needs. It has worked for me in the past and it's quick and easy and less error prone.


Find entries related to user and display field name

What could work for you is to get the events for each field separatly:

{% set firstEvents = craft.entries.section('events').relatedTo({
    element: user,
    field: 'firstEntriesField'
}) %}

{% set secondEvents = craft.entries.section('events').relatedTo({
    element: user,
    field: 'secondEntriesField'
}) %}

If you need them combined in one loop, I can't think of another way other than to query x + 1 times:

{% set allEvents = craft.entries.section('events').relatedTo({
    element: user,
}) %}

{% set firstEventsIds = craft.entries.section('events').relatedTo({
    element: user,
    field: 'firstEntriesField'
}).ids() %}

{% set secondEventsIds = craft.entries.section('events').relatedTo({
    element: user,
    field: 'secondEntriesField'
}).ids() %}

{% for event in allEvents %}
    {% if event.id in firstEventsIds %}
        {# Event related through firstEntriesField #}
    {% elseif event.id in secondEventsIds %}
        {# Event related through secondEntriesField #}
    {% endif %}
{% endfor %}


AJAX page transitions with history.pushState

So I just went through my article step-by-step and I indeed missed adding the new content into $main. I think there may have been a versioning mistake in the article's code as I definitely tested it.

Anyways, here's an updated loadPage function for you. Which I will be adding to the article tomorrow. I've added a few extra things to ensure that there's no content (like the footer if you include it in layout.twig) jumping when the content switches out, so feel free to modify that.

loadPage = function(href) {

  $main.wrapInner('
');

  /* ----- Get height of new container inside results container and to ensure the footer doesn't jump up -----  */
  var newResultsHeight = $('.new-results-div').outerHeight();
  $main.height(newResultsHeight);

  $('.js-ajax-wrapper').velocity('transition.fadeOut', {
    /* ----- Upon completion of animating out content put user at top of page. ----- */
    complete: function(){
      $('html').velocity("scroll", {
        duration: 0,
        easing: "ease",
        mobileHA: false
      });
    }
  });

  $.ajax({
    type: 'POST',
    url: href,
    data: {},
    success: function(result){
      /* ----- HERE'S WERE THE CONTENT GET'S ADDED ----- */
      $main.html(result);

      /* ----- Wrap content in div so we can get it's height ----- */
      $main.wrapInner('
');

      /* ----- Get height of new container inside results container and set $main to it so there's no content jumpage -----  */
      var newResultsHeight = $('.new-results-div').outerHeight();
      $main.height(newResultsHeight);

      /* ----- Bring In New Content ----- */
      $('.js-main .js-ajax-wrapper').velocity('transition.fadeIn', {
        visibility: 'visible',
        complete: function() {
          /* ----- Removes the temp height from $main ----- */
          $main.css('height', '');
          ajaxLoad();
        }
      });
    },
    error: function(){
      console.log("error.");
      location.reload();
    }
  });

};

Apologize for any trouble due to the missing code. Feel free to let me know if you have any other questions.


Locale Global section in CP bringing back page can not be found

All of your settings look fine.

Most likely what happened is that when you added the new Russian locale under Settings->Locales, the "Resave All Elements" task that needs to run either didn't get a chance to complete or just ran into an error and failed.

If you don't have any Russian content yet, try deleting the locale and re-adding it - making sure that the task in the upper right hand corner completes.  If you already have Russian content, temporarily add any new locale, let the task complete, then delete the temporary locale.  That will also fix it for the Russian locale.


This can be done with a plugin.

The plugin would create an 'activity' table that saves the current user's ID and a timestamp of their last known activity.

You can insert the first row by listening to the onLogin event and remove their row by listening to the onLogout.

All subsequent requests, you'd compare the last known activity timestamp to the userSessionDuration config setting to still see if they have an active session.

You'd also be listening to the onBeforeLogin event. If someone tries to login on another computer, you'd see that they already have a row in the database and their last known activity timestamp is still in a valid userSessionDuration window.  Then you'd pass $performAction = false as a property of the event, and that will prevent the login action from occurring.


Random posts displayed but exclude current entry ID

Here are a couple of useful stackexchange questions

Getting a random entry: Section entries - order by random
Excluding the current entry: Exclude Current Entry

Putting that together:

{% set randomEntry = craft.entries({
  id: 'not '~entry.id,
  order: 'RAND()'
}).first %}

This will fetch entries whose id is not entry.id (entry is the current entry), ordered randomly, and then give you the first of those.

Probably you want to specify the section or sections to fetch your entries from:

{% set randomEntry = craft.entries({
  section: ['thisSection', 'thatSection', 'theOtherSection'],
  id: 'not '~entry.id,
  order: 'RAND()'
}).first %}

This will do the same thing, but only fetch entries from those 3 sections.

After all of that, you should also be sure to test that there actually is an entry:

{% if randomEntry %}
  ... do your thing ...
{% endif %}


Just make a channel for your generic pages, and set the route to {slug}, that way your editors can create, delete, edit and disable any generic template pages without needing to touch the more involved areas of the Control Panel


Setup default Matrix blocks and default values for sub-fields?

It's not possible to have default values for most field types and it is also not possible to have default matrix block already added to your new entries.

If there was an event that gets raised before an entry is displayed in the Control Panel's "Edit Entry" view, you could set default values rather easily with a custom business logic plugin.

There's no such event available yet, but you could vote for the feature request I just added:

Raise an event before entry is edited/loaded in CP


Resolving the $entry variable for custom routes somewhere but a Twig template

Alright, if you didn't already create a custom plugin I suggest you to visit Pluginfactory.io to create the boilerplate. Let's assume your plugin handle is awesome-plugin, just for testing purposes.

You create a controller with the name RouteController that contains the function actionMyAwesomeRoute

In summary:

handle: awesome-plugin
controller: RouteController
function: actionMyAwesomeRoute

All routes in Craft are defined via plugin-handle/controller-name/function-name without the "required" parts - and in snake-case

That means your route will be awesome-plugin/route/my-awesome-route (remove "Controller" and "action")

That being said: you can register your routes in your Plugins main init function

Defining the routes

public function init(){

    Event::on(
        UrlManager::class,
        UrlManager::EVENT_REGISTER_SITE_URL_RULES,
        function(RegisterUrlRulesEvent $event) {
            // easy route for demonstration
            $event->rules[] = [
                'pattern' => '/some-nice-url',
                'route'   => 'awesome-plugin/route/my-awesome-route'
            ];
            // when you visit www.example.com/some-nice-url your function will be executed

            // more complex example if you are not interested
            // skip that and don't think about it
            $event->rules[] = [
                'pattern' => 'my-fancy-route/<action:>/<id:\d+>/<siteHandle:\w+>',
                'route' => 'awesome-plugin/route/<action>',
                'defaults' => [
                    'id' => null,
                    'siteHandle' => null,
                    'action' => 'index'
                ]
            ];
            // you can visit this route via www.example.com/my-fancy-route
            // this way, your actionIndex will be called
            // when you do www.example.com/my-fancy-route/foo
            // Craft tries to call the actionFoo in your Controller
            // www.example.com/my-fancy-route/bar -> actionBar

            // when you do www.example.com/my-fancy-route/edit/5
            // your actionEdit will be called and Craft passes 5 as a parameter

            // www.example.com/my-fancy-route/save-entry/9/siteOne
            // will call actionSaveEntry(9, 'siteOne');

        }
    }
}

Your Controller

public function actionMyAwesomeRoute(){
    $variables = // define whatever you want

    return $this->renderTemplate('path/to/template', $variables);
}

More complex examples, skip that if you don't need it

// www.example.com/my-fancy-route/show-entry/9/siteOne
public function actionShowEntry($id = null, $siteHandle = null){
    // $id will be 9, $siteHandle 'siteOne' since those parameters
    // are passed via URL, otherwise they are both null
    $entry = Craft::$app->getElements()->getElementById($id, $siteHandle);

    return $this->renderTemplate('path/to/template',  ['entry' => $entry]);
}

Just a little bit more complex stuff

You could actually use the default route and inject it with another parameter as well. So When you have an existing route to an entry like news/2018/feb/awesome-news/ you can as well visit this entry with news/2018/feb/awesome-news/fooBar

$event->rules[] = [
    'pattern' => '/<route:\S+>/<something:\S+>',
    'route'   => 'awesome-plugin/route/catch-all'
];

And in your Controller

public function actionCatchAll($route, $something){
    $entry = Craft::$app->getElements()->getElementByUri($route);
    if ($entry instanceof Entry) {
        $section = $event->getSection();
        $siteSettings = $section->getSiteSettings();
        foreach ($siteSettings as $site) {
            if ((int) $site->siteId === (int) $entry->siteId) {
                $templatePath = $site->template; // the template path for the entry
                return $this->renderTemplate(
                    $templatePath,
                        [
                            'entry'     => $entry,
                            'whatever'  => $something
                        ]
                    );
            }
        }
    }

    return Craft::$app->runAction(Craft::$app->getRequest()->getFullPath());
}


Craft 3 plugin to import channel entries

As per your comments, the title field is required and causing a validation error. This can be resolved simply by setting the title.

$entry->title = 'The Title';

You can output the validation errors to be notified of any issues with saving the entries as follows.

if ($result === false) {
    \Craft:dd($entry->getErrors());
}


Canonical url for product with multiple categories

You should be able to use {{ entry.categoryFieldHandle[0].slug }}. The [0] simply grabs the first category in the field.

You will need to make sure there is always at least one category allocated to an entry or you'll get an error.


Craft 3 replacement for the Craft 2 fileCache component?

So, that will quite literally be the cache class then... sheesh, what a day this is.


Can't Upload JPEGs to live site (PEL error)

@lindseydiloreto in the Craft Slack gave me the solution.

For some reason, the craft/app folder got messed up in the last update.

Re-downloading and replacing the /app folder fixed the problem


Conditional to compare segment_1 in the URL with the URI of a specified entry

In Twig you'll never, ever need to nest logic tags, within output tags or visa-versa.

The syntax you're looking for is:

{% set about_entry = craft.entries.id(17).first() %}

{{ about_entry.title }}


How can additional custom fields be added to the Sites settings?

I would say the easiest thing is to create a set of Globals with all site specific settings. You can include custom settings for sites via Behaviors and Events and include your custom HTML and run custom logic to store those values in custom tables but I doubt it's worth it. A global set should be fine.

By the way: GlobalSets are site specific - you don't need to include a site handle or something. Just insert different settings for each site, Craft will fetch the correct one by itself.

If you want to loop a set for all sites

{% for site in craft.app.getSites().getAllSites() %}
    {% set globalSet = craft.app.globals.getSetByHandle('myset', site.id) %}
    {{ globalSet.myValue }}
{% endfor %}


Imager plugin doesn't generate images in multi-language website

Problem solved: imagerSystemPath was set relatively


Freeform - limit who can view 'results' page

The short answer is that freeform doesn't do it.

The longer answer is that you can cheat with a cookie.

I used a cookie. Used this plugin: https://github.com/nystudio107/cookies

In Freeform's admin, I created a hidden field called "cookie validator" and added it to my form.

On the page that my form lives on /form/, I added a cookie that sets a value of a large random number.

{% set randomNumber = random(9999999999) %}
{{ setCookie('formcookie', randomNumber, now | date_modify("+1 hour").timestamp ) }}

{{ form.render }}

{% set cookievalueJS %}
  $(document).ready(function() {
    $('#form-input-cookievalidator').val("{{ randomNumber }}");
 });
{% endset %}
{% includeJs cookievalueJS %}

This creates a random number first. Then sets a cookie with that number as a value. And then sets the input field to have that same number.

Then, on my form results page /form/results/

Instead of using an 'isSuccess' check, I check for the cookie & field value...

{% set submissionId = segment2 %}
{% set form = craft.freeform.form('myForm') %}
{% set submission = craft.freeform.submissions({
    id: submissionId,
    form: formHandle,
}).first() %}

{% if myCookie  ==  submission.cookievalidator %}
  {# render all the submission data here #}
{% else %}
  {# show the "nothing to show here" page #}
{% endif %}

NOTE: I'm going to move this to an all https site eventually, and will probably have to swap the cookie to be a securecookie. But that's pretty straightforward with the cookie plugin.