Skip to content

WordPress plugin to manage pledges for signature collections

Notifications You must be signed in to change notification settings

grueneschweiz/collectme

Repository files navigation

Collectme V2

.github/workflows/tests.yml Crowdin

The community loves to promise signatures in pledges. We love signatures. This WordPress plugin helps to push the community to fulfill their promises. It mixes some gamification elements with pressure by control, and it makes achievements visible to the community.

While a first version was developed for the @jungegruene, this is a complete rewrite, that is here to grow.

User Guide

For folks that just want to use employ the plugin on their site.

Setup

  1. Grab the latest release and install it like any other plugin.
  2. Head over to Settings > Collectme and create a new cause.
  3. Check out the causes settings on the same page.
  4. Copy the shortcode and paste it into your post or page.
  5. Visit the post or page and follow the instructions.

Generating Links that Log-in the Users Automatically

  1. Populate the wp_collectme_account_tokens table with the data of the users you want to log in automatically. The token must contain exactly 64 alpha-numeric characters (e.g. SHA256 hash).
  2. You may then send the users their personal link, which must look like follows: https://example.com/post-with-collectme-shortcode?action=create&email=<email>&token=<token>

Variant: You may also use the collectme_get_account_token filter and craft your own token validation function. See /docs/link-auth-with-mailchimp.md for an example.

Adding Goals from External Data

The action collectme_after_user_setup is built for this purpose. It fires every time a user is added to a cause.

Example:

add_action( 'collectme_after_user_setup', function( 
        \Collectme\Model\Entities\User $user,
        string $groupUuid, 
        string $causeUuid 
    ) {
    // add your logic to grab the objective data from an external source
    // assign the goal to $count, a string identifying the source to $source
    $count = 123;
    $source = 'Form XY';
    
    $objective = new \Collectme\Model\Entities\Objective(
        null,
        $count,
        $groupUuid,
        $source
    );
    
    try {
        $objective->save();
    } catch (\Collectme\Exceptions\CollectmeDBException $e) {
        // it's possibly ok to just ignore the error as the
        // user will be prompted by the application to set a
        // goal if he hasn't got one yet.
    }
}, 10, 3 );

Email Notifications

The plugin can send the following email notifications:

Collection reminders:

  • Start collecting reminder to users that have created an account but haven't entered any signatures yet.
  • Continue collecting reminder to users that haven't entered any signatures for some time (see the Timing section below) and haven't reached their goal. If no goal was set, no reminder is sent.

Goal related:

  • Thank you for adding a personal goal to users that have added their first goal.
  • Thank you for upgrading your personal goal for users that raised their goal.
  • Thank you for achieving your personal goal for users that entered at least as many signatures as their personal goal.
  • Thank you for achieving the final goal for users that reached the highest goal.
  • Thank you for achieving your personal goal and upgrading it afterwards to users that reached their goal and have set a higher one after.

Timing

There is a section Scheduled E-Mails on the settings page of the plugin that controls the timing of the emails. A good starting point is 21 days for the reminder emails and 24 hours for the goal related emails. If the fields are left blank, no emails will be sent.

The mail scheduler also respects the collection start date and the collection end date. No emails are sent before the start and after the end date. If the dates are left blank, the mailer won't stop sending emails.

Customizing the emails

You can customize the email subject and body using the string override feature on the plugin's settings page.

Dev Guide

For the cool kids that want to contribute 😎

Architecture

The plugin is primarily built as single page application (SPA) using Vue.js and the WordPress REST API.

The REST API is specified in OpenAPI 3.0 format (the specification: docs/api/rest-api.yaml). It is JSON:API Spec compliant.

The settings page and the SPAs home are classic HTML pages and not Vue components. The SPAs home also handles part of the login process (see docs/uml/login-flow.puml).

Vue JS

We use the standard installation of Vue3 with Typescript, Vue Router 4, Pinia for state management and Vite for the build setup.

The sources live in /app and the build files are output to /dist.

Asset loading

To get seamless integration of the Vuejs dev environment with WordPress, we had to get messy with the asset loading. It works as follows:

Non dev mode

Unless you set define( 'SCRIPT_DEBUG', true ); in wp-config.php the static built assets from /dist are served, as in production.

Run docker-compose run node yarn build to rebuild the /dist files.

Dev mode

To enable dev mode, add define( 'SCRIPT_DEBUG', true ); to your wp-config.php. In this setup this is already the case, via the WORDPRESS_CONFIG_EXTRA environment variable in docker-compose.yml.

In dev mode, the vue scripts are the loaded directly from the vite dev server, which runs under localhost:3000 (cf. docker-compose.yml). If you need to change the hostname or port, use the NODEJS_DEV_SERVER_BASE_URL environment variable in the docker-compose.yml.

