Introduction

The CASH Music platform gives all musicians access to tools that let them manage, promote, and sell their music online — all owned and controlled themselves.

Everything is modular. We built up around a PHP core. The admin app, API, and each element are separate applications that work together to form a full platform. We’ve built around the concept of cooperation, so we’ve abstracted all third party services so artists can easily change mailing providers, payment processors, etc. And we’ve kept dependencies as lightweight as possible so functionality can play well with other apps like WordPress, Drupal, etc.

We host a free (now and forever) version of the CASH Music platform at cashmusic.org or the whole thing can be downloaded and installed on your own server. A few simple changes to settings switch the platform from single-user mode to mutli-user (hosted) mode.

In short: the CASH Music platform was built to create possibilies for artists on the open web.

In these docs we’ll cover:

  • Working directly with the PHP Core
  • Our APIs (verbose and RESTful)
  • Defining connections to third party services
  • Building elements (custom workflow for embeds)
  • Working with our admin app

Useful Links

Github / Twitter / Facebook / Development Group

Introduction: Setup

One of our goals is for this to run in as many places as possible, so we’ve worked hard to keep the requirements minimal:

  • PHP 5.4+
  • PDO (a default) and MySQL OR SQLite
  • mod_rewrite (for admin app)
  • fopen wrappers OR cURL

For local testing and development all you need to get started is VirtualBox, Vagrant 1.4+, and this repo. Just fork, install VirtualBox and Vagrant, then open a terminal window and in the repo directory type:

Vagrant will fire up a VM, set up Apache, install the platform, and start serving a special dev website with tools, docs, and a live instance of the platform — all mapped right to http://localhost:8888.

If you want to go beyond the basic setup included wth our vagrant scripts, you’ll nbeed to edit the /framework/settings/cashmusic.ini.php file. We include a template (cashmusic_template.ini.php) and the settings are pretty straightforward. You can change database settings, modify default system salt for password security, set timezone and email settings, and switch between single or multi-user mode

Introduction: Code Standards

When in doubt, code for legibility and easy adoption. Capitalization and CamelCase should be used for class names, camelCase starting with lowercase for function names, and variable names in snake_case (lowercase and underscores.) Indentation has been kept simple — a single hard tab for each level, with curly brackets on the same line as the control statement.

So a simplified file will look something like:

PHP Core

All functionality of the platform is accessed through a consistent request/response model at the heart of the PHP Core. No direct function calls should be made — instead data should be accessed and set through a secure and standard request/response model.

The request/response model lets us build consistency from PHP to API and into elements and connections. It mimics a REST-style API and standardizes calls and responses across the methods.

Every request is made with a specific type and action, plus any required or optional parameters. It’s response will contain an http-esque status code, a uid containing type/action/code, a human-readable status message, a more detailed contextual message, an echo of the request type and action, a payload with the full response data or false if the request failed, the api version, and a timestamp.

Initiating a PHP Request looks like this:

An example of a failed response object:

Or on success:

The payload is returned as an associative array. Most basic data requests will include creation and modification dates which are standard and automated in the system. Requests to create new resources will return an id number on success.

All core files are located in the repo at /framework/classes/core with requests divided by type and organized into individual plant classes at /framework/classes/plants. Most new functionality is defined at the plant level, with the core classes used to route requests, to plants, abstract database connections, etc.

Each plant includes a routing table for requests that points to internal functions and defines the authentication context under which they’re allowed. See below for a complete list of requests exposed by the core.

PHP Core: Calendar requests

All actions defined for ‘calendar’ type requests:

calendar / addevent

Allowed methods:

Parameters:

  • date (REQUIRED)
  • user_id (REQUIRED)
  • venue_id (REQUIRED)
  • purchase_url (default: ”)
  • comment (default: ”)
  • published (default: 0)
  • cancelled (default: 0)

calendar / addvenue

Allowed methods:

Parameters:

  • name (REQUIRED)
  • city (REQUIRED)
  • address1 (REQUIRED)
  • address2 (REQUIRED)
  • region (REQUIRED)
  • country (REQUIRED)
  • postalcode (REQUIRED)
  • url (REQUIRED)
  • phone (REQUIRED)
  • user_id (REQUIRED)

calendar / deleteevent

Allowed methods:

Parameters:

  • event_id (REQUIRED)

calendar / deletevenue

Allowed methods:

Parameters:

  • venue_id (REQUIRED)

calendar / editevent

Allowed methods:

Parameters:

  • event_id (REQUIRED)
  • date (default: false)
  • venue_id (default: false)
  • purchase_url (default: false)
  • comment (default: false)
  • published (default: false)
  • cancelled (default: false)
  • user_id (default: false)

calendar / editvenue

Allowed methods:

Parameters:

  • venue_id (REQUIRED)
  • name (default: false)
  • address1 (default: false)
  • address2 (default: false)
  • city (default: false)
  • region (default: false)
  • country (default: false)
  • postalcode (default: false)
  • url (default: false)
  • phone (default: false)

calendar / findvenues

Allowed methods:

Parameters:

  • query (REQUIRED)
  • user_id (REQUIRED)
  • page (default: 1)
  • max_returned (default: 12)

calendar / getallvenues

Allowed methods:

Parameters:

  • user_id (REQUIRED)
  • visible_event_types (REQUIRED)

calendar / getevent

Allowed methods:

Parameters:

  • event_id (REQUIRED)

calendar / getevents

Allowed methods:

Parameters:

  • user_id (REQUIRED)
  • offset (default: 0)
  • published_status (default: 1)
  • cancelled_status (default: 0)
  • cutoff_date_low (default: false)
  • cutoff_date_high (default: false)
  • visible_event_types (default: ‘upcoming’)

calendar / geteventsnostatus

Allowed methods:

Parameters:

  • user_id (REQUIRED)
  • visible_event_types (default: ‘upcoming’)
  • cutoff_date_low (default: false)
  • cutoff_date_high (default: false)

calendar / getvenue

Allowed methods:

Parameters:

  • venue_id (REQUIRED)

PHP Core: Element requests

All actions defined for ‘element’ type requests:

element / addelementtocampaign

Allowed methods:

Parameters:

  • element_id (REQUIRED)
  • campaign_id (REQUIRED)

element / addcampaign

Allowed methods:

Parameters:

  • title (REQUIRED)
  • description (REQUIRED)
  • user_id (REQUIRED)
  • elements (default: ‘[]’)
  • metadata (default: ‘{}’)

element / addelement

Allowed methods:

Parameters:

  • name (REQUIRED)
  • type (REQUIRED)
  • options_data (REQUIRED)
  • user_id (REQUIRED)

element / addlockcode

Allowed methods:

Parameters:

  • element_id (REQUIRED)

element / checkuserrequirements

Allowed methods:

Parameters:

  • user_id (REQUIRED)
  • element_type (REQUIRED)

element / deletecampaign

Allowed methods:

Parameters:

  • id (REQUIRED)
  • user_id (default: false)

element / deleteelement

Allowed methods:

Parameters:

  • id (REQUIRED)
  • user_id (default: false)

element / editelement

Allowed methods:

Parameters:

  • id (REQUIRED)
  • name (REQUIRED)
  • options_data (REQUIRED)
  • user_id (default: false)

element / editcampaign

Allowed methods:

Parameters:

  • id (REQUIRED)
  • user_id (default: false)
  • title (default: false)
  • description (default: false)
  • elements (default: false)
  • metadata (default: false)
  • template_id (default: false)

element / getanalytics

Allowed methods:

Parameters:

  • analtyics_type (REQUIRED)
  • user_id (REQUIRED)
  • element_id (default: 0)

element / getanalyticsforcampaign

Allowed methods:

Parameters:

  • id (REQUIRED)

element / getcampaign

Allowed methods:

Parameters:

  • id (REQUIRED)
  • user_id (default: false)

element / getelement

Allowed methods:

Parameters:

  • id (REQUIRED)
  • user_id (default: false)

element / getcampaignsforuser

Allowed methods:

Parameters:

  • user_id (REQUIRED)

element / getcampaignforelement

Allowed methods:

Parameters:

  • id (REQUIRED)

element / getelementsforcampaign

Allowed methods:

Parameters:

  • id (REQUIRED)

element / getelementsforuser

Allowed methods:

Parameters:

  • user_id (REQUIRED)

element / getelementtemplate

Allowed methods:

Parameters:

  • element_id (REQUIRED)
  • return_template (default: false)

element / getmarkup

Allowed methods:

Parameters:

  • id (REQUIRED)
  • status_uid (REQUIRED)
  • original_request (default: false)
  • original_response (default: false)
  • access_method (default: ‘direct’)
  • location (default: false)
  • geo (default: false)
  • donottrack (default: false)

element / getsupportedtypes

Allowed methods:

Parameters:

  • force_all (default: false)

element / redeemcode

Allowed methods:

Parameters:

  • code (REQUIRED)
  • element_id (REQUIRED)

element / removeelementfromcampaign

Allowed methods:

Parameters:

  • element_id (REQUIRED)
  • campaign_id (REQUIRED)

element / setelementtemplate

Allowed methods:

Parameters:

  • element_id (REQUIRED)
  • template_id (REQUIRED)
  • user_id (default: false)

API

Our API is a standalone app located in the repo at /interfaces/api/ and available at the /api/ URL at cashmusic.org and locally for testing. Like the admin app, all configuration is handled by the constants.php file located in the app root, and all requests are routed through the controller.php script via mod_rewrite.

The main API functionality is handled by the APICore class, located at /interfaces/api/classes/APICore.php.

The API returns JSON objects for any request. Simply requesting / at the /api gives a basic hello with version number:

A bad request returns a more standard format with status code and messages:

We’ll detail succesful requests in the verbose and RESTful docs below

API: Verbose API

The verbose API is a direct wrapper for all requests that allow the apipublic or apikey access methods. At the moment those are mostly limited to list signups and initiating new transactions, but we’re working on a full authorization scheme to expand scope.

The response object and payload are nearly identical to the return from the PHP core, except returned as JSON. Making a request that doesn’t allow API access will give you a forbidden status, but here’s an example endpoint:

bn,zz֬

The format is simple: /verbose/plant/request/{parameter name}/{parameter value} — it’ll parse as many parameters as you throw at it and respond:

More about authorization methods coming soon.

API: RESTful API

The RESTful version of the API is still a work in progress.

Connections

Connections are basically API wrappers — an abstracted way for us to connect to third party services, categorize their offering, and define the data we need to store to make each one work. (Tokens, settings, etc.)

Defining a connection happens in two different files:

  1. A definition JSON in /framework/settings/connections
  2. A Seed Class in /framework/classes/seeds

The connection is defined in JSON and should look something like this:

It needs a name, description, unique type, and you must define the classname of the seed used with the connection. There’s an option for scope. This lets us narrow the connection type so we can show connections contextually in the admin app and beyond. (Currently, the scope types have been chosen fairly arbitrarily, though we need to define them better going forward.) Lastly, there’s an array listing compatibility — does the connection work in single-user mode, multi-user mode, or both?

Note that you can define different data needed for single-user and multi-user versions of the connection. (We frown on storing keys/secrets in multi-user mode.)

The Seed class

In the larger scheme of the platform, Plants handle requests while Seeds handle specific functionality — mostly in the form of connecting to third party APIs. Because seeds are supposed to be flexible, they’re pretty arbitrary in structure, but you’ll notice a bunch of common functions between similar seeds — check seeds for S3 and Google Drive.

We’ll be defining this more concretely on a scope-by-scope basis soon, but for now please look for similar services and pattern after them. We want as much uniformity as we can get at the seed level so we can abstract as much as possible at the plant level.

Any connection supporting an OAuth style redirect (generally in multi-user mode) will need to have these two functions defined:

  • getRedirectMarkup($data=false)
  • handleRedirectReturn($data=false)

The getRedirectMarkup will handle any logic needed to present the user with a redirect link, and the handleRedirectReturn will deal with the returned token and complete the connection process.

Any data like application-level keys that need to be stored to initiate OAuth requests can be stored in /framework/settings/connections.json. See template at /framework/settings/_connections.json for a quick example.

Elements