See AssetLoader::getScriptUrls() for further details. Yes, it is hacky :)

Components

Paths:

Component Naming:

  • PascalCase file names
  • General components names are prefixed with Base
  • Components naming rules:
    • At least two words
    • The prefix for components, that should only ever have a single active instance (e.g. TheBaseOverlay)
    • Tightly coupled or specific child components include the parent component name as prefix.
    • Start with the highest level words and put more specific ones after (e.g. SearchButtonClear.vue)

HTML Class Naming:

  • Wrapping container: collectme-{component-name} e.g. collectme-the-base-overlay
  • Inner elements: collectme-{component-name}__{specific-tag} e.g. collectme-the-base-overlay__title

CSS:

  • Don't use the styles scoped attribute (so themes can overwrite the plugins styles)

Testing

Is set up (vitest and cypress) but not used.

Node

All JS building in done with Yarn in the node container.

# install dependencies
docker-compose run node yarn install

# build for production
docker-compose run node yarn run build

See app/package.json for details.

WordPress

To get good IDE integration and nice tooling with Composer Autoloading, the PHP parts of the plugin use PSR-4 and PSR-12 instead of the WordPress Coding Standards.

For the sake of testability, we use dependency injection with PHP-DI. Testing is done with PHPUnit.

The plugin need at least PHP 8.1.

Testing

# install test environement
docker-compose run wordpress bash -c \
  'cd wp-content/plugins/collectme \
  && TMPDIR=$(pwd)/tmp bin/install-wp-tests.sh collectme_test root "" mysql latest'

# run tests
docker-compose run wordpress bash -c \
  'cd wp-content/plugins/collectme \
  && php vendor/bin/phpunit'

Set PHP_IDE_CONFIG='serverName=Docker' in your IDEs run configuration if you want to run tests directly in your IDE. serverName=Docker corresponds to the server name configured in your IDE (Settings > PHP > Servers > Name for PHPStorm).

Please note also, that the WP_TESTS_DIR="$(pwd)/tmp/wordpress-tests-lib" environment variable is set in /phpunit.xml.dist. It must point to the same directory as TMPDIR, you've used for installing the tests.

WordPress-Cli

The WordPress container also contains the WP-CLI. Currently it is only used to extract the plugin translation template (.pot file) and to compile the .mo files:

# extract translations
docker-compose run wordpress bash -c 'XDEBUG_MODE=off php -d memory_limit=1G $(which wp) --allow-root i18n make-pot wp-content/plugins/collectme/ wp-content/plugins/collectme/languages/collectme.pot --slug=collectme --domain=collectme --exclude=tmp,vendor --skip-js --skip-block-json --skip-theme-json && chown 1000:1000 wp-content/plugins/collectme/languages/collectme.pot'

# generate mo files
docker-compose run wordpress bash -c 'XDEBUG_MODE=off $(which wp) --allow-root i18n make-mo wp-content/plugins/collectme/languages'

Composer

Dependecies are managed with Composer. To install the dependencies, run:

docker-compose run wordpress composer --working-dir=wp-content/plugins/collectme install

Mailhog

Mails sent by the Plugin are caught by Mailhog and accessible under localhost:8020.

Swagger

Editor

In browser editor and preview for the openapi definition of our rest-api.

docker-compose run swagger-editor

Visit localhost:8030

Helpful resources:

Codegen

  • Generate type definitions in /app/src/models/generated

    docker-compose run swagger-codegen generate -i /tmp/swagger/input/rest-api.yaml -o /tmp/swagger/output -l typescript-axios
    sudo chown -R $(id -u):$(id -g) gen
    sed -i '/import type/! s/import /import type /g' gen/models/*.ts
    sed -i -r 's/(\s)Date(\s)/\1string\2/g' gen/models/*.ts
    rsync -av --delete gen/models/ app/src/models/generated
  • Generate API docs in /docs/api/index.html

    docker-compose run swagger-codegen generate -i /tmp/swagger/input/rest-api.yaml -o /tmp/swagger/output -l html2 
    sudo chown -R $(id -u):$(id -g) gen
    cp gen/index.html docs/api/index.html 

Helpful resources:

  • docker-compose run swagger-codegen langs
  • docker-compose run swagger-codegen generate
  • Swagger-Codegen on GitHub

L10N - Crowdin

Localization is done with gettext and Crowdin. The workflow:

  1. Use translation functions as usual ( see How to Internationalize Your Plugin)
  2. Create a pull request
  3. The translation template (.pot file) is generated and uploaded to Crowdin by the GitHub action l10n.yml.
  4. Translate in Crowdin
  5. Merge pull request into main
  6. The translations are downloaded from Crowdin, mo-files are compiled and committed to by the GitHub action l10n.yml.

See also crowdin.yml.

Translate JS