Elements bundle unique workflows in the CASH Music platform. Think of them as apps accessing the core the same way apps access APIs on a phone. We took inspiration from app stores in the idea of creating a simple bundle that’s easy to use and configure.

Each element has a PHP class that follows a set pattern, mustache templates for markup, an image for thumbs, a LICENSE, and an accompanying JSON definition file. The rest is handled automatically. All settings forms are generated by the admin app itself (and could even be set manually from PHP if you feel like going gusto.)

The main element class defines and responds to various states — usually triggered by GET or POST request via embed. The element listens for its own id and reacts to all matching CASH Responses.

An element is embedded with a single function call, and will respond automatically when interacted with. The whole idea being that it’s a simple to use structure that can be powerful and flexible enough to innovate on top of the PHP core.

Elements: Structure

Each element is essentially a bundle all to itself, with three main parts:

  1. An app.json definition file that defines element messages and data to be stored
  2. A Class file with logic and states for the element
  3. A folder of mustache templates (markup views for every state)

Data and settings for each instance of an element are stored as encrypted JSON in the database, and defined in an element’s app.json file. The app.json file defines all details like title and description of the element, instructions, and the data structure along with labels, messages, etc. A detailed sample file is better than a description here:

The options define what can be set in the element admin, what options will be present and expected, and provide default values, etc.

The allowed types for options are:

  • select
    • values (required)
  • boolean
  • number
  • text
  • markup

And every option can also contain:

  • required
  • default
  • displaysize
  • helptext
  • placeholder

The main element class

The main element class file extends the ElementBase class, automating most of the state management and template selection you’ll need. The logic can be as complex as necessary,
but in the end the goal is to define some data into $this->element_data and return it as the output of the getData() function. Whatever you define will be added to the stored data and accessible in your mustache templates.

In the example below, note that we use the $this->setTemplate() function to choose a template other than the (required) default.mustache file. This is based on filenames in the template folder, and controlled by state — the CASH Response UID returned the the last request. So if the element contains a form that triggers a CASH Request via GET or POST the element responds, sets its internal state, and your getData() function does some magic based on state before returning the data needed to render your embed.

By separating the getData from rendering output we allow elements to work at all states on a purely data level — leaving room in the future for mobile app support, etc. But for now the data is combined with the mustache template you choose to render HTML in the browser.

This is just a starting point. For more examples see the /framework/classes/elements directory in the main platform repo.

Elements: Embedding

We use our (tiny) custom javascript library, cashmusic.js, to create iframe embeds for elements. They can be styled to match any site with full user control over CSS and markup. Embedding is pretty straightforward and happens with a single copy and paste code.

In a basic example, an element is embedded in place by id only:

Endpoint and id are always required, but you can also choose to have the element appear in an overlay (lightboxed.) A lightboxed element will create a link inline with the caption passed in to the window.cashmusic.embed function. You can also pass in an object specifying size and position of the element inside the overlay.

For embed calls after page load, provide a target element as the final argument to window.cashmusic.embed. This will place the embed, iframe or lightbox link, inside the first matching element. The target should be a string that will work with document.querySelector, like “#id”, “#id .class”, or similar.

For styling, all iframe embeds are placed in a <div> classed with “cashmusic embed” and lightboxed embed links are placed in a <span> classed “cashmusic embed”.

An example with all options:

We’re also working on a new JSON object based embed call. It’s mostly for clear formatting, but you’ll notice a new CSS override option not available by the standard method. More on that soon..

Admin app

The admin app for the platform (/interfaces/admin) is a fairly straight-forward MVC-style webapp built with a front controller, individual controllers for each route, mustache views, and using the framework for the model instead of a traditional database layer. Basically it’s dog-fooding the PHP core but building a much more complex app than a simple element.

In terms of structure, it’s fairly simple:

  • Settings are stored in the constants.php file
  • The .htaccess pushes all traffic through the controller.php file
  • Each route has a controller in /components/pages/controllers and after doing any logic the controller calls a mustache template view from /components/pages/views
  • The main page UI is stored in mustache templates in /ui/default

It shouldn’t be lost that the admin app is structured to mirror the CASH Request/Response types — this is very much on purpose with the goal of getting musicians and developers speaking the same language.