├── source ├── CNAME ├── includes │ ├── _plugin_extending_intro.md │ ├── _plugin_misc.md │ ├── _plugin_services.md │ ├── _plugin_migrations_3.0.md │ ├── _plugin_misc_helpers.md │ ├── _plugin_migrations.md │ ├── _api_endpoints.md │ ├── _plugin_services_parameters.md │ ├── _plugin_misc_helpers_datetime.md │ ├── _plugin_services_cookie_helper.md │ ├── _plugin_services_plugin_config_helper.md │ ├── _plugin_misc_helpers_input.md │ ├── _plugin_intro.md │ ├── _api_authorization_basic.md │ ├── _plugin_services_ip_lookup_helper.md │ ├── _plugin_services_users.md │ ├── _plugin_mvc.md │ ├── _plugin_services_paths_helper.md │ ├── _api_rate_limiter.md │ ├── _plugin_services_factory.md │ ├── _plugin_services_database.md │ ├── _api_libraries.md │ ├── _plugin_migrations_1.2.md │ ├── _plugin_services_model_factory.md │ ├── _plugin_services_event_dispatcher.md │ ├── _plugin_services_session.md │ ├── _api_authorization.md │ ├── marketplace.md │ ├── _plugin_services_router.md │ ├── _plugin_integrations_builder.md │ ├── _plugin_migrations_2.0.md │ ├── _plugin_services_security.md │ ├── _api_intro.md │ ├── _plugin_misc_commands.md │ ├── _plugin_misc_flashes.md │ ├── _plugin_services_request.md │ ├── _plugin_structure.md │ ├── _plugin_extending_api.md │ ├── _plugin_integrations_migrations.md │ ├── _plugin_extending_broadcasts.md │ ├── _plugin_misc_variant_entities.md │ ├── _plugin_services_translator.md │ ├── _plugin_extending_categories.md │ ├── _plugin_extending_points.md │ ├── _plugin_extending_maintenance.md │ ├── _plugin_misc_translated_entities.md │ ├── _plugin_extending_pages.md │ ├── _plugin_services_mail_helper.md │ ├── _plugin_mvc_models.md │ ├── _plugin_misc_helpers_chartquery.md │ ├── _api_endpoint_themes.md │ ├── _api_endpoint_files.md │ ├── _plugin_translations.md │ ├── _mauticjs_api_reference.md │ ├── _plugin_integrations_sync.md │ ├── _api_endpoint_tags.md │ ├── _plugin_integrations.md │ ├── _plugin_manipulating_contacts.md │ ├── _cache.md │ ├── _plugin_integrations_configuration.md │ ├── _plugin_misc_events.md │ ├── _api_endpoint_notes.md │ ├── _introduction.md │ ├── _plugin_install.md │ ├── _api_endpoint_categories.md │ ├── _api_endpoint_roles.md │ ├── _plugin_misc_forms.md │ └── _plugin_configuration.md ├── javascripts │ ├── all.js │ ├── all_nosearch.js │ ├── app │ │ ├── search.js │ │ ├── toc.js │ │ └── lang.js │ └── lib │ │ ├── jquery.highlight.js │ │ ├── energize.js │ │ └── jquery_ui.js ├── images │ ├── logo.png │ └── navbar.png ├── fonts │ ├── icomoon.eot │ ├── icomoon.ttf │ ├── icomoon.woff │ └── icomoon.svg ├── stylesheets │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── syntax.css.scss.erb │ ├── icon-font.scss │ ├── print.css.scss │ └── variables.scss └── index.md ├── .travis.yml ├── Rakefile ├── .gitignore ├── Dockerfile ├── LICENSE ├── CONTRIBUTING.md ├── README.md ├── CHANGELOG.md ├── .github └── workflows │ └── ci.yml ├── Gemfile ├── config.rb └── Gemfile.lock /source/CNAME: -------------------------------------------------------------------------------- 1 | developer.mautic.org -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9.3 3 | - 2.0.0 4 | -------------------------------------------------------------------------------- /source/includes/_plugin_extending_intro.md: -------------------------------------------------------------------------------- 1 | ## Extending Mautic -------------------------------------------------------------------------------- /source/includes/_plugin_misc.md: -------------------------------------------------------------------------------- 1 | ## Miscellaneous 2 | 3 | 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'middleman-gh-pages' 2 | 3 | task :default => [:build] 4 | -------------------------------------------------------------------------------- /source/javascripts/all.js: -------------------------------------------------------------------------------- 1 | //= require_tree ./lib 2 | //= require_tree ./app 3 | -------------------------------------------------------------------------------- /source/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/images/logo.png -------------------------------------------------------------------------------- /source/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/fonts/icomoon.eot -------------------------------------------------------------------------------- /source/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/fonts/icomoon.ttf -------------------------------------------------------------------------------- /source/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/fonts/icomoon.woff -------------------------------------------------------------------------------- /source/images/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/images/navbar.png -------------------------------------------------------------------------------- /source/includes/_plugin_services.md: -------------------------------------------------------------------------------- 1 | ## Services 2 | 3 | These are the services used commonly throughout Mautic. 4 | -------------------------------------------------------------------------------- /source/includes/_plugin_migrations_3.0.md: -------------------------------------------------------------------------------- 1 | ### 3.0.0 2 | See [UPGRADE-3.0.md](https://github.com/mautic/mautic/blob/features/UPGRADE-3.0.md) -------------------------------------------------------------------------------- /source/javascripts/all_nosearch.js: -------------------------------------------------------------------------------- 1 | //= require_tree ./lib 2 | //= require_tree ./app 3 | //= stub ./app/search.js 4 | //= stub ./lib/lunr.js 5 | -------------------------------------------------------------------------------- /source/stylesheets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/stylesheets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /source/includes/_plugin_misc_helpers.md: -------------------------------------------------------------------------------- 1 | ### Helpers 2 | 3 | Mautic has many helper classes available. A few of the most commonly used ones are highlighted below. -------------------------------------------------------------------------------- /source/stylesheets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/stylesheets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /source/stylesheets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/stylesheets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /source/stylesheets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/stylesheets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /source/stylesheets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautic/developer-documentation/HEAD/source/stylesheets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /source/includes/_plugin_migrations.md: -------------------------------------------------------------------------------- 1 | ## Migrations/Deprecations 2 | 3 | The following is a list of deprecations and migrations required to support latest versions of Mautic.____ -------------------------------------------------------------------------------- /source/includes/_api_endpoints.md: -------------------------------------------------------------------------------- 1 | ## Endpoints 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .phpintel 6 | coverage 7 | InstalledFiles 8 | lib/bundler/man 9 | pkg 10 | rdoc 11 | spec/reports 12 | test/tmp 13 | test/version_tmp 14 | tmp 15 | *.DS_STORE 16 | build/ 17 | .cache 18 | 19 | # YARD artifacts 20 | .yardoc 21 | _yardoc 22 | doc/ 23 | .idea/ 24 | /.project 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | 3 | RUN apt-get update 4 | RUN apt-get install -yq ruby ruby-dev build-essential 5 | RUN gem install --no-ri --no-rdoc bundler 6 | ADD Gemfile /app/Gemfile 7 | ADD Gemfile.lock /app/Gemfile.lock 8 | RUN cd /app; bundle install 9 | ADD . /app 10 | EXPOSE 4567 11 | WORKDIR /app 12 | CMD ["bundle", "exec", "middleman", "server"] -------------------------------------------------------------------------------- /source/includes/_plugin_services_parameters.md: -------------------------------------------------------------------------------- 1 | ### Config Parameters 2 | ```php 3 | get('mautic.helper.core_parameters'); 5 | 6 | $apiEnabled = $config->getParameter('helloworld_api_enabled', false); 7 | ``` 8 | 9 | * Service name: `mautic.helper.core_parameters` 10 | * Class: `Mautic\CoreBundle\Helper\CoreParametersHelper` 11 | -------------------------------------------------------------------------------- /source/includes/_plugin_misc_helpers_datetime.md: -------------------------------------------------------------------------------- 1 | #### Date/Time Helper 2 | 3 | The date/time helper can be used to convert between UTC and the local timezone. 4 | 5 | ```php 6 | toUtcString(); 10 | 11 | // refer to the class for other functions 12 | ``` 13 |
14 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_cookie_helper.md: -------------------------------------------------------------------------------- 1 | ### Cookie Helper 2 | 3 | ```php 4 | get('mautic.helper.cookie'); 7 | $cookieHelper->setCookie('name', 'value', 3600); 8 | 9 | $cookieHelper->deleteCookie('name'); 10 | ``` 11 | 12 | * Service name: `mautic.helper.cookie` 13 | * Class: `Mautic\CoreBundle\Helper\CookieHelper` 14 | 15 | The cookie helper can be used to set cookies based on system settings. -------------------------------------------------------------------------------- /source/includes/_plugin_services_plugin_config_helper.md: -------------------------------------------------------------------------------- 1 | ### Plugin Config Helper 2 | ```php 3 | get('mautic.helper.bundle'); 5 | 6 | $menu = $configHelper->getBundleConfig('HelloWorldBundle', 'menu', true); 7 | ``` 8 | 9 | * Service name: `mautic.helper.bundle` 10 | * Class: `Mautic\CoreBundle\Helper\BundleHelper` 11 | 12 | This can be used to get the configuration array of a bundle/plugin's Config/config.php file. 13 | -------------------------------------------------------------------------------- /source/includes/_plugin_misc_helpers_input.md: -------------------------------------------------------------------------------- 1 | #### Input Helper 2 | 3 | The input helper can be used to ensure clean strings from user input. 4 | 5 | ```php 6 | -------------------------------------------------------------------------------- /source/includes/_plugin_intro.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | Plugins are bundles that can extend the functionality of Mautic. They can be very simple or very complex and have access to leverage pretty much all that Symfony offers. Just as as reminder, the basics are covered in this document. If more advanced processes are required, the [Symfony book](http://symfony.com/doc/2.8/book/index.html) and [cookbook](http://symfony.com/doc/2.8/cookbook/index.html) will be valuable resources. 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2008-2013 Concur Technologies, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | not use this file except in compliance with the License. You may obtain 5 | a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | License for the specific language governing permissions and limitations 13 | under the License. -------------------------------------------------------------------------------- /source/includes/_api_authorization_basic.md: -------------------------------------------------------------------------------- 1 | ### Basic Authentication 2 | 3 | As of Mautic 2.3.0, support for basic authentication can be enabled through Mautic's Configuration -> API Settings. As with OAuth2, it is only recommended to use basic authentication over HTTPS. 4 | 5 | To authorize a request for basic authentication, set an `Authorization` header. 6 | 7 | 1. Combine the username and password of a Mautic user with a colon `:`. For example, `user:password`. 8 | 2. Base64 encode the string from above. `dXNlcjpwYXNzd29yZA==`. 9 | 3. Add an Authorization header to each API request as `Authorization: Basic dXNlcjpwYXNzd29yZA==` -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Mautic developer documentation 2 | 3 | Thanks for contributing to Mautic's developer documentation! A couple of quick guidelines for submitting PRs: 4 | 5 | - Please point your pull requests at the `master` branch, and keep your commit messages clear and informative. 6 | - Please make sure your contributions work in the most recent version of Chrome, Firefox, and IE. 7 | - If you're implementing a new feature, even if it's relatively small, it's nice to open an issue before you start so that others know what you're working on and can help make sure you're on the right track. 8 | 9 | Thanks again! Happy coding. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This documentation is deprecated. Use https://github.com/mautic/developer-documentation-new instead. 2 | 3 | ### Mautic Developer Documentation ### 4 | 5 | Developer documentation for Mautic. 6 | 7 | The content is published at https://developer.mautic.org. 8 | 9 | It is generated using [Slate](https://github.com/slatedocs/slate), a great API documentation generator. The content of Mautic's documentation is located in the source/includes folder. 10 | 11 | ### Updating the live documentation 12 | 13 | The live documentation at https://developer.mautic.org is updated automatically using GitHub Actions when a new commit is pushed to master. 14 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_ip_lookup_helper.md: -------------------------------------------------------------------------------- 1 | ### IP Lookup Helper 2 | ```php 3 | get('mautic.helper.ip_lookup'); 5 | 6 | $requestIp = $ipHelper->getIpAddressFromRequest(); // 1.2.3.4 7 | 8 | /** @var \Mautic\CoreBundle\Entity\IpAddress $ipAddressEntity */ 9 | $ipAddressEntity = $ipHelper->getIpAddress(); 10 | 11 | /** @var array $details */ 12 | $details = $ipAddressEntity->getIpDetails(); 13 | 14 | echo $details['city']; 15 | ``` 16 | 17 | * Service name: `mautic.helper.ip_lookup` 18 | * Class: `Mautic\CoreBundle\Helper\IpLookupHelper` 19 | 20 | This helper can be used to retrieve the real IP for the request. 21 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_users.md: -------------------------------------------------------------------------------- 1 | ### User 2 | 3 | ```php 4 | get('mautic.helper.user')->getUser(); 6 | 7 | $firstName = $user->getFirstname(); 8 | $lastName = $user->getLastname(); 9 | $email = $user->getEmail(); 10 | $profile = $user->getProfile(); 11 | 12 | $role = $user->getRole()->getName(); 13 | 14 | if ($role->isAdmin()) { 15 | // do something 16 | } 17 | ``` 18 | 19 | * Service name: `mautic.helper.user` 20 | * Class: `Mautic\CoreBundle\Helper\UserHelper` 21 | 22 | `getUser()` will return the [entity](#database), `\Mautic\UserBundle\Entity\User` that can then be used to get information about the currently logged in user. -------------------------------------------------------------------------------- /source/includes/_plugin_mvc.md: -------------------------------------------------------------------------------- 1 | ## MVC 2 | 3 | Mautic uses a Model-View-Controller structure to manage user interaction on the frontend (views) with the underlying code processes (controllers and models). ([Entity and Repository classes](#database) are also used to manage interaction with the database). 4 | 5 | In Symfony, and thus Mautic, the controller is the center of the MVC structure. The route requested by the user determines the controller method to call. The controller will then communicate with the model to get or manipulate data then display it to the user through the view. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_paths_helper.md: -------------------------------------------------------------------------------- 1 | ### Paths Helper 2 | ```php 3 | get('mautic.helper.paths'); 5 | 6 | $relativeImagesDir = $pathsHelper->getSystemPath('images'); // media/images 7 | $absoluteImageDir = $pathsHelper->getSystemPath('images', true); // /home/user/public_html/media/images 8 | ``` 9 | 10 | * Service name: `mautic.helper.paths` 11 | * Class: `Mautic\CoreBundle\Helper\PathsHelper` 12 | 13 | This helper should be used when retrieving system paths for Mautic (images, themes, cache, etc) 14 | 15 | There is also a `tmp` or `temporary` option to store temporary files. It should be used by developers for such use case instead of the general `cache` location. -------------------------------------------------------------------------------- /source/includes/_api_rate_limiter.md: -------------------------------------------------------------------------------- 1 | 2 | ## API Rate limiter 3 | 4 | You can configure rate limiter cache in local.php 5 | By default, filesystem is used as: 6 | ```php 7 | api_rate_limiter_cache => [ 8 | 'type' => 'file_system', 9 | ], 10 | ``` 11 | 12 | You can configure memcached server: 13 | ```php 14 | 'api_rate_limiter_cache' => [ 15 | 'memcached' => [ 16 | 'servers' => 17 | [ 18 | [ 19 | 'host' => 'localhost', 20 | 'port' => 11211 21 | ] 22 | ] 23 | ] 24 | ], 25 | ``` 26 | 27 | Or whatever cache you want described in [Symfony cache documentation](https://symfony.com/doc/current/bundles/DoctrineCacheBundle/reference.html). 28 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_factory.md: -------------------------------------------------------------------------------- 1 | ### Factory Service 2 | 3 | `\Mautic\Factory\MauticFactory` is deprecated as of 2.0 and will be phased out through 2.x release cycles and completely removed in 3.0. Direct dependency injection into the services should be used instead where possible. 4 | 5 | For [controllers](#controllers), extend either `\Mautic\CoreBundle\Controller\CommonController` or `\Mautic\CoreBundle\Controller\FormController` and it will be available via `$this->factory` by default. Otherwise, obtain the factory from the service container via `$factory = $this->container->get('mautic.factory');` 6 | 7 | For [models](#models), it will be available via `$this->factory` by default. 8 | 9 | For custom [services](#services), pass 'mautic.factory' as an argument and MauticFactory will be passed into the __construct of the service. -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 1.1 4 | 5 | *July 27th, 2014* 6 | 7 | **Fixes:** 8 | 9 | - Finally, a fix for the redcarpet upgrade bug 10 | 11 | ## Version 1.0 12 | 13 | *July 2, 2014* 14 | 15 | [View Issues](https://github.com/tripit/slate/issues?milestone=1&state=closed) 16 | 17 | **Features:** 18 | 19 | - Responsive designs for phones and tablets 20 | - Started tagging versions 21 | 22 | **Fixes:** 23 | 24 | - Fixed 'unrecognized expression' error 25 | - Fixed #undefined hash bug 26 | - Fixed bug where the current language tab would be unselected 27 | - Fixed bug where tocify wouldn't highlight the current section while searching 28 | - Fixed bug where ids of header tags would have special characters that caused problems 29 | - Updated layout so that pages with disabled search wouldn't load search.js 30 | - Cleaned up Javascript 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build developer docs (and deploy on push) 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: 2.3.1 19 | bundler-cache: true 20 | - name: install deps 21 | run: bundle install 22 | - name: build docs 23 | run: bundle exec middleman build 24 | - name: deploy docs 25 | if: ${{ github.event_name == 'push' }} 26 | run: bundle exec middleman deploy 27 | env: 28 | SSH_HOST: ${{ secrets.SSH_HOST }} 29 | SSH_DEPLOYMENT_PATH: ${{ secrets.SSH_PATH }} 30 | SSH_USER: ${{ secrets.SSH_USER }} 31 | SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} 32 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # If you have OpenSSL installed, we recommend updating 2 | # the following line to use "https" 3 | source 'http://rubygems.org' 4 | 5 | gem "rouge", "1.7.2" 6 | 7 | gem "middleman", "~>3.3.0" 8 | 9 | # For syntax highlighting 10 | gem "middleman-syntax" 11 | 12 | # Plugin for middleman to generate Github pages 13 | gem 'middleman-gh-pages' 14 | 15 | # For deploying to Mautic's live server 16 | gem 'middleman-deploy', '~> 1.0' 17 | 18 | # Live-reloading plugin 19 | gem "middleman-livereload", "~> 3.3.0" 20 | 21 | gem 'redcarpet', '~> 3.2.1' 22 | 23 | # For faster file watcher updates on Windows: 24 | gem "wdm", "~> 0.1.0", :platforms => [:mswin, :mingw] 25 | 26 | # Cross-templating language block fix for Ruby 1.8 27 | platforms :mri_18 do 28 | gem "ruby18_source_location" 29 | end 30 | 31 | gem "rake", "~> 12.3.3" 32 | 33 | gem 'therubyracer', :platforms => :ruby 34 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_database.md: -------------------------------------------------------------------------------- 1 | ### Database/Entity Manager 2 | ```php 3 | getDoctrine()->getManager(); 6 | $repository = $em->getRepository('HelloWorldBundle:World'); 7 | $worlds = $repository->getEntities(); 8 | 9 | /** @var \MauticPlugin\HelloWorldBundle\Entity\World $world */ 10 | foreach ($worlds as $world) { 11 | $world->upVisitCount(); 12 | } 13 | 14 | $repository->saveEntities($worlds); 15 | ``` 16 | 17 | Doctrine includes an ORM and DBAL layers. 18 | 19 | ORM/entity manager: 20 | 21 | * Service name: `doctrine.orm.default_entity_manager` 22 | * Class: `Doctrine\ORM\EntityManager` 23 | 24 | DBAL (direct DB driver): 25 | 26 | * Service name: `doctrine.dbal.connection` 27 | * Class: `Doctrine\DBAL\Connection` 28 | 29 | The entity manager can be used to interact with the bundle's repositories and entities. See [Database](##database) for more info. -------------------------------------------------------------------------------- /source/stylesheets/syntax.css.scss.erb: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2008-2013 Concur Technologies, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | License for the specific language governing permissions and limitations 14 | under the License. 15 | */ 16 | 17 | @import 'variables'; 18 | 19 | <%= Rouge::Themes::Base16::Monokai.render(:scope => '.highlight') %> 20 | 21 | .highlight .c, .highlight .cm, .highlight .c1, .highlight .cs { 22 | color: #909090; 23 | } 24 | 25 | .highlight, .highlight .w { 26 | background-color: $code-bg; 27 | } -------------------------------------------------------------------------------- /source/includes/_api_libraries.md: -------------------------------------------------------------------------------- 1 | ## Libraries 2 | 3 | ### PHP Library 4 | 5 | Mautic provides a [PHP library on Github](https://packagist.org/packages/mautic/api-library). It is recommended that it be used in PHP projects. Other languages will need to use custom means and/or a 3rd party library to handle the OAuth/request processes. 6 | 7 | #### Install via Composer 8 | 9 | To install using composer, simply run `composer require mautic/api-library`. 10 | 11 | #### Install Manually 12 | 13 | [Download the package from Github](https://github.com/mautic/api-library/archive/master.zip). Extract then include the following code in your project (of course change the file path if needed): 14 | 15 |
16 | require_once __DIR__ . '/lib/Mautic/MauticApi.php';
17 | 
18 | 19 | -------------------------------------------------------------------------------- /source/stylesheets/icon-font.scss: -------------------------------------------------------------------------------- 1 | %icon { 2 | font-family: 'FontAwesome'; 3 | speak: none; 4 | font-style: normal; 5 | font-weight: normal; 6 | font-variant: normal; 7 | text-transform: none; 8 | line-height: 1; 9 | } 10 | 11 | %icon-exclamation-sign { 12 | @extend %icon; 13 | content: "\f12a"; 14 | } 15 | %icon-question-sign { 16 | @extend %icon; 17 | content: "\f128"; 18 | } 19 | %icon-info-sign { 20 | @extend %icon; 21 | content: "\f129"; 22 | } 23 | %icon-remove-sign { 24 | @extend %icon; 25 | content: "\f00d"; 26 | } 27 | %icon-plus-sign { 28 | @extend %icon; 29 | content: "\f067"; 30 | } 31 | %icon-minus-sign { 32 | @extend %icon; 33 | content: "\f068"; 34 | } 35 | %icon-ok-sign { 36 | @extend %icon; 37 | content: "\f00c"; 38 | } 39 | %icon-search { 40 | @extend %icon; 41 | content: "\f002"; 42 | } 43 | %icon-open { 44 | @extend %icon; 45 | content: "\f0da"; 46 | } 47 | %icon-close { 48 | @extend %icon; 49 | content: "\f0d7"; 50 | } 51 | -------------------------------------------------------------------------------- /source/includes/_plugin_migrations_1.2.md: -------------------------------------------------------------------------------- 1 | ### 1.2.0 2 | 3 | 1.2.0 deprecated the Mautic Addon with it's successor, the Plugin. Mautic addons will continue to work but should be migrated to a plugin before Mautic 2.0 when support will be dropped. 4 | 5 | The migration path is as follows: 6 | 7 | 1. Move the plugin from addons/ to plugins/ 8 | 2. Replace the namespace `MauticAddon` with `MauticPlugin` 9 | 3. Replace the MauticFactory getModel() notation of `addon.` to `plugin.` 10 | 4. Replace the permission notation of `addon:` to `plugin:` 11 | 5. Change the plugin's bundle class to extend `PluginBundleBase` instead of `AddonBundleBase` 12 | 6. In the plugin's bundle class, use the function's `onPluginInstall()` and `onPluginUpdate()` instead of the deprecated `onInstall()` and `onUpdate()` 13 | 7. Migrate Entity use of annotations for ORM to the static PHP function `loadMetadata()` 14 | 8. Migrate Entity use of annotations for the serializer (used with the REST API) to the static PHP function `loadApiMetadata()` -------------------------------------------------------------------------------- /source/includes/_plugin_services_model_factory.md: -------------------------------------------------------------------------------- 1 | ### Model Factory 2 | 3 | ```php 4 | hasModel($channel)) { 10 | $model = $modelFactory->getModel($channel); 11 | 12 | if ($entity = $model->getEntity($channelId)) { 13 | echo $entity->getName(); 14 | } 15 | } 16 | ``` 17 | 18 | `Mautic/CoreBundle/Factory/ModelFactory` can be used in services that a model dependency is unknown at the time the service is created. This is great for scenarios where the channel and channel ID are stored in a database and the executing code needs to obtain information on the channel entity (name, etc). 19 | 20 | It has two methods: `hasModel($modelNameKey)` and `getModel($modelNameKey)`. `hasModel` simple checks to see if a model exists. It uses the same format as using the controller helper method `getModel()`. For example, to obtain the `Mautic\EmailBundle\Model\EmailModel` class, you could use something like the code example. 21 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_event_dispatcher.md: -------------------------------------------------------------------------------- 1 | ### Event Dispatcher 2 | ```php 3 | get('event_dispatcher'); 6 | if ($dispatcher->hasListeners(HelloWorldEvents::ARMAGEDDON)) { 7 | $event = $dispatcher->dispatch(HelloWorldEvents::ARMAGEDDON, new ArmageddonEvent($world)); 8 | 9 | if ($event->shouldPanic()) { 10 | throw new \Exception("Run for the hills!"); 11 | } 12 | } 13 | ``` 14 | 15 | * Service name: `event_dispatcher` 16 | * Class: `Symfony\Component\EventDispatcher\EventDispatcher` 17 | * Implements `Symfony\Component\EventDispatcher\EventDispatcherInterface` (When type hinting, use this class since different environments may use different classes for the dispatcher) 18 | * Docs: [http://symfony.com/doc/2.8/components/event_dispatcher/introduction.html#dispatch-the-event](http://symfony.com/doc/2.8/components/event_dispatcher/introduction.html#dispatch-the-event) 19 | 20 | Dispatch [custom events](#custom-events) using the `event_dispatcher` service. 21 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_session.md: -------------------------------------------------------------------------------- 1 | ### Session 2 | 3 | ```php 4 | get('session'); 7 | $requestSession = $request->getSession(); // Shortcut to session 8 | 9 | // Get all session parameters 10 | $all = $session->all(); 11 | 12 | // Get specific parameter setting mars as default 13 | $world = $session->get('helloworld.world', 'mars'); 14 | 15 | // Check if a parameter exists 16 | if ($session->has('helloworld.world')) { 17 | // do something 18 | } 19 | 20 | // Set a session parameter 21 | $session->set('helloworld.world', 'mars'); 22 | 23 | // Remove a session parameter 24 | $session->remove('helloworld.world'); 25 | 26 | // Clear the whole session 27 | $session->clear(); 28 | ``` 29 | 30 | * Service name: `session` 31 | * Class: `Symfony\Component\HttpFoundation\Session` 32 | * Docs: [http://symfony.com/doc/2.8/components/http_foundation/sessions.html](http://symfony.com/doc/2.8/components/http_foundation/sessions.html) 33 | 34 | To access the session service in a view, use `$app->getSession()`. -------------------------------------------------------------------------------- /source/includes/_api_authorization.md: -------------------------------------------------------------------------------- 1 | 2 | ## Authorization 3 | 4 | Mautic uses OAuth or Basic Authentication (as of Mautic 2.3.0) for API authorization. It supports both [OAuth 1a](http://tools.ietf.org/html/rfc5849) and [OAuth 2](https://tools.ietf.org/html/rfc6749); however, as of 1.1.2, the administrator of the Mautic instance must choose one or the other. Of course OAuth 2 is only recommended for servers secured behind SSL. Basic authentication must be enabled in Configuration -> API Settings. 5 | 6 | The Mautic administrator should enable the API in the Configuration -> API Settings. This will add the "API Credentials" to the admin menu. A client/consumer ID and secret should then be generated which will be used in the following processes. 7 | 8 | All authorization requests should be made to the specific Mautic instances URL, i.e. `https://your-mautic.com`. 9 | 10 | -------------------------------------------------------------------------------- /source/includes/marketplace.md: -------------------------------------------------------------------------------- 1 | # Marketplace 2 | 3 | Mautic 4 comes with a Marketplace directly in the Mautic administration user interface and command line interface as well. 4 | 5 | ## Marketplace under the hood 6 | 7 | The Marketplace use [Packagist](https://packagist.org) and [Composer](https://getcomposer.org) v2 under the hood. Packagist API is used to list the Mautic plugins and find the plugin details. Composer v2 is used to install and update the plugins. Composer will take care of the dependencies of your plugin and also compatibility with different Mautic, PHP and other dependecies versions. 8 | 9 | ## How to list a plugin in the Mautic Marketplace 10 | 11 | Please check the resources on the [WIP new Developer Documentation](https://mautic-developer.readthedocs.io/en/latest/marketplace/listing.html) 12 | 13 | ## How to get included in the allow-list 14 | 15 | Please check the resources on the [WIP new Developer Documentation](https://mautic-developer.readthedocs.io/en/latest/marketplace/listing.html) 16 | 17 | ## Best Practices 18 | 19 | Please check the best practices on the [WIP new Developer Documentation](https://mautic-developer.readthedocs.io/en/latest/marketplace/best_practices.html) 20 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_router.md: -------------------------------------------------------------------------------- 1 | ### Router 2 | 3 | ```php 4 | get('router'); 7 | 8 | // Relative URL 9 | $url = $router->generateUrl('plugin_helloworld_admin'); 10 | 11 | // URL with placeholders 12 | $url = $router->generateUrl('plugin_helloworld_world', array('%world%', 'mars')); 13 | 14 | // Absolute URL 15 | $absoluteUrl = $router->generateUrl('plugin_helloworld_admin', array(), true); 16 | ``` 17 | 18 | ```php 19 | generate('plugin_helloworld_admin'); // result is /hello/admin 22 | 23 | // Generate a path in a view template 24 | $path = $view['router']->path('plugin_helloworld_admin'); // result is /hello/admin 25 | $url = $view['router']->url('plugin_helloworld_admin'); // result is http://yoursite.com/hello/admin 26 | 27 | ``` 28 | 29 | * Service name: `router` 30 | * Class: `Symfony\Bundle\FrameworkBundle\Routing\Router` 31 | * Docs: [https://symfony.com/doc/2.8/routing.html#generating-urls](https://symfony.com/doc/2.8/routing.html#generating-urls) 32 | 33 | For views, use the `$view['router']` helper. The difference with the [template helper](#router-helper) is that `url()` or `path()` is used instead of `generateUrl()` of the `router` service. 34 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | set :css_dir, 'stylesheets' 2 | 3 | set :js_dir, 'javascripts' 4 | 5 | set :images_dir, 'images' 6 | 7 | set :fonts_dir, 'fonts' 8 | 9 | set :markdown_engine, :redcarpet 10 | 11 | set :markdown, :fenced_code_blocks => true, :smartypants => true, :disable_indented_code_blocks => true, :prettify => true, :tables => true, :with_toc_data => true, :no_intra_emphasis => true 12 | 13 | # Activate the syntax highlighter 14 | activate :syntax 15 | 16 | # This is needed for Github pages, since they're hosted on a subdomain 17 | activate :relative_assets 18 | set :relative_links, true 19 | 20 | # Build-specific configuration 21 | configure :build do 22 | # For example, change the Compass output style for deployment 23 | activate :minify_css 24 | 25 | # Minify Javascript on build 26 | activate :minify_javascript 27 | 28 | # Enable cache buster 29 | # activate :asset_hash 30 | 31 | # Use relative URLs 32 | # activate :relative_assets 33 | 34 | # Or use a different image path 35 | # set :http_prefix, "/Content/images/" 36 | end 37 | 38 | activate :deploy do |deploy| 39 | deploy.method = :sftp 40 | deploy.port = 22 41 | deploy.path = ENV["SSH_DEPLOYMENT_PATH"] 42 | deploy.host = ENV["SSH_HOST"] 43 | deploy.user = ENV["SSH_USER"] 44 | deploy.password = ENV["SSH_PASSWORD"] 45 | end 46 | -------------------------------------------------------------------------------- /source/includes/_plugin_integrations_builder.md: -------------------------------------------------------------------------------- 1 | ### Integration Builders 2 | Builders can register itself as a "builder" for email and/or landing pages. 3 | 4 | #### Registering the Integration as a Builder 5 | To tell the IntegrationsBundle that this integration has configuration options, tag the integration or support class with `mautic.config_integration` in the plugin's `app/config.php`. 6 | 7 | ```php 8 | [ 12 | // ... 13 | 'integrations' => [ 14 | // ... 15 | 'helloworld.integration.builder' => [ 16 | 'class' => \MauticPlugin\HelloWorldBundle\Integration\Support\BuilderSupport::class, 17 | 'tags' => [ 18 | 'mautic.builder_integration', 19 | ], 20 | ], 21 | // ... 22 | ], 23 | // ... 24 | ], 25 | // ... 26 | ]; 27 | ``` 28 | 29 | The `BuilderSupport` class must implement `\Mautic\IntegrationsBundle\Integration\Interfaces\BuilderInterface`. 30 | 31 | The only method currently defined for the interface is `isSupported` which should return a boolean if it supports the given feature. Currently, Mautic supports `email` and `page` (landing pages). This will determine what themes should be displayed as an option for the given builder/feature. 32 | -------------------------------------------------------------------------------- /source/includes/_plugin_migrations_2.0.md: -------------------------------------------------------------------------------- 1 | ### 2.0.0 2 | 3 | The big ticket item with 2.0.0 is the deprecation of MauticFactory which will be phased out during the 2.x release cycles and to be removed in 3.0. Where possible, please use direct dependency injection of services rather than relying on MauticFactory. 4 | 5 | 1. MauticFactory deprecated - use dependency injection of required services. Many MauticFactory helper functions have been replaced with [services](#services). 6 | 2. Models need to be registered as services using a specific nomenclature. See [Models](#models) for more information. 7 | 3. The static callback function for campaign actions and decisions has been deprecated. Please see [Extending Campaigns](#extending-campaigns) for more information on the new event based method. (Form submit actions, point triggers, and the like will follow suit with a similar migration throughout the lifecycle of 2.0 but for now still uses the static callback method). 8 | 4. Minimum PHP version has been increased to 5.6.19 and thus newer PHP code goodies are available for developers (traits, etc) 9 | 5. Themes have been completely revamped although the old format is still supported for now. Please see [Themes](#themes) for the new format. 10 | 6. Some routes for /leads/* were removed in favor of /contacts/*. I.e. /api/leads is now /api/contacts, /s/leads is now /s/contacts, and so forth. The route names were also updated. For example, `mautic_lead_action` is now `mautic_contact_action` and so forth. -------------------------------------------------------------------------------- /source/includes/_plugin_services_security.md: -------------------------------------------------------------------------------- 1 | ### Security 2 | 3 | ```php 4 | get('mautic.security'); 7 | 8 | // Check if user is granted a single permission 9 | if ($security->isGranted('plugin:helloWorld:worlds:view')) { 10 | // do something 11 | } 12 | 13 | // Check if user is granted multiple permissions (must be granted to all to be true) 14 | if ($security->isGranted( 15 | array( 16 | 'plugin:helloWorld:worlds:view', 17 | 'plugin:helloWorld:worlds:create', 18 | ) 19 | ) 20 | ) { 21 | //do something 22 | } 23 | 24 | // Check if user is granted to at least one permission 25 | if ($security->isGranted( 26 | array( 27 | 'plugin:helloWorld:worlds:view', 28 | 'plugin:helloWorld:worlds:edit', 29 | ), 30 | 'MATCH_ONE' 31 | ) 32 | ) { 33 | //do something 34 | } 35 | 36 | // Get an array of user permissions 37 | $permissions = $security->isGranted( 38 | array( 39 | 'plugin:helloWorld:worlds:view', 40 | 'plugin:helloWorld:worlds:edit', 41 | ), 42 | 'RETURN_ARRAY' 43 | ); 44 | 45 | if ($permissions['plugin:helloWorld:worlds:view']) { 46 | // do something 47 | } 48 | 49 | // Check to see if a user is anonymous (not logged in) 50 | if ($security->isAnonymous()) { 51 | // do something 52 | } 53 | ``` 54 | 55 | * Service name: `mautic.security` 56 | * Class: `Mautic\CoreBundle\Security\Permissions\CorePermissions` 57 | 58 | Using the service to check permissions is explained more in [Using Permissions](#using-permissions). -------------------------------------------------------------------------------- /source/includes/_api_intro.md: -------------------------------------------------------------------------------- 1 | # REST API 2 | 3 | Mautic provides a REST API to manipulate leads and/or obtain information for various entities of Mautic. 4 | 5 | 8 | 9 | ## Error Handling 10 | 11 | If an OAuth error is encountered, it'll be a JSON encoded array similar to: 12 | 13 |
14 | {
15 |   "error": "invalid_grant",
16 |   "error_description": "The access token provided has expired."
17 | }
18 | 
19 | 20 | If a system error encountered, it'll be a JSON encoded array similar to: 21 | 22 |
23 | {
24 |     "error": {
25 |         "message": "You do not have access to the requested area/action.",
26 |         "code": 403
27 |     }
28 | }
29 | 
30 | 31 | ## Mautic version check 32 | 33 | In case your API service wants to support several Mautic versions with different features, you might need to check the version of Mautic you communicate with. Since Mautic 2.4.0 the version number is added to all API response headers. The header name is `Mautic-Version`. With Mautic PHP API library you can get the Mautic version like this: 34 | 35 | ``` 36 | // Make any API request: 37 | $api = $this->getContext('contacts'); 38 | $response = $api->getList('', 0, 1); 39 | 40 | // Get the version number from the response header: 41 | $version = $api->getMauticVersion(); 42 | ``` 43 | `$version` will be in a semantic versioning format: `[major].[minor].[patch]`. For example: `2.4.0`. If you'll try it on the latest GitHub version, the version will have `-dev` at the end. Like `2.5.1-dev`. 44 | -------------------------------------------------------------------------------- /source/includes/_plugin_misc_commands.md: -------------------------------------------------------------------------------- 1 | ### Commands 2 | 3 | Support for new CLI commands can be added using [Symfony's console component](http://symfony.com/doc/2.8/console.html). 4 | 5 | #### Moderated Commands 6 | 7 | ```php 8 | checkRunStatus($input, $output)) { 44 | return 0; 45 | } 46 | 47 | // Execute some stuff 48 | 49 | // Complete this execution 50 | $this->completeRun(); 51 | 52 | return 0; 53 | } 54 | } 55 | ``` 56 | 57 | Mautic provide an method for moderating commands meaning it will only allow one instance to run at a time. To utilize this method, extend the `Mautic\CoreBundle\Command\ModeratedCommand` class. 58 | 59 | -------------------------------------------------------------------------------- /source/includes/_plugin_misc_flashes.md: -------------------------------------------------------------------------------- 1 | ### Flash Messages 2 | 3 | ```php 4 | addFlash( 9 | 'mautic.translation.key', 10 | array('%placeholder%' => 'some text'), 11 | 'notice', // Notification type 12 | 'flashes', // Translation domain 13 | $addNotification // Add a notification entry 14 | ); 15 | ``` 16 | 17 | ```php 18 | translator->trans(, 23 | array( 24 | '%placeholder%' => 'some text' 25 | ), 26 | 'flashes' 27 | ); 28 | $this->session->getFlashBag()->add('notice', $translatedString); 29 | ``` 30 | 31 | To create an alert, aka flash message, you can use the flash bag in the session. 32 | 33 | If your controller extends one of [Mautic's common controllers](#controllers), you can simply use the helper function `addFlash()`. 34 | 35 | From a model, or any service, you can use the session to obtain the flash bag. 36 | 37 | `$flashBag = $this->get('session')->getFlashBag();` 38 | 39 | ### Notifications 40 | 41 | ```php 42 | addNotification($message, $type, $isRead, $header, $iconClass, new \DateTime()); 46 | ``` 47 | 48 | ```php 49 | addNotification($message, $type, $isRead, $header, $iconClass, $datetime ); 54 | 55 | ``` 56 | Mautic also has a notification center. By default, addFlash() will also add a notification to the center. But, a message can be manually added as well. 57 | 58 | Controllers can use the helper function while models and other services can obtain the NotificationModel. 59 | -------------------------------------------------------------------------------- /source/includes/_plugin_services_request.md: -------------------------------------------------------------------------------- 1 | ### Request 2 | ```php 3 | get('request_stack')->getCurrentRequest(); 6 | 7 | // $_GET 8 | $get = $request->query->all(); 9 | 10 | // $_POST 11 | $post = $request->request->all(); 12 | 13 | // $_COOKIE 14 | $cookies = $request->cookies->all(); 15 | 16 | // $_SERVER 17 | $server = $request->server->all(); 18 | 19 | // Headers 20 | $headers = $request->headers->all(); 21 | 22 | // Attributes - custom parameters 23 | $headers = $request->attributes->all(); 24 | 25 | // Check if a parameter exists 26 | if ($request->request->has('hello')) { 27 | // do something 28 | } 29 | 30 | // Retrieve value of a specific parameter setting mars as default 31 | $world = $request->query->get('world', 'mars'); 32 | 33 | // Set custom request value 34 | $request->attributes->set('hello', 'world'); 35 | 36 | // Get the value of a nested array 37 | $mars = $request->request->get('world[mars]', array(), true); 38 | ``` 39 | 40 | * Service name: `request_stack` 41 | * Class: `Symfony\Component\HttpFoundation\RequestStack` 42 | * Docs: [http://symfony.com/doc/2.8/book/service_container.html#book-container-request-stack](http://symfony.com/doc/2.8/book/service_container.html#book-container-request-stack) 43 | 44 | There are multiple ways to obtain the request service. 45 | 46 | If the controller is extending one of [Mautic's controllers](#controllers), it is already available via `$this->request`. Alternatively, Symfony will [auto-inject the request object](http://symfony.com/doc/2.8/book/controller.html#the-request-object-as-a-controller-argument) into the controller action method if the variable is type-hinted as `Symfony\Component\HttpFoundation\Request`. 47 | 48 | For services, pass the `request_stack` service then use `$request = $requestStack->getCurrentRequest()`. 49 | 50 | From within a view, use `$app->getRequest()`. -------------------------------------------------------------------------------- /source/includes/_plugin_structure.md: -------------------------------------------------------------------------------- 1 | ## Plugin Directory Structure 2 | 3 | ```php 4 | 23 | - - - Config/
24 | - - - - - config.php
25 | - - - HelloWorldBundle.php 26 | 27 | Read more about the [config file](#plugin-config-file). 28 | 29 | The HelloWorldBundle.php file registers the bundle with Symfony. See the code block for the minimum required code. 30 | 31 | A typical MVC plugin may look something like: 32 | 33 | HelloWorldBundle/
34 | - - - [Assets/](#asset-helper)
35 | - - - - - - images/
36 | - - - - - - - - - earth.png
37 | - - - - - - - - - mars.png
38 | - - - - - - helloworld.js
39 | - - - - - - helloworld.css
40 | - - - [Config/](#plugin-config-file)
41 | - - - - - - config.php
42 | - - - [Controller/](#controllers)
43 | - - - - - - DefaultController.php
44 | - - - [Model/](#models)
45 | - - - - - - ContactModel.php
46 | - - - - - - WorldModel.php
47 | - - - [Translations/](#translations)/
48 | - - - - - - en_US/
49 | - - - - - - - - - flashes.ini
50 | - - - - - - - - - messages.ini
51 | - - - [Views/](#views)
52 | - - - - - - Contact/
53 | - - - - - - - - - form.html.php
54 | - - - - - - World/
55 | - - - - - - - - - index.html.php
56 | - - - - - - - - - list.html.php
57 | - - - HelloWorldBundle.php
58 | - - - HelloWorldEvents.php 59 | 60 | Each of the other directories and files are explained elsewhere in the document. -------------------------------------------------------------------------------- /source/javascripts/app/search.js: -------------------------------------------------------------------------------- 1 | (function (global) { 2 | 3 | var $global = $(global); 4 | var content, darkBox, searchResults; 5 | var highlightOpts = { element: 'span', className: 'search-highlight' }; 6 | 7 | var index = new lunr.Index(); 8 | 9 | index.ref('id'); 10 | index.field('title', { boost: 10 }); 11 | index.field('body'); 12 | index.pipeline.add(lunr.trimmer, lunr.stopWordFilter); 13 | 14 | $(populate); 15 | $(bind); 16 | 17 | function populate() { 18 | $('h1, h2, h3, h4').each(function() { 19 | var title = $(this); 20 | var body = title.nextUntil('h1, h2, h3, h4'); 21 | index.add({ 22 | id: title.prop('id'), 23 | title: title.text(), 24 | body: body.text() 25 | }); 26 | }); 27 | } 28 | 29 | function bind() { 30 | content = $('.content'); 31 | darkBox = $('.dark-box'); 32 | searchResults = $('.search-results'); 33 | 34 | $('#input-search').on('keyup', search); 35 | } 36 | 37 | function search(event) { 38 | unhighlight(); 39 | searchResults.addClass('visible'); 40 | 41 | // ESC clears the field 42 | if (event.keyCode === 27) this.value = ''; 43 | 44 | if (this.value) { 45 | var results = index.search(this.value).filter(function(r) { 46 | return r.score > 0.0001; 47 | }); 48 | 49 | if (results.length) { 50 | searchResults.empty(); 51 | $.each(results, function (index, result) { 52 | searchResults.append("
  • " + $('#'+result.ref).text() + "
  • "); 53 | }); 54 | highlight.call(this); 55 | } else { 56 | searchResults.html('
  • '); 57 | $('.search-results li').text('No Results Found for "' + this.value + '"'); 58 | } 59 | } else { 60 | unhighlight(); 61 | searchResults.removeClass('visible'); 62 | } 63 | } 64 | 65 | function highlight() { 66 | if (this.value) content.highlight(this.value, highlightOpts); 67 | } 68 | 69 | function unhighlight() { 70 | content.unhighlight(highlightOpts); 71 | } 72 | 73 | })(window); 74 | -------------------------------------------------------------------------------- /source/includes/_plugin_extending_api.md: -------------------------------------------------------------------------------- 1 | ### Extending API 2 | ```php 3 | array( 10 | 11 | // ... 12 | 13 | 'api' => array( 14 | 'plugin_helloworld_api' => array( 15 | 'path' => '/hello/worlds', 16 | 'controller' => 'HelloWorldBundle:Api:worlds', 17 | 'method' => 'GET' 18 | ) 19 | ) 20 | ), 21 | 22 | // ... 23 | ); 24 | ``` 25 | 26 | ```php 27 | get('mautic.security')->isGranted('plugin:helloWorld:worlds:view')) { 46 | return $this->accessDenied(); 47 | } 48 | 49 | $filter = $this->request->query->get('filter', null); 50 | $limit = $this->request->query->get('limit', null); 51 | $start = $this->request->query->get('start', null); 52 | 53 | /** @var \MauticPlugin\HelloWorldBundle\Model\WorldsModel $model */ 54 | $model = $this->getModel('helloworld.worlds'); 55 | 56 | $worlds = $model->getWorlds($filter, $limit, $start); 57 | $worlds = $this->view($worlds, Codes::HTTP_OK); 58 | 59 | return $this->handleView($worlds); 60 | } 61 | } 62 | ``` 63 | 64 | To add custom API endpoints, simply define the routes under the API firewall in the [plugin's config file](#routes). This will place the route behind /api which will only be accessible if the requester has been authorized via OAuth. 65 | 66 | The api controller(s), should extend `Mautic\ApiBundle\Controller\CommonApiController` to leverage the helper methods provided and to utilize the REST views. 67 | -------------------------------------------------------------------------------- /source/includes/_plugin_integrations_migrations.md: -------------------------------------------------------------------------------- 1 | ### Plugin Schema 2 | 3 | The integration framework provides a means for plugins to better manage their schema. Queries are in migration files that match the plugin's versions number in it's config. When the a plugin is installed or upgraded, it will loop over the migration files up to the latest version. 4 | 5 | #### AbstractPluginBundle 6 | 7 | The plugin's root bundle class should extend `MauticPlugin\IntegrationsBundle\Bundle\AbstractPluginBundle`. 8 | 9 | ```php 10 | getTable($this->concatPrefix($this->table))->hasColumn('is_enabled'); 47 | } catch (SchemaException $e) { 48 | return false; 49 | } 50 | } 51 | 52 | protected function up(): void 53 | { 54 | $this->addSql("ALTER TABLE `{$this->concatPrefix($this->table)}` ADD `is_enabled` tinyint(1) 0"); 55 | 56 | $this->addSql("CREATE INDEX {$this->concatPrefix('is_enabled')} ON {$this->concatPrefix($this->table)}(is_enabled);"); 57 | } 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /source/includes/_plugin_extending_broadcasts.md: -------------------------------------------------------------------------------- 1 | ### Extending Broadcasts 2 | 3 | Broadcasts are communications sent in bulk through a channel such as email (segment emails). Mautic 2.2.0 introduced a new event to execute the sending of these bulk communications via the `mautic:broadcasts:send` command. 4 | 5 | ```php 6 | model = $model; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public static function getSubscribedEvents() 39 | { 40 | return [ 41 | CoreEvents::CHANNEL_BROADCAST => ['onChannelBroadcast', 0] 42 | ]; 43 | } 44 | 45 | 46 | public function onDataCleanup (ChannelBroadcastEvent $event) 47 | { 48 | if (!$event->checkContext('world')) { 49 | return; 50 | } 51 | 52 | // Get list of published broadcasts or broadcast if there is only a single ID 53 | $id = $event->getId(); 54 | $broadcasts = $this->model->getRepository()->getPublishedBroadcasts($id); 55 | $output = $event->getOutput(); 56 | 57 | while (($broadcast = $broadcasts->next()) !== false) { 58 | list($sentCount, $failedCount, $ignore) = $this->model->sendIntergalacticMessages($broadcast[0], null, 100, true, $output); 59 | $event->setResults($this->translator->trans('plugin.helloworld').': '.$broadcast[0]->getName(), $sentCount, $failedCount); 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | To hook into the `mautic:broadcasts:send` command, create a listening for the `\Mautic\CoreBundle\CoreEvents::CHANNEL_BROADCAST` event. The event listener should check for the appropriate context and ID. See example code. -------------------------------------------------------------------------------- /source/includes/_plugin_misc_variant_entities.md: -------------------------------------------------------------------------------- 1 | ### Implementing Variant (A/B Test) Support to Entities 2 | 3 | Mautic has some helper methods with adding support for creating variants of a given entity. This becomes particularly useful for A/B testing. 4 | 5 | #### \Mautic\CoreBundle\Entity\VariantInterface 6 | 7 | This Entity interface ensures that everything is needed in order for Mautic to handle the variants correctly for an entity. 8 | 9 | #### \Mautic\CoreBundle\Entity\VariantEntityTrait 10 | 11 | This trait provides properties needed to define an Entity's relationship to other items. In the Entity's `loadMetadata()` method, be sure to call `$this->addVariantMetadata()`. 12 | 13 | #### \Mautic\CoreBundle\VariantModelTrait 14 | 15 | This trait provides the methods `preVariantSaveEntity()`, `postVariantSaveEntity()` and `convertVariant()`. `preVariantSaveEntity()` should be executed prior to `saveEntity` then `postVariantSaveEntity()`. See example. 16 | 17 | ```php 18 | preVariantSaveEntity($entity, ['setVariantHits'], $variantStartDate); 25 | 26 | parent::saveEntity($entity, $unlock); 27 | 28 | $this->postVariantSaveEntity($entity, $resetVariants, $entity->getRelatedEntityIds(), $variantStartDate); 29 | ```` 30 |
    31 | 32 | #### \Mautic\CoreBundle\Doctrine\VariantMigrationTrait 33 | 34 | To ease the generation of schema to match the Entity, use this trait then execute `$this->addVariantSchema()`. 35 | 36 | #### Translated Entity Form 37 | 38 | Add `variantParent` field's like the code example. In the example, the `variantParent` value is set in the controller due to a `Add A/B Test` button is clicked. The specific use for the plugin may require a select list rather than a hidden field. Change this to meet the code's needs. 39 | 40 | ```php 41 | em, 'HelloWorldBundle:World'); 45 | $builder->add( 46 | $builder->create( 47 | 'variantParent', 48 | 'hidden' 49 | )->addModelTransformer($transformer) 50 | ); 51 | ``` -------------------------------------------------------------------------------- /source/includes/_plugin_services_translator.md: -------------------------------------------------------------------------------- 1 | ### Translator 2 | 3 | ```php 4 | get('translator'); 7 | 8 | // Simple string 9 | echo $translator->trans('plugin.helloworld.goodbye'); 10 | 11 | // Simple string with placeholders 12 | echo $translator->trans('plugin.helloworld.greeting', array('%name%' => $name)); 13 | 14 | // String from a domain other than messages (will use planets.ini) 15 | echo $translator->trans('plugin.helloworld.worlds', array('%world%' => $world), 'planets'); 16 | 17 | // Plural translations 18 | $planetCount = 3; 19 | echo $translator->transChoice('plugin.helloworld.number_of_planets', $planetCount, array('%planets%' => $planetCount)); 20 | 21 | // Check to see if a translation key exists 22 | if ($translator->hasId('plugin.helloworld.goodbye')) { 23 | echo $translator->trans('plugin.helloworld.goodbye'); 24 | } else { 25 | // other logic 26 | } 27 | 28 | // Use the first key if it exists, otherwise use the second (helpful to prevent managing duplicate keys with the same string) 29 | echo $translator->transConditional('plugin.helloworld.planets.' . $planet, 'plugin.helloworld.dwarf_planets. ' . $planet); 30 | ``` 31 | 32 | * Service name: `translator` 33 | * Class: `Mautic\CoreBundle\Translation\Translator` 34 | * Docs: [http://symfony.com/doc/2.8/components/translation/usage.html](http://symfony.com/doc/2.8/components/translation/usage.html) 35 | 36 | Use the translator service to include translated strings in the code. Depending on where the translation is necessary will determine how to obtain the service. 37 | 38 | To use the template service in view templates, simply use the [template helper](#translation-helper), `$view['translator']`. 39 | 40 | The translator service has the following functions to help with translating strings: 41 | 42 | **Simple translation**
    43 | `trans($id, array $parameters = array(), $domain = null, $locale = null)` 44 | 45 | **[Pluralization](http://symfony.com/doc/current/components/translation/usage.html#pluralization)**
    46 | `transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)` 47 | 48 | **Check to see if a key exists**
    49 | `hasId($id, $domain = null, $locale = null)` 50 | 51 | **Use the $preferred key if it exists, if not, use $alternative**
    52 | `transConditional($preferred, $alternative, $parameters = array(), $domain = null, $locale = null)` -------------------------------------------------------------------------------- /source/includes/_plugin_extending_categories.md: -------------------------------------------------------------------------------- 1 | ### Extending Categories 2 | 3 | Mautic has a CategoryBundle that can be leveraged to incorporate categories into a plugin. 4 | 5 | #### Adding categories 6 | As of Mautic 1.2.1, register categories through the plugin's config.php file by adding the following as a key to the returned config array: 7 | 8 |
     9 |     'categories' => array(
    10 |         'plugin:helloWorld' => 'mautic.helloworld.world.categories'
    11 |     ),
    12 | 
    13 | 14 | The category keys need be prefixed with `plugin:` as it is used in determining permissions to manage categories. The `helloWorld` should match the permission class name. 15 | 16 | #### Configuring Categories for Menu 17 | 18 | It is now recommended to not show the category in the main Menu. 19 | 20 | To add a category menu item for the plugin, simply add the following to `menu` [config](#menu) for whichever menu the item should appear (`main` or `admin`): 21 | 22 |
    23 |     'mautic.category.menu.index' => array(
    24 |         'bundle' => 'plugin:helloWorld'
    25 |     )
    26 | 
    27 | 28 | The `bundle` value needs be prefixed with `plugin:` as it is used in determining permissions to manage categories. The `helloWorld` should be the bundle name of the plugin. 29 | 30 | #### Configuring Categories for Routes 31 | 32 | There is no need to add custom routes for categories. However, when [generating a URL](#router) to the plugin's category list, use 33 | 34 |
    35 | $categoryUrl = $router->generateUrl('mautic_category_index', array('bundle' => 'plugin:helloWorld'));
    36 | 
    37 | 38 | #### Including Category in Forms 39 | 40 | To add a category select list to a [form](#forms), use `category` as the form type and pass `bundle` as an option: 41 | 42 |
    43 |     //add category
    44 |     $builder->add('category', 'category', array(
    45 |         'bundle' => 'plugin:helloWorld'
    46 |     ));
    47 | 
    48 | 49 | #### Restricting Category Management 50 | 51 | To restrict access to catgories, use the following in the plugin's [Permission class](#roles-and-permissions). 52 | 53 | In `__construct()` add `$this->addStandardPermissions('categories');` then in `buildForm()`, add `$this->addStandardFormFields('helloWorld', 'categories', $builder, $data);`. 54 | 55 | See a code example in [Roles and Permissions](#roles-and-permissions). 56 | 57 | The two standard helper methods will add the permissions of `view`, `edit`, `create`, `delete`, `publish`, and `full` for categories. 58 | 59 | 60 | -------------------------------------------------------------------------------- /source/includes/_plugin_extending_points.md: -------------------------------------------------------------------------------- 1 | ### Extending Points 2 | 3 | Custom point actions and triggers can be added by listening to their respective on build events. Read more about [listeners and subscribers](#events). 4 | 5 | #### Point Actions 6 | 7 | To add a custom point action used to give a lead x points for doing a certain action, add a listener to the `\Mautic\PointBundle\PointEvents::POINT_ON_BUILD` event then configure the custom point action with `$event->addAction($identifier, $parameters)` method. `$identifier` must be something unique. The `$parameters` array can contain the following elements: 8 | 9 | 10 | Key|Required|Type|Description 11 | ---|--------|----|----------- 12 | **label**|REQUIRED|string|The language string for the option in the dropdown 13 | **formType**|OPTIONAL|string|The alias of a custom form type used to set config options. 14 | **formTypeOptions**|OPTIONAL|array|Array of options to include into the formType's $options argument 15 | **formTypeCleanMasks**|OPTIONAL|array|Array of input masks to clean a values from formType 16 | **formTypeTheme**|OPTIONAL|string|Theme to customize elements for formType 17 | **template**|OPTIONAL|string|View template used to render the formType 18 | **callback**|OPTIONAL|mixed|Static callback function used to validate the action. Return true to add the points to the lead. 19 | 20 | In order for the custom point action to work, add something like the following in the code logic when the lead executes the custom action: 21 | 22 | 23 | `$this->getModel('point')->triggerAction('page.hit', $event->getHit());` 24 | 25 | 26 | #### Point Triggers 27 | 28 | 29 | To add a custom point trigger used to execute a specific action once a lead hits X number of points, add a listener to the `\Mautic\PointBundle\PointEvents::TRIGGER_ON_BUILD` event then configure the custom point trigger with `$event->addEvent($identifier, $parameters)` method. `$identifier` must be something unique. The `$parameters` array can contain the following elements: 30 | 31 | Key|Required|Type|Description 32 | ---|--------|----|----------- 33 | **label**|REQUIRED|string|The language string for the option in the dropdown 34 | **formType**|OPTIONAL|string|The alias of a custom form type used to set config options. 35 | **formTypeOptions**|OPTIONAL|array|Array of options to include into the formType's $options argument 36 | **formTypeCleanMasks**|OPTIONAL|array|Array of input masks to clean a values from formType 37 | **formTypeTheme**|OPTIONAL|string|Theme to customize elements for formType 38 | **template**|OPTIONAL|string|View template used to render the formType 39 | **callback**|OPTIONAL|mixed|Static callback function used to execute the custom action. -------------------------------------------------------------------------------- /source/javascripts/app/toc.js: -------------------------------------------------------------------------------- 1 | (function (global) { 2 | 3 | var closeToc = function () { 4 | $(".tocify-wrapper").removeClass('open'); 5 | $("#nav-button").removeClass('open'); 6 | }; 7 | 8 | var makeToc = function () { 9 | global.toc = $("#toc").tocify({ 10 | selectors: 'h1, h2, h3', 11 | extendPage: false, 12 | theme: 'none', 13 | smoothScroll: false, 14 | showEffectSpeed: 0, 15 | hideEffectSpeed: 180, 16 | ignoreSelector: '.toc-ignore', 17 | highlightOffset: 60, 18 | scrollTo: -1, 19 | scrollHistory: true, 20 | hashGenerator: function (text, element) { 21 | return element.prop('id'); 22 | } 23 | }).data('toc-tocify'); 24 | 25 | $("#nav-button").click(function () { 26 | $(".tocify-wrapper").toggleClass('open'); 27 | $("#nav-button").toggleClass('open'); 28 | return false; 29 | }); 30 | 31 | $(".page-wrapper").click(closeToc); 32 | $(".tocify-item").click(closeToc); 33 | 34 | $('ul').prev('li').find('a').each(function (index) { 35 | var that = this; 36 | setTimeout(function () { 37 | var startingClass = ($(that).parent().hasClass('tocify-focus')) ? 'close' : 'open'; 38 | $(that).append( 39 | $("").addClass(startingClass) 40 | ); 41 | }, 200); 42 | 43 | $(this).click(function () { 44 | $('#toc a span').removeClass('close'); 45 | $(this).find('span').first().addClass('close'); 46 | }); 47 | }); 48 | }; 49 | 50 | // Hack to make already open sections to start opened, 51 | // instead of displaying an ugly animation 52 | function animate() { 53 | setTimeout(function () { 54 | toc.setOption('showEffectSpeed', 180); 55 | }, 50); 56 | } 57 | 58 | function makeLinks() { 59 | // Append anchor links 60 | $(":header").each(function () { 61 | if ($(this).attr('id')) { 62 | var link = $('').attr('href', '#' + $(this).attr('id')); 63 | $(this).append(link); 64 | 65 | $(this).on('mouseenter',function(){ 66 | $(this).find('i.fa-link').css('display', 'inline'); 67 | }); 68 | $(this).on('mouseleave',function(){ 69 | $(this).find('i.fa-link').css('display', 'none'); 70 | }); 71 | } 72 | }); 73 | } 74 | 75 | $(makeToc); 76 | $(animate); 77 | 78 | $(makeLinks); 79 | })(window); 80 | 81 | -------------------------------------------------------------------------------- /source/includes/_plugin_extending_maintenance.md: -------------------------------------------------------------------------------- 1 | ### Extending Maintenance Cleanup 2 | 3 | ```php 4 | db = $db; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public static function getSubscribedEvents() 41 | { 42 | return [ 43 | CoreEvents::MAINTENANCE_CLEANUP_DATA => ['onDataCleanup', -50] 44 | ]; 45 | } 46 | 47 | /** 48 | * @param $isDryRun 49 | * @param $date 50 | * 51 | * @return int 52 | */ 53 | public function onDataCleanup (MaintenanceEvent $event) 54 | { 55 | $qb = $this->db->createQueryBuilder() 56 | ->setParameter('date', $event->getDate()->format('Y-m-d H:i:s')); 57 | 58 | if ($event->isDryRun()) { 59 | $rows = (int) $qb->select('count(*) as records') 60 | ->from(MAUTIC_TABLE_PREFIX.'worlds', 'w') 61 | ->where( 62 | $qb->expr()->gte('w.date_added', ':date') 63 | ) 64 | ->execute() 65 | ->fetchColumn(); 66 | } else { 67 | $rows = (int) $qb->delete(MAUTIC_TABLE_PREFIX.'worlds') 68 | ->where( 69 | $qb->expr()->lte('date_added', ':date') 70 | ) 71 | ->execute(); 72 | } 73 | 74 | $event->setStat($this->translator->trans('mautic.maintenance.hello_world'), $rows, $qb->getSQL(), $qb->getParameters()); 75 | } 76 | } 77 | ``` 78 | 79 | To hook into the `mautic:maintenance:cleanup` command, create a listening for the `\Mautic\CoreBundle\CoreEvents::MAINTENANCE_CLEANUP_DATA` event. The event listener should check if data should be deleted or a counted. Use `$event->setStat($key, $affectedRows, $sql, $sqlParameters)` to give feedback to the CLI command. Note that `$sql` and `$sqlParameters` are only used for debugging and shown only in the dev environment. 80 | 81 | -------------------------------------------------------------------------------- /source/includes/_plugin_misc_translated_entities.md: -------------------------------------------------------------------------------- 1 | ### Implementing Translation Support to Entities 2 | 3 | Mautic has some helper methods with adding support for translated content to an entity. 4 | 5 | #### \Mautic\CoreBundle\Entity\TranslationInterface 6 | 7 | This Entity interface ensures that everything is needed in order for Mautic to handle translations correctly for an entity. 8 | 9 | #### \Mautic\CoreBundle\Entity\TranslationEntityTrait 10 | 11 | This trait provides properties needed to define an Entity's language and relationships to other items. In the Entity's `loadMetadata()` method, be sure to call `$this->addTranslationMetadata()`. 12 | 13 | #### \Mautic\CoreBundle\TranslationModelTrait 14 | 15 | This trait provides the method `getTranslatedEntity()` that will determine the entity to use as the translation based on the `$lead` and/or the `HTTP_ACCEPT_LANGUAGE` header. It also has a `postTranslationEntitySave()` that should be called at the end of the Entity's `saveEntity()` method. 16 | 17 | #### \Mautic\CoreBundle\Doctrine\TranslationMigrationTrait 18 | 19 | To ease the generation of schema to match the Entity, use this trait then execute `$this->addTranslationSchema()`. 20 | 21 | #### Translated Entity Form 22 | 23 | Add a `locale` and `translatedParent` form fields like the code example. 24 | 25 | ```php 26 | em, 'HelloWorldBundle:World'); 30 | $builder->add( 31 | $builder->create( 32 | 'translationParent', 33 | 'world_list', 34 | array( 35 | 'label' => 'mautic.core.form.translation_parent', 36 | 'label_attr' => array('class' => 'control-label'), 37 | 'attr' => array( 38 | 'class' => 'form-control', 39 | 'tooltip' => 'mautic.core.form.translation_parent.help' 40 | ), 41 | 'required' => false, 42 | 'multiple' => false, 43 | 'empty_value' => 'mautic.core.form.translation_parent.empty', 44 | 'top_level' => 'translation', 45 | 'ignore_ids' => array((int) $options['data']->getId()) 46 | ) 47 | )->addModelTransformer($transformer) 48 | ); 49 | 50 | $builder->add( 51 | 'language', 52 | 'locale', 53 | array( 54 | 'label' => 'mautic.core.language', 55 | 'label_attr' => array('class' => 'control-label'), 56 | 'attr' => array( 57 | 'class' => 'form-control', 58 | 'tooltip' => 'mautic.page.form.language.help', 59 | ), 60 | 'required' => false, 61 | 'empty_data' => 'en' 62 | ) 63 | ); 64 | ``` -------------------------------------------------------------------------------- /source/javascripts/app/lang.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2008-2013 Concur Technologies, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | License for the specific language governing permissions and limitations 14 | under the License. 15 | */ 16 | (function (global) { 17 | var languages = []; 18 | 19 | global.setupLanguages = setupLanguages; 20 | global.activateLanguage = activateLanguage; 21 | 22 | function activateLanguage(language) { 23 | if (!language) return; 24 | if (language === "") return; 25 | 26 | $(".lang-selector a").removeClass('active'); 27 | $(".lang-selector a[data-language-name='" + language + "']").addClass('active'); 28 | for (var i=0; i < languages.length; i++) { 29 | $(".highlight." + languages[i]).parent().hide(); 30 | } 31 | $(".highlight." + language).parent().show(); 32 | 33 | global.toc.calculateHeights(); 34 | 35 | // scroll to the new location of the position 36 | if ($(window.location.hash).get(0)) { 37 | $(window.location.hash).get(0).scrollIntoView(true); 38 | } 39 | } 40 | 41 | // if a button is clicked, add the state to the history 42 | function pushURL(language) { 43 | if (!history) { return; } 44 | var hash = window.location.hash; 45 | if (hash) { 46 | hash = hash.replace(/^#+/, ''); 47 | } 48 | history.pushState({}, '', '?' + language + '#' + hash); 49 | 50 | // save language as next default 51 | localStorage.setItem("language", language); 52 | } 53 | 54 | function setupLanguages(l) { 55 | var currentLanguage = l[0]; 56 | var defaultLanguage = localStorage.getItem("language"); 57 | 58 | languages = l; 59 | 60 | if ((location.search.substr(1) !== "") && (jQuery.inArray(location.search.substr(1), languages)) != -1) { 61 | // the language is in the URL, so use that language! 62 | activateLanguage(location.search.substr(1)); 63 | 64 | localStorage.setItem("language", location.search.substr(1)); 65 | } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) { 66 | // the language was the last selected one saved in localstorage, so use that language! 67 | activateLanguage(defaultLanguage); 68 | } else { 69 | // no language selected, so use the default 70 | activateLanguage(languages[0]); 71 | } 72 | } 73 | 74 | // if we click on a language tab, activate that language 75 | $(function() { 76 | $(".lang-selector a").on("click", function() { 77 | var language = $(this).data("language-name"); 78 | pushURL(language); 79 | activateLanguage(language); 80 | return false; 81 | }); 82 | window.onpopstate = function(event) { 83 | activateLanguage(window.location.search.substr(1)); 84 | }; 85 | }); 86 | })(window); 87 | -------------------------------------------------------------------------------- /source/includes/_plugin_extending_pages.md: -------------------------------------------------------------------------------- 1 | ### Extending Landing Pages 2 | 3 | ```php 4 | array('onPageBuild', 0), 27 | PageEvents::PAGE_ON_DISPLAY => array('onPageDisplay', 0) 28 | ); 29 | } 30 | 31 | /** 32 | * Register the tokens and a custom A/B test winner 33 | * 34 | * @param PageBuilderEvent $event 35 | */ 36 | public function onPageBuild(PageBuilderEvent $event) 37 | { 38 | // Add page tokens 39 | $content = $this->templating->render('HelloWorldBundle:SubscribedEvents\PageToken:token.html.php'); 40 | $event->addTokenSection('helloworld.token', 'plugin.helloworld.header', $content); 41 | 42 | // Add AB Test Winner Criteria 43 | $event->addAbTestWinnerCriteria( 44 | 'helloworld.planetvisits', 45 | array( 46 | // Label to group by 47 | 'group' => 'plugin.helloworld.header', 48 | 49 | // Label for this specific a/b test winning criteria 50 | 'label' => 'plugin.helloworld.pagetokens.', 51 | 52 | // Static callback function that will be used to determine the winner 53 | 'callback' => '\MauticPlugin\HelloWorldBundle\Helper\AbTestHelper::determinePlanetVisitWinner' 54 | ) 55 | ); 56 | } 57 | 58 | /** 59 | * Search and replace tokens with content 60 | * 61 | * @param PageSendEvent $event 62 | */ 63 | public function onPageDisplay(PageSendEvent $event) 64 | { 65 | // Get content 66 | $content = $event->getContent(); 67 | 68 | // Search and replace tokens 69 | $content = str_replace('{hello}', 'world!', $content); 70 | 71 | // Set updated content 72 | $event->setContent($content); 73 | } 74 | } 75 | ``` 76 | 77 | There are two way to extend pages: page tokens used to insert dynamic content into a page and a/b test winning criteria . Both leverage the `\Mautic\PageBundle\PageEvents::PAGE_ON_BUILD` event. Read more about [listeners and subscribers](#events). 78 | 79 | #### Page Tokens 80 | 81 | Page tokens are handled exactly the same as [Email Tokens](#page-tokens). 82 | 83 | #### Page A/B Test Winner Criteria 84 | 85 | Custom landing page A/B test winner criteria is handled exactly the same as [page A/B test winner criteria](#page-a/b-test-winner-criteria) with the only differences being that the `callback` function is passed `Mautic\PageBundle\Entity\Page $page` and `Mautic\PageBundle\Entity\Page $parent` instead. Of course `$children` is an ArrayCollection of Page entities as well. -------------------------------------------------------------------------------- /source/includes/_plugin_services_mail_helper.md: -------------------------------------------------------------------------------- 1 | ### Mail Helper 2 | 3 | ```php 4 | get('mautic.helper.mailer')->getMailer(); 6 | 7 | // To address; can use setTo(), addCc(), setCc(), addBcc(), or setBcc() as well 8 | $mailer->addTo($toAddress, $toName); 9 | 10 | // Set a custom from; will use system settings by default 11 | $mailer->setFrom( 12 | $this->user->getEmail(), 13 | $this->user->getFirstName().' '.$this->user->getLastName() 14 | ); 15 | 16 | // Set subject 17 | $mailer->setSubject($email['subject']); 18 | 19 | // Set content 20 | $mailer->setBody($content); 21 | $mailer->parsePlainText($content); 22 | 23 | // Optional lead tracking (array) 24 | $mailer->setLead($lead); 25 | $mailer->setIdHash(); 26 | 27 | // Send the mail, pass true to dispatch through event listeners (for replacing tokens, etc) 28 | if ($mailer->send(true)) { 29 | 30 | // Optional to create a stat to allow a web view, tracking, etc 31 | $mailer->createLeadEmailStat(); 32 | } else { 33 | $errors = $mailer->getErrors(); 34 | $failedRecipients = $errors['failures']; 35 | } 36 | ``` 37 | 38 | ```php 39 | get('mautic.helper.mailer')->getMailer(); 45 | $failed = array(); 46 | 47 | $mailer->enableQueue(); 48 | foreach ($emailList as $email) { 49 | try { 50 | if (!$mailer->addTo($email['email'], $email['name'])) { 51 | // Clear the errors so it doesn't stop the next send 52 | $mailer->clearErrors(); 53 | 54 | $failed[] = $email; 55 | 56 | continue; 57 | } 58 | } catch (BatchQueueMaxException $e) { 59 | // Queue full so flush (send) then try again 60 | if (!$mailer->flushQueue()) { 61 | $errors = $mailer->getErrors(); 62 | $failed = array_merge($failed, $errors['failures']); 63 | } 64 | 65 | if (!$mailer->addTo($email['email'], $email['email'], $email['name'])) { 66 | // ... 67 | } 68 | } 69 | } 70 | 71 | // Flush pending 72 | if (!$mailer->flushQueue()) { 73 | // ... 74 | } 75 | ``` 76 | 77 | The mail helper can be used to send email, running the content through the event listeners to search/replace tokens, manipulate the content, etc. 78 | 79 | Some transports, such as Mandrill, support tokenized emails for multiple recipients. The mail helper makes it easy to leverage this feature by using it's `queue()` and `flushQueue()` functions in place of `send()`. If sending a batch of emails, it is recommended to use the `queue()` function. Although these classes will still work with using just `send()` for one off emails, if sending a batch of the same email to multiple contacts, enable tokenization/batch mode with `enableQueue()`. 80 | 81 | If using an Email entity (`\Mautic\EmailBundle\Entity\Email`), just pass the Email entity to `$mailer->setEmail($email)` and the subject, body, assets, etc will be extracted and automatically set. 82 | 83 | #### Attachments 84 | 85 | Attachments can be attached to emails by using the `attachFile()` function. You can also attach Mautic assets (`\Mautic\AssetBundle\Entity\Asset`) via `attachAsset()`. 86 | 87 | Refer to the class for more details on available functions. -------------------------------------------------------------------------------- /source/stylesheets/print.css.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | @import 'normalize'; 3 | @import 'compass'; 4 | @import 'variables'; 5 | @import 'icon-font'; 6 | 7 | /* 8 | Copyright 2008-2013 Concur Technologies, Inc. 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); you may 11 | not use this file except in compliance with the License. You may obtain 12 | a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 | License for the specific language governing permissions and limitations 20 | under the License. 21 | */ 22 | 23 | $print-color: #999; 24 | $print-color-light: #ccc; 25 | $print-font-size: 12px; 26 | 27 | body { 28 | @extend %default-font; 29 | } 30 | 31 | .tocify, .toc-footer, .lang-selector, .search, #nav-button { 32 | display: none; 33 | } 34 | 35 | .tocify-wrapper>img { 36 | margin: 0 auto; 37 | display: block; 38 | } 39 | 40 | .content { 41 | font-size: 12px; 42 | 43 | pre, code { 44 | @extend %code-font; 45 | @extend %break-words; 46 | border: 1px solid $print-color; 47 | border-radius: 5px; 48 | font-size: 0.8em; 49 | } 50 | 51 | pre { 52 | padding: 1.3em; 53 | } 54 | 55 | code { 56 | padding: 0.2em; 57 | } 58 | 59 | table { 60 | border: 1px solid $print-color; 61 | tr { 62 | border-bottom: 1px solid $print-color; 63 | } 64 | td,th { 65 | padding: 0.7em; 66 | } 67 | } 68 | 69 | p { 70 | line-height: 1.5; 71 | } 72 | 73 | a { 74 | text-decoration: none; 75 | color: #000; 76 | } 77 | 78 | h1 { 79 | @extend %header-font; 80 | font-size: 2.5em; 81 | padding-top: 0.5em; 82 | padding-bottom: 0.5em; 83 | margin-top: 1em; 84 | margin-bottom: $h1-margin-bottom; 85 | border: 2px solid $print-color-light; 86 | border-width: 2px 0; 87 | text-align: center; 88 | } 89 | 90 | h2 { 91 | @extend %header-font; 92 | font-size: 1.8em; 93 | margin-top: 2em; 94 | border-top: 2px solid $print-color-light; 95 | padding-top: 0.8em; 96 | } 97 | 98 | h1+h2, h1+div+h2 { 99 | border-top: none; 100 | padding-top: 0; 101 | margin-top: 0; 102 | } 103 | 104 | h3, h4 { 105 | @extend %header-font; 106 | font-size: 0.8em; 107 | margin-top: 1.5em; 108 | margin-bottom: 0.8em; 109 | text-transform: uppercase; 110 | } 111 | 112 | h5, h6 { 113 | text-transform: uppercase; 114 | } 115 | 116 | aside { 117 | padding: 1em; 118 | border: 1px solid $print-color-light; 119 | border-radius: 5px; 120 | margin-top: 1.5em; 121 | margin-bottom: 1.5em; 122 | line-height: 1.6; 123 | } 124 | 125 | aside:before { 126 | vertical-align: middle; 127 | padding-right: 0.5em; 128 | font-size: 14px; 129 | } 130 | 131 | aside.notice:before { 132 | @extend %icon-info-sign; 133 | } 134 | 135 | aside.warning:before { 136 | @extend %icon-exclamation-sign; 137 | } 138 | 139 | aside.success:before { 140 | @extend %icon-ok-sign; 141 | } 142 | } -------------------------------------------------------------------------------- /source/includes/_plugin_mvc_models.md: -------------------------------------------------------------------------------- 1 | ### Models 2 | 3 | ```php 4 | mailer; 23 | 24 | $mailer->message->addTo( 25 | $this->factory->getParameter('mailer_from_email') 26 | ); 27 | 28 | $this->message->setFrom( 29 | array($data['email'] => $data['name']) 30 | ); 31 | 32 | $mailer->message->setSubject($data['subject']); 33 | 34 | $mailer->message->setBody($data['message']); 35 | 36 | $mailer->send(); 37 | } 38 | } 39 | ``` 40 | Models are used to retrieve and process data between controllers and views. Models aren't required for plugins but, if used, Mautic provides means to easily obtain the model objects and some commonly used methods. 41 | 42 | #### Model Classes 43 | 44 | Model's should be registered as [`model` services](#service-types). The names of these services should match the following nomenclature: `mautic.UNIQUE_BUNDLE_IDENTIFIER.model.MODEL_IDENTIFIER`. `UNIQUE_BUNDLE_IDENTIFIER` can be whatever is desired but must be unique across all Mautic bundles and plugins. `MODEL_IDENTIFIER` just has to be unique for the given bundle. For example, the model example code could be registered as `mautic.helloworld.model.contact`. This allows the helper functions to retrieve model objects to find the correct model service. 45 | 46 | Custom models can extend one of two Mautic base models to leverage some helper functions: 47 | 48 | ##### \Mautic\CoreBundle\Model\AbstractCommonModel 49 | 50 | This is the basic model that mainly provides access to services frequently used with models. 51 | 52 | Property|Service 53 | --------|------- 54 | $this->factory | [Mautic's factory service](#factory-service) - deprecated as of 2.0; use direct dependency injection where possible 55 | $this->em | [Entity manager service](#database) 56 | $this->security | [Mautic's security service](#security) 57 | $this->dispatcher | [Event dispatcher service](#events) 58 | $this->translator | [Translator service](#translator) 59 | 60 | ##### \Mautic\CoreBundle\Model\FormModel 61 | 62 | The FormModel extends AbstractCommonModel and provides a set of helper methods for interacting with entities and repositories. To read more about these methods, refer to the [Database](#database) section. 63 | 64 | #### Getting Model Objects 65 | 66 | ```php 67 | getModel('lead'); // shortcut for lead.lead 71 | 72 | /** @var \Mautic\LeadBundle\Model\ListModel $leadListModel */ 73 | $leadListModel = $this->getModel('lead.list'); 74 | 75 | /** @var \MauticPlugin\HelloWorldBundle\Model\ContactModel $contactModel */ 76 | $contactModel = $this->getModel('helloworld.contact'); 77 | ``` 78 | 79 | If using a model in another service or model, inject the model service as a dependency. If in a controller, use the `getModel()` helper function. -------------------------------------------------------------------------------- /source/includes/_plugin_misc_helpers_chartquery.md: -------------------------------------------------------------------------------- 1 | #### ChartQuery and Graphs 2 | 3 | There are several classes available to assist with generating chart data. 4 | 5 | ```php 6 | em->getConnection(), $dateFrom, $dateTo); 12 | $q = $query->prepareTimeDataQuery('lead_points_change_log', 'date_added', $filter); 13 | $data = $query->loadAndBuildTimeData($q); 14 | $chart->setDataset($this->translator->trans('mautic.point.changes'), $data); 15 | $data = $chart->render(); 16 | ``` 17 |
    18 | 19 | ##### ChartQuery 20 | 21 | ChartQuery is a helper class to get the chart data from the database. Instantiate it with the Doctrine connection object, date from and date to. Those dates must be instances of the DateTime class. ChartQuery will automatically guess the time unit (hours, days, weeks, months or years) based on the items between the date range. However, if you want to receive the data from a specific time unit, pass it as the fourth parameter (H, d, W, m, Y). 22 | 23 | ChartQuery also fills in the missing items (if any) which is required to display the data properly in the line chart generated by the [ChartJS](http://chartjs.org). 24 | 25 | ##### LineChart 26 | 27 | LineChart is used in Mautic to display a date/time related data where the time is on the horizontal axis and the values are in the vertical axis. ChartJS' line charts can display multiple datasets in one chart. Instantiate the LineChart with a unit (null for unit guessing), from date, to date (the same as for the ChartQuery) and a date format (null for automatically generated date format). 28 | 29 | All the params you need to instantiate the LineChart are used to generate the labels of the horizontal axis. To add the dataset to the LineChart object, use the `setDataset($label, $data)` method where the label is string and data is an array generated by the ChartQuery. The color of each dataset will be generated automatically. 30 | 31 | Call the `render()` method to get the data prepared for ChartJS. 32 | 33 | ##### PieChart 34 | 35 | A PieChart can be instantiated simply by `new PieChart()`. To add a dataset, use again the `setDataset($label, $value)` method where the label is a string and value is integer. 36 | 37 | Call the `render()` method to get the data prepared for ChartJS. 38 | 39 | ##### BarChart 40 | 41 | BarChart is used to display different variants of the same value where the variants are in the horizontal axis and the value is in vertical axis. To create a bar chart, use `new BarChart($labels)` where labels is an array of all the variants. Each variant can have multiple datasets. To add a dataset, use the `setDataset($label, $data, $order)` method where the label is string, data is array of values for each variant. Order can be used to move the dataset before already created dataset. 42 | 43 | Call the `render()` method to get the data prepared for ChartJS. 44 | 45 | 46 | ```php 47 | render('MauticCoreBundle:Helper:chart.html.php', array('chartData' => $data, 'chartType' => 'line', 'chartHeight' => 300)); ?> 48 | ``` 49 | 50 | ##### Frontend 51 | 52 | At the frontend, simply use the prepared chart template, pass in the chart data, the chart type (line/bar/pie) and the chart height. The width is responsive. 53 | -------------------------------------------------------------------------------- /source/includes/_api_endpoint_themes.md: -------------------------------------------------------------------------------- 1 | ## Themes 2 | This endpoint is useful for working with Mautic themes. 3 | 4 | ```php 5 | newAuth($settings); 11 | $apiUrl = "https://your-mautic.com"; 12 | $api = new MauticApi(); 13 | $themesApi = $api->newApi("themes", $auth, $apiUrl); 14 | ``` 15 | 16 | ### Get theme 17 | 18 | Returns directly the zip file in the response with the `application/zip` header on success or a JSON response body with error messages on fail. The PHP API library will save the zip file to the system temporary directory and provides you with the path. 19 | 20 | ```php 21 | get($themeName); 23 | ``` 24 | ```json 25 | { 26 | "file": "/absolute/path/to/the/system/temp/dir/with/the/theme/zip/file" 27 | } 28 | ``` 29 | 30 | #### HTTP Request 31 | 32 | `GET /themes/THEME_NAME` 33 | 34 | #### Response 35 | 36 | `Expected Response Code: 200` 37 | 38 | ### Set Temporary File Path 39 | 40 | Changes the default temporary directory where the zip file is created. The directory is created if it does not exist. 41 | 42 | ```php 43 | setTemporaryFilePath("/absolute/path/to/a/different/temp/dir"); 45 | $response = $themesApi->get($themeName); 46 | ``` 47 | ```json 48 | { 49 | "file": "/absolute/path/to/a/different/temp/dir/zipfile" 50 | } 51 | ``` 52 | 53 | ### Get List of themes 54 | 55 | Lists all installed themes with the detailes stored in their config.json files. 56 | 57 | ```php 58 | getList(); 60 | ``` 61 | ```json 62 | { 63 | "themes": { 64 | "blank": { 65 | "name": "Blank", 66 | "key": "blank", 67 | "config": { 68 | "name": "Blank", 69 | "author": "Mautic team", 70 | "authorUrl": "https:\/\/mautic.org", 71 | "features": [ 72 | "page", 73 | "email", 74 | "form" 75 | ] 76 | } 77 | }, 78 | ... 79 | } 80 | } 81 | ``` 82 | 83 | #### HTTP Request 84 | 85 | `GET /themes` 86 | 87 | #### Response 88 | 89 | `Expected Response Code: 200` 90 | 91 | See JSON code example. 92 | 93 | **Response Properties** 94 | 95 | Name|Type|Description 96 | ----|----|----------- 97 | themes|array|List of installed themes and their configs 98 | 99 | ### Create Theme 100 | 101 | Creates a new theme or updates an existing one (based on the file name) from provided zip file. 102 | 103 | ```php 104 | dirname(__DIR__).'/'.'mytheme.zip' // Must be a path to an existing file 107 | ); 108 | 109 | $response = $themeApi->create($data); 110 | ``` 111 | The file is sent via regular POST files array like a browser sends it during file upload. 112 | 113 | #### HTTP Request 114 | 115 | `POST /themes/new` 116 | 117 | #### Response 118 | 119 | `Expected Response Code: 200` 120 | ```json 121 | { 122 | "success": true 123 | } 124 | ``` 125 | 126 | ### Delete File 127 | ```php 128 | delete($themeName); 130 | ``` 131 | Delete a theme. The stock themes cannot be deleted 132 | 133 | #### HTTP Request 134 | 135 | `DELETE /themes/THEME_NAME/delete` 136 | 137 | #### Response 138 | 139 | `Expected Response Code: 200` 140 | ```json 141 | { 142 | "success": true 143 | } 144 | ``` 145 | -------------------------------------------------------------------------------- /source/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mautic Developer Documentation 3 | 4 | language_tabs: 5 | - php 6 | - json 7 | 8 | toc_footers: 9 | - Download Mautic source 10 | - Documentation powered by Slate 11 | 12 | includes: 13 | - introduction 14 | - cache 15 | - plugin_intro 16 | - plugin_migrations 17 | - plugin_migrations_1.2 18 | - plugin_migrations_2.0 19 | - plugin_migrations_3.0 20 | - plugin_structure 21 | - plugin_install 22 | - plugin_config 23 | - plugin_translations 24 | - plugin_mvc 25 | - plugin_mvc_controllers 26 | - plugin_mvc_models 27 | - plugin_mvc_views 28 | - plugin_services 29 | - plugin_services_factory 30 | - plugin_services_users 31 | - plugin_services_security 32 | - plugin_services_translator 33 | - plugin_services_router 34 | - plugin_services_request 35 | - plugin_services_session 36 | - plugin_services_database 37 | - plugin_services_parameters 38 | - plugin_services_event_dispatcher 39 | - plugin_services_paths_helper 40 | - plugin_services_ip_lookup_helper 41 | - plugin_services_plugin_config_helper 42 | - plugin_services_cookie_helper 43 | - plugin_services_mail_helper 44 | - plugin_services_model_factory 45 | - plugin_database 46 | - plugin_permissions 47 | - plugin_configuration 48 | - plugin_integrations 49 | - plugin_integrations_migrations 50 | - plugin_integrations_authentication 51 | - plugin_integrations_configuration 52 | - plugin_integrations_sync 53 | - plugin_integrations_builder 54 | - plugin_manipulating_contacts 55 | - plugin_extending_intro 56 | - plugin_extending_api 57 | - plugin_extending_broadcasts 58 | - plugin_extending_campaigns 59 | - plugin_extending_categories 60 | - plugin_extending_contacts 61 | - plugin_extending_emails 62 | - plugin_extending_forms 63 | - plugin_extending_integrations 64 | - plugin_extending_maintenance 65 | - plugin_extending_pages 66 | - plugin_extending_points 67 | - plugin_extending_reports 68 | - plugin_extending_webhooks 69 | - plugin_extending_ui 70 | - plugin_misc 71 | - plugin_misc_flashes 72 | - plugin_misc_helpers 73 | - plugin_misc_helpers_input 74 | - plugin_misc_helpers_datetime 75 | - plugin_misc_helpers_chartquery 76 | - plugin_misc_commands 77 | - plugin_misc_forms 78 | - plugin_misc_events 79 | - plugin_misc_translated_entities.md 80 | - plugin_misc_variant_entities.md 81 | - marketplace.md 82 | - themes 83 | - api_intro 84 | - api_authorization 85 | - api_authorization_oauth1a 86 | - api_authorization_oauth2 87 | - api_authorization_basic 88 | - api_rate_limiter 89 | - api_libraries 90 | - api_endpoints 91 | - api_endpoint_assets 92 | - api_endpoint_campaigns 93 | - api_endpoint_categories 94 | - api_endpoint_companies 95 | - api_endpoint_contacts 96 | - api_endpoint_data 97 | - api_endpoint_dynamic_contents 98 | - api_endpoint_emails 99 | - api_endpoint_fields 100 | - api_endpoint_files 101 | - api_endpoint_forms 102 | - api_endpoint_messages 103 | - api_endpoint_notes 104 | - api_endpoint_notifications 105 | - api_endpoint_pages 106 | - api_endpoint_point_actions 107 | - api_endpoint_point_triggers 108 | - api_endpoint_reports 109 | - api_endpoint_roles 110 | - api_endpoint_segments 111 | - api_endpoint_smses 112 | - api_endpoint_stages 113 | - api_endpoint_stats 114 | - api_endpoint_tags 115 | - api_endpoint_themes 116 | - api_endpoint_tweets 117 | - api_endpoint_users 118 | - api_endpoint_webhooks 119 | - webhooks 120 | - mauticjs_api_intro 121 | - mauticjs_api_reference 122 | 123 | search: true 124 | --- 125 | -------------------------------------------------------------------------------- /source/includes/_api_endpoint_files.md: -------------------------------------------------------------------------------- 1 | ## Files 2 | This endpoint is useful for working with files of images and assets. 3 | 4 | _Note: Assets doesn't have nor support subdirectories._ 5 | 6 | ```php 7 | newAuth($settings); 13 | $apiUrl = "https://your-mautic.com"; 14 | $api = new MauticApi(); 15 | $filesApi = $api->newApi("files", $auth, $apiUrl); 16 | ``` 17 | 18 | ### Get List of files 19 | ```php 20 | getList(); 24 | 25 | // Get list of some sub-directory (flags in this case) of media/images: 26 | $filesApi->setFolder('images/flags'); 27 | $files = $filesApi->getList(); 28 | 29 | // Get list of root media/files directory where the asset files are stored: 30 | $files = $filesApi->setFolder('assets'); 31 | $files = $filesApi->getList(); 32 | ``` 33 | ```json 34 | { 35 | "files":{ 36 | "3":"0b0f20185251d1c0cd5ff17950213fc9.png", 37 | "4":"0f530efdf837d3005bd2ab81cc30e878.jpeg", 38 | "5":"162a694f4101cb06c27c0a0699bd87c4.png", 39 | "6":"16ada2e2ecfa3f1d8cbb5d633f0bd8c6.png", 40 | ... 41 | } 42 | } 43 | ``` 44 | 45 | #### HTTP Request 46 | 47 | `GET /files/images` to get root images directory 48 | `GET /files/images?subdir=flags` to get images/flags directory 49 | `GET /files/assets` to get root assets directory 50 | 51 | #### Response 52 | 53 | `Expected Response Code: 200` 54 | 55 | See JSON code example. 56 | 57 | **Response Properties** 58 | 59 | Name|Type|Description 60 | ----|----|----------- 61 | files|array|List of requested files and directories 62 | 63 | ### Create File 64 | ```php 65 | dirname(__DIR__).'/'.'mauticlogo.png' // Must be a path to an existing file 68 | ); 69 | 70 | // Create a file in root media/images directory: 71 | $response = $fileApi->create($data); 72 | 73 | // Create a file in some sub-directory (flags in this case) of media/images: 74 | $filesApi->setFolder('images/flags'); 75 | $response = $fileApi->create($data); 76 | 77 | // Create a file in media/files directory where the asset files are stored: 78 | $files = $filesApi->setFolder('assets'); 79 | $response = $fileApi->create($data); 80 | ``` 81 | Creates a file. The file is sent via regular POST files array like a browser sends it during file upload. 82 | 83 | #### HTTP Request 84 | 85 | `POST /files/DIR/new` 86 | 87 | #### Response 88 | 89 | `Expected Response Code: 200` 90 | ```json 91 | { 92 | "file":{ 93 | "link":"http:\/\/yourmautic\/media\/images\/2b912b934dd2a4da49a226d0bf68bfea.png", 94 | "name":"2b912b934dd2a4da49a226d0bf68bfea.png" 95 | } 96 | } 97 | ``` 98 | 99 | **Response Properties** 100 | 101 | Name|Type|Description 102 | ----|----|----------- 103 | link|string|Appears only for files in image directory, not for assets 104 | name|string|File name of newly created file 105 | 106 | ### Delete File 107 | ```php 108 | delete($fileName); 111 | 112 | // Delete a file from some sub-directory (flags in this case) of media/images: 113 | $filesApi->setFolder('images/flags'); 114 | $response = $fileApi->delete($fileName); 115 | 116 | // Delete a file from media/files directory where the asset files are stored: 117 | $files = $filesApi->setFolder('assets'); 118 | $response = $fileApi->delete($fileName); 119 | ``` 120 | Delete a file. 121 | 122 | #### HTTP Request 123 | 124 | `DELETE /files/DIR/FILE/delete` 125 | 126 | #### Response 127 | 128 | `Expected Response Code: 200` 129 | ```json 130 | { 131 | "success": true 132 | } 133 | ``` 134 | -------------------------------------------------------------------------------- /source/includes/_plugin_translations.md: -------------------------------------------------------------------------------- 1 | ## Translations 2 | 3 | Mautic uses INI files for translations. 4 | 5 | HelloWorldBundle/
    6 | - - - Translations/
    7 | - - - - - - en_US/
    8 | - - - - - - - - - messages.ini 9 | 10 | 13 | 14 | The directory structure for translations should be `Translations/locale/domain.ini`. 15 | 16 | 17 | ### Domains 18 | 19 | Language strings can be organized into domains. Each domain should be its own file in the plugin's language locale folder(s). The plugin can use any domain it wants but Mautic makes consistent use of three domains: 20 | 21 | Domain | Description 22 | -------|------------ 23 | messages | Default domain for the translator service when no domain is specified 24 | flashes | Domain for [flash messages](#flash-messages) 25 | validators | Domain for [form validation](#validation) messages 26 | 27 | ### INI files 28 | 29 | > Sample INI files 30 | 31 | ``` 32 | ; plugins/HelloWorldBundle/Translations/en_US/messages.ini 33 | 34 | plugin.helloworld.contact_us="Contact Us" 35 | plugin.helloworld.goodbye="Goodbye and have a good day!" 36 | plugin.helloworld.greeting="Hello %name%!" 37 | plugin.helloworld.index="Hello World" 38 | plugin.helloworld.manage_worlds="Manage Worlds" 39 | plugin.helloworld.number_of_planets="{0}0 planets|{1}1 planet|]1,Inf[%planets% planets" 40 | plugin.helloworld.world="World" 41 | plugin.helloworld.worlds="%world% Description" 42 | ``` 43 | 44 | ``` 45 | ; plugins/HelloWorldBundle/Translations/en_US/flashes.ini 46 | 47 | plugin.helloworld.notice.thank_you="Thank you %name% for your interest! We will be in contact soon." 48 | plugin.helloworld.notice.planet_demoted="%planet% has been demoted to a dwarf planet." 49 | plugin.helloworld.error.planet_demotion_failed="%planet% could not be demoted because the scientists say so." 50 | ``` 51 | 52 | General guidelines for the translation keys: 53 | 54 | 1. Segment the key using a period 55 | 2. Use underscores to separate words 56 | 3. Must be unique 57 | 4. Be short yet descriptive 58 | 5. Use all lowercase letters and numbers (no punctuation in the key other than period or underscore) 59 | 60 | Guidelines for translation strings: 61 | 62 | 1. Wrap placeholders with %% 63 | 2. Use a single key for duplicate translation strings. 64 | 3. Use " for double quotes 65 | 4. HTML is allowed 66 | 67 | ### Using the translator 68 | Refer to the [translator service](#translator) to learn how to use translations in the code. 69 | 70 | ### Using the translator in your javascript 71 | If your bundle implements custom javascript where translations are 72 | required, you can get them by the `Mautic.translate(key, params)` method. 73 | 74 | Create a `javascript.ini` in the same directory as the `messages.ini` as 75 | described above. Any translation strings added to that file will be 76 | available when translating in javascript. 77 | 78 | For example, if your `javascript.ini` file contained the following 79 | translation strings: 80 | 81 | ```ini 82 | mautic.core.dynamic_content="Dynamic Content" 83 | mautic.core.dynamic_content.new="Dynamic Content %number%" 84 | ``` 85 | 86 | You can request those translation strings in your javascript by 87 | passing the key to the `Mautic.translate()` function. 88 | 89 | ```js 90 | Mautic.translate("mautic.core.dynamic_content"); 91 | // outputs "Dynamic Content" 92 | ``` 93 | 94 | String interpolation for messages with variables works with js 95 | translations just as you'd expect. 96 | 97 | ```js 98 | Mautic.translate("mautic.core.dynamic_content.new", {number: 4}); 99 | // outputs "Dynamic Content 4" 100 | ``` 101 | -------------------------------------------------------------------------------- /source/includes/_mauticjs_api_reference.md: -------------------------------------------------------------------------------- 1 | ## MauticJS API Functions 2 | 3 | ### MauticJS.serialize(object) 4 | 5 | This method will transform an object properties into a key=value string, concatenating them with an ampersand. It is used when submitting data via MauticJS.makeCORSRequest. 6 | 7 | ```js 8 | var obj = {firstname: "John", lastname: "Doe"}; 9 | 10 | var serialized = MauticJS.serialize(obj); 11 | 12 | alert(serialized); // Shows "firstname=John&lastname=Doe" 13 | ``` 14 | 15 | ### MauticJS.documentReady(functionName|function) 16 | 17 | This method will check if the document has finished rendering, then execute the given function. The function argument can be the name of a function or an anonymous function. 18 | 19 | ```js 20 | function test() { 21 | alert('test'); 22 | } 23 | 24 | MauticJS.documentReady(test); 25 | ``` 26 | 27 | ### MauticJS.iterateCollection(collection)(functionName|function) 28 | 29 | This method will iterate over the provided collection (array, object, HTMLCollection, etc) using the provided function argument. The function argument can be the name of a function or an anonymous function. The function will receive the collection node and the iteration number as arguments. 30 | 31 | ```js 32 | var videos = document.getElementsByTagName('video'); 33 | 34 | // Add a custom data attribute to all videos 35 | MauticJS.iterateCollection(videos)(function(node, i) { 36 | node.dataset.customAttribute = 'test'; 37 | }); 38 | ``` 39 | 40 | ### MauticJS.log(arguments) 41 | 42 | This method is a lightweight wrapper around `console.log`. It exists because some browsers do not provide this functionality. It takes any number of arguments, logs them, then passes those same arguments to the `console.log` method if it exists. 43 | 44 | ```js 45 | MauticJS.log('Something happened'); 46 | ``` 47 | 48 | ### MauticJS.createCORSRequest(method, url) 49 | 50 | This method creates an `XMLHttpRequest`, then checks to see if it supports the `withCredentials` property. If not, we are probably on windows, so it then checks for the existence of `XDomainRequest`, then creates it if found. Finally, it opens then returns the xhr. It can be used to send cross domain requests that include the cookies for the domain. It is used internally within the `MauticJS.makeCORSRequest` method. 51 | 52 | ```js 53 | MauticJS.createCORSRequest('GET', 'https://mymautic.com/dwc/slot1'); 54 | ``` 55 | 56 | ### MauticJS.makeCORSRequest(method, url, data, callbackSuccess, callbackError) 57 | 58 | This method uses `MauticJS.createCORSRequest` to open a cross domain request to the specified URL, then sets the callbackSuccess and callbackError values appropriately. You may omit either of the callbacks. If you do, the callbacks are replaced with a basic function that uses `MauticJS.log(response)` to log the response from the request. The callback methods receive the server response and the xhr object as arguments. If the response is determined to be a JSON string, it is automatically parsed to a JSON object. The data argument will be serialized using `MauticJS.serialize(data)`, then sent with the request to the server. All requests made this way have the `X-Requested-With` header set to `XMLHttpRequest`. 59 | 60 | ```js 61 | MauticJS.makeCORSRequest('GET', 'https://mymautic.com/dwc/slot1', [], function (response, xhr) { 62 | if (response.success) { 63 | document.getElementById('slot1').innerHTML = response.content; 64 | } 65 | }); 66 | ``` 67 | 68 | ### MauticJS.parseTextToJSON(maybeJSON) 69 | 70 | This method will take a text string and check to see if it is a valid JSON string. If so, it parses it into a JSON object and returns. If not, then it will simply return the argument passed to it. 71 | 72 | ```js 73 | var text = '{"firstname": "John", "lastname": "Doe"}'; 74 | 75 | var json = MauticJS.parseTextToJSON(text); 76 | 77 | alert(json); // Will show [object Object] 78 | 79 | var text = 'not valid json'; 80 | 81 | var json = MauticJS.parseTextToJSON(text); 82 | 83 | alert(json); // Will show 'not valid json' 84 | ``` 85 | 86 | ### MauticJS.insertScript(scriptUrl) 87 | 88 | This method will insert a script tag with the provided URL in the head of your document, before other scripts. 89 | 90 | ```js 91 | MauticJS.insertScript('http://google.com/ga.js'); 92 | ``` -------------------------------------------------------------------------------- /source/includes/_plugin_integrations_sync.md: -------------------------------------------------------------------------------- 1 | ### Integration Sync Engine 2 | The sync engine supports bidirectional syncing between Mautic's contact and companies with 3rd party objects. The engine generates a "sync report" from Mautic that it converts to a "sync order" for the integration to process. It then asks for a "sync report" from the integration which it converts to a "sync order" for Mautic to process. 3 | 4 | When building the report, Mautic or the integration will fetch the objects that have been modified or created within the specified timeframe. If the integration supports changes at the field level, it should tell the report on a per field basis when the field was last updated. Otherwise, it should tell the report when the object itself was last modified. These dates are used by the "sync judge" to determine which value should be used in a bi-directional sync. 5 | 6 | The sync is initiated using the `mautic:integrations:sync` command. For example: 7 | `php app/console mautic:integrations:sync HelloWorld --start-datetime="2020-01-01 00:00:00" --end-datetime="2020-01-02 00:00:00"`. (Use `php bin/console` for Mautic 3). 8 | 9 | #### Registering the Integration for the Sync Engine 10 | To tell the IntegrationsBundle that this integration provides a syncing feature, tag the integration or support class with `mautic.sync_integration` in the plugin's `app/config.php`. 11 | 12 | ```php 13 | [ 17 | // ... 18 | 'integrations' => [ 19 | // ... 20 | 'helloworld.integration.sync' => [ 21 | 'class' => \MauticPlugin\HelloWorldBundle\Integration\Support\SyncSupport::class, 22 | 'tags' => [ 23 | 'mautic.sync_integration', 24 | ], 25 | ], 26 | // ... 27 | ], 28 | // ... 29 | ], 30 | // ... 31 | ]; 32 | ``` 33 | 34 | The `SyncSupport` class must implement `\Mautic\IntegrationsBundle\Integration\Interfaces\SyncInterface`. 35 | 36 | 37 | #### Syncing 38 | 39 | ##### The Mapping Manual 40 | The mapping manual tells the sync engine which integration should be synced with which Mautic object (contact or company), the integration fields that were mapped to Mautic fields, and the direction the data is supposed to flow. 41 | 42 | See [https://github.com/mautic/plugin-helloworld/blob/mautic-2/Sync/Mapping/Manual/MappingManualFactory.php]( https://github.com/mautic/plugin-helloworld/blob/mautic-2/Sync/Mapping/Manual/MappingManualFactory.php) 43 | 44 | ##### The Sync Data Exchange 45 | This is where the sync takes place and is executed by the `mautic:integrations:sync` command. Mautic and the integration will build their respective reports of new or modified objects then execute the order from the other side. 46 | 47 | See [https://github.com/mautic/plugin-helloworld/blob/mautic-2/Sync/DataExchange/SyncDataExchange.php](https://github.com/mautic/plugin-helloworld/blob/mautic-2/Sync/DataExchange/SyncDataExchange.php) 48 | 49 | ###### Building Sync Report 50 | The sync report tells the sync engine what objects are new and/or modified between the two timestamps given by the engine (or up to the integration discretion if it is a first time sync). Objects should be processed in batches which can be done using the `RequestDAO::getSyncIteration()`. The sync engine will execute `SyncDataExchangeInterface::getSyncReport()` until a report comes back with no objects. 51 | 52 | If the integration supports field level change tracking, it should tell the report so that the sync engine can merge the two data sets more accurately. 53 | 54 | See [https://github.com/mautic/plugin-helloworld/blob/mautic-2/Sync/DataExchange/ReportBuilder.php](https://github.com/mautic/plugin-helloworld/blob/mautic-2/Sync/DataExchange/ReportBuilder.php) 55 | 56 | ###### Executing the Sync Order 57 | The sync order contains all the changes the sync engine has determined should be written to the integration. The integration should communicate back the ID of any objects created or adjust objects as needed such as if they were converted from one to another or deleted. 58 | 59 | See [https://github.com/mautic/plugin-helloworld/blob/mautic-2/Sync/DataExchange/OrderExecutioner.php](https://github.com/mautic/plugin-helloworld/blob/mautic-2/Sync/DataExchange/OrderExecutioner.php) 60 | -------------------------------------------------------------------------------- /source/includes/_api_endpoint_tags.md: -------------------------------------------------------------------------------- 1 | ## Tags 2 | Use this endpoint to obtain details on Mautic's tags. Implemented in Mautic 2.12.0. 3 | 4 | ```php 5 | newAuth($settings); 12 | $apiUrl = "https://your-mautic.com"; 13 | $api = new MauticApi(); 14 | $tagApi = $api->newApi("tags", $auth, $apiUrl); 15 | ``` 16 | 17 | ### Get Tag 18 | ```php 19 | get($id); 23 | ``` 24 | ```json 25 | { 26 | "tag": { 27 | "id": 34, 28 | "tag": "tagA", 29 | } 30 | } 31 | ``` 32 | Get an individual tag by ID. 33 | 34 | #### HTTP Request 35 | 36 | `GET /tags/ID` 37 | 38 | #### Response 39 | 40 | `Expected Response Code: 200` 41 | 42 | See JSON code example. 43 | 44 | **Tag Properties** 45 | 46 | Name|Type|Description 47 | ----|----|----------- 48 | id|int|ID of the tag 49 | tag|string|Title of the tag 50 | 51 | ### List Tags 52 | ```php 53 | getList($searchFilter, $start, $limit, $orderBy, $orderByDir, $publishedOnly, $minimal); 57 | ``` 58 | ```json 59 | { 60 | "total":1, 61 | "tags":[ 62 | { 63 | "id": 34, 64 | "tag": "tagA", 65 | } 66 | ] 67 | } 68 | ``` 69 | #### HTTP Request 70 | 71 | `GET /tags` 72 | 73 | **Query Parameters** 74 | 75 | Name|Description 76 | ----|----------- 77 | search|String or search command to filter entities by. 78 | start|Starting row for the entities returned. Defaults to 0. 79 | limit|Limit number of entities to return. Defaults to the system configuration for pagination (30). 80 | orderBy|Column to sort by. Can use any column listed in the response. 81 | orderByDir|Sort direction: asc or desc. 82 | publishedOnly|Only return currently published entities. 83 | minimal|Return only array of entities without additional lists in it. 84 | 85 | #### Response 86 | 87 | `Expected Response Code: 200` 88 | 89 | See JSON code example. 90 | 91 | **Properties** 92 | 93 | Same as [Get Tag](#get-tag). 94 | 95 | ### Create Tag 96 | ```php 97 | 'Tag A', 101 | ); 102 | 103 | $tag = $tagApi->create($data); 104 | ``` 105 | Create a new tag. 106 | 107 | #### HTTP Request 108 | 109 | `POST /tags/new` 110 | 111 | **Post Parameters** 112 | 113 | Name|Type|Description 114 | ----|----|----------- 115 | id|int|ID of the tag 116 | tag|string|Title of the tag 117 | 118 | #### Response 119 | 120 | `Expected Response Code: 201` 121 | 122 | **Properties** 123 | 124 | Same as [Get Tag](#get-tag). 125 | 126 | ### Edit Tag 127 | ```php 128 | 'Tag B', 133 | ); 134 | 135 | // Create new a tag of ID 1 is not found? 136 | $createIfNotFound = true; 137 | 138 | $tag = $tagApi->edit($id, $data, $createIfNotFound); 139 | ``` 140 | Edit a new tag. Note that this supports PUT or PATCH depending on the desired behavior. 141 | 142 | **PUT** creates a tag if the given ID does not exist and clears all the tag information, adds the information from the request. 143 | **PATCH** fails if the tag with the given ID does not exist and updates the tag field values with the values form the request. 144 | 145 | #### HTTP Request 146 | 147 | To edit a tag and return a 404 if the tag is not found: 148 | 149 | `PATCH /tags/ID/edit` 150 | 151 | To edit a tag and create a new one if the tag is not found: 152 | 153 | `PUT /tags/ID/edit` 154 | 155 | **Post Parameters** 156 | 157 | Name|Type|Description 158 | ----|----|----------- 159 | id|int|ID of the tag 160 | tag|string|Title of the tag 161 | 162 | #### Response 163 | 164 | If `PUT`, the expected response code is `200` if the tag was edited or `201` if created. 165 | 166 | If `PATCH`, the expected response code is `200`. 167 | 168 | **Properties** 169 | 170 | Same as [Get Tag](#get-tag). 171 | 172 | ### Delete Tag 173 | ```php 174 | delete($id); 177 | ``` 178 | Delete a tag. 179 | 180 | #### HTTP Request 181 | 182 | `DELETE /tags/ID/delete` 183 | 184 | #### Response 185 | 186 | `Expected Response Code: 200` 187 | 188 | **Properties** 189 | 190 | Same as [Get Tag](#get-tag). 191 | -------------------------------------------------------------------------------- /source/includes/_plugin_integrations.md: -------------------------------------------------------------------------------- 1 | ## Integration Framework 2 | 3 | The IntegrationsBundle is meant to be a drop in replacement for Mautic's PluginBundle's AbstractIntegration class. It provides cleaner interfaces for configuring, authenticating and syncing contacts/companies with 3rd party integrations. 4 | 5 | An example HelloWorld plugin is available [here](https://github.com/mautic/plugin-helloworld). 6 | 7 | ### Using the Integration Framework 8 | 9 | *If the integration requires authentication with the 3rd party service* 10 | 11 | 1. [Register the integration](#registering-the-integration-for-authentication) as an integration that requires configuration options. 12 | 2. Create a custom Symfony form type for the required credentials and return it as part of the [config interface](#mauticpluginintegrationsbundleintegrationinterfacesconfigformauthinterface). 13 | 3. Create a custom service that builds and configures the Guzzle client required to authenticate and communicate with the 3rd party service. Use an [existing supported factory or create a new one](#authentication-providers). 14 | 15 | *If the integration has extra configuration settings for features unique to it* 16 | 17 | 1. [Register the integration](#registering-the-integration-for-configuration) as an integration that requires configuration options. 18 | 2. Create a custom Symfony form type for the features and return it as part of the [config interface](#mauticpluginintegrationsbundleintegrationinterfacesconfigformfeaturesettingsinterface). 19 | 20 | *If the integration syncs with Mautic's contacts and/or companies* 21 | 22 | 1. Read about [the sync engine](#integration-sync-engine). 23 | 24 | *If the integration includes a builder integration (email or landing page)* 25 | 26 | 1. [Register the integration](#registering-the-integration-for-builders) as an integration that provides a custom builder. 27 | 2. Configure what featured builders the integration supports (Mautic currently supports "email" and "page" builders). 28 | 29 | ### Basics 30 | Each integration provides its unique name as registered with Mautic, icon, and display name. When an integration is registered, the integration helper classes will manage the `\Mautic\PluginBundle\Entity\Integration` object through `\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface`. It handles decryption and encryption of the integration's API keys so the implementing code never has to. 31 | 32 | ##### Registering the Integration 33 | All integrations whether it uses the config, auth or sync interfaces must have a class that registers itself with Mautic. The integration will be listed no the `/s/plugins` page. 34 | 35 | In the plugin's `Config/config.php`, register the integration using the tag `mautic.basic_integration`. 36 | 37 | ```php 38 | [ 42 | // ... 43 | 'integrations' => [ 44 | 'helloworld.integration' => [ 45 | 'class' => \MauticPlugin\HelloWorldBundle\Integration\HelloWorldIntegration::class, 46 | 'tags' => [ 47 | 'mautic.basic_integration', 48 | ], 49 | ], 50 | // ... 51 | ], 52 | // ... 53 | ], 54 | // ... 55 | ]; 56 | ``` 57 | 58 | The `HelloWorldIntegration` will need to implement `\Mautic\IntegrationsBundle\Integration\Interfaces\IntegrationInterface` and `\Mautic\IntegrationsBundle\Integration\Interfaces\BasicInterface` interfaces. Most use cases can simply extend the `\Mautic\IntegrationsBundle\Integration\BasicIntegration` abstract class then define the `getName()`, `getDisplayName()` and `getIcon()` methods. 59 | 60 | ```php 61 | (default options) 16 | * $('#content').highlight('lorem'); 17 | * 18 | * // search for and highlight more terms at once 19 | * // so you can save some time on traversing DOM 20 | * $('#content').highlight(['lorem', 'ipsum']); 21 | * $('#content').highlight('lorem ipsum'); 22 | * 23 | * // search only for entire word 'lorem' 24 | * $('#content').highlight('lorem', { wordsOnly: true }); 25 | * 26 | * // don't ignore case during search of term 'lorem' 27 | * $('#content').highlight('lorem', { caseSensitive: true }); 28 | * 29 | * // wrap every occurrance of term 'ipsum' in content 30 | * // with 31 | * $('#content').highlight('ipsum', { element: 'em', className: 'important' }); 32 | * 33 | * // remove default highlight 34 | * $('#content').unhighlight(); 35 | * 36 | * // remove custom highlight 37 | * $('#content').unhighlight({ element: 'em', className: 'important' }); 38 | * 39 | * 40 | * Copyright (c) 2009 Bartek Szopka 41 | * 42 | * Licensed under MIT license. 43 | * 44 | */ 45 | 46 | jQuery.extend({ 47 | highlight: function (node, re, nodeName, className) { 48 | if (node.nodeType === 3) { 49 | var match = node.data.match(re); 50 | if (match) { 51 | var highlight = document.createElement(nodeName || 'span'); 52 | highlight.className = className || 'highlight'; 53 | var wordNode = node.splitText(match.index); 54 | wordNode.splitText(match[0].length); 55 | var wordClone = wordNode.cloneNode(true); 56 | highlight.appendChild(wordClone); 57 | wordNode.parentNode.replaceChild(highlight, wordNode); 58 | return 1; //skip added node in parent 59 | } 60 | } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children 61 | !/(script|style)/i.test(node.tagName) && // ignore script and style nodes 62 | !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted 63 | for (var i = 0; i < node.childNodes.length; i++) { 64 | i += jQuery.highlight(node.childNodes[i], re, nodeName, className); 65 | } 66 | } 67 | return 0; 68 | } 69 | }); 70 | 71 | jQuery.fn.unhighlight = function (options) { 72 | var settings = { className: 'highlight', element: 'span' }; 73 | jQuery.extend(settings, options); 74 | 75 | return this.find(settings.element + "." + settings.className).each(function () { 76 | var parent = this.parentNode; 77 | parent.replaceChild(this.firstChild, this); 78 | parent.normalize(); 79 | }).end(); 80 | }; 81 | 82 | jQuery.fn.highlight = function (words, options) { 83 | var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false }; 84 | jQuery.extend(settings, options); 85 | 86 | if (words.constructor === String) { 87 | words = [words]; 88 | } 89 | words = jQuery.grep(words, function(word, i){ 90 | return word != ''; 91 | }); 92 | words = jQuery.map(words, function(word, i) { 93 | return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 94 | }); 95 | if (words.length == 0) { return this; }; 96 | 97 | var flag = settings.caseSensitive ? "" : "i"; 98 | var pattern = "(" + words.join("|") + ")"; 99 | if (settings.wordsOnly) { 100 | pattern = "\\b" + pattern + "\\b"; 101 | } 102 | var re = new RegExp(pattern, flag); 103 | 104 | return this.each(function () { 105 | jQuery.highlight(this, re, settings.element, settings.className); 106 | }); 107 | }; 108 | 109 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | activesupport (4.1.16) 5 | i18n (~> 0.6, >= 0.6.9) 6 | json (~> 1.7, >= 1.7.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.1) 9 | tzinfo (~> 1.1) 10 | celluloid (0.16.0) 11 | timers (~> 4.0.0) 12 | chunky_png (1.3.6) 13 | coffee-script (2.4.1) 14 | coffee-script-source 15 | execjs 16 | coffee-script-source (1.10.0) 17 | compass (1.0.3) 18 | chunky_png (~> 1.2) 19 | compass-core (~> 1.0.2) 20 | compass-import-once (~> 1.0.5) 21 | rb-fsevent (>= 0.9.3) 22 | rb-inotify (>= 0.9) 23 | sass (>= 3.3.13, < 3.5) 24 | compass-core (1.0.3) 25 | multi_json (~> 1.0) 26 | sass (>= 3.3.0, < 3.5) 27 | compass-import-once (1.0.5) 28 | sass (>= 3.2, < 3.5) 29 | em-websocket (0.5.1) 30 | eventmachine (>= 0.12.9) 31 | http_parser.rb (~> 0.6.0) 32 | erubis (2.7.0) 33 | eventmachine (1.2.0.1) 34 | execjs (2.7.0) 35 | ffi (1.13.1) 36 | haml (5.1.2) 37 | temple (>= 0.8.0) 38 | tilt 39 | hike (1.2.3) 40 | hitimes (1.2.4) 41 | hooks (0.4.1) 42 | uber (~> 0.0.14) 43 | http_parser.rb (0.6.0) 44 | i18n (0.7.0) 45 | json (1.8.3) 46 | kramdown (1.11.1) 47 | libv8 (3.16.14.15) 48 | listen (2.10.1) 49 | celluloid (~> 0.16.0) 50 | rb-fsevent (>= 0.9.3) 51 | rb-inotify (>= 0.9) 52 | middleman (3.3.12) 53 | coffee-script (~> 2.2) 54 | compass (>= 1.0.0, < 2.0.0) 55 | compass-import-once (= 1.0.5) 56 | execjs (~> 2.0) 57 | haml (>= 4.0.5) 58 | kramdown (~> 1.2) 59 | middleman-core (= 3.3.12) 60 | middleman-sprockets (>= 3.1.2) 61 | sass (>= 3.4.0, < 4.0) 62 | uglifier (~> 2.5) 63 | middleman-core (3.3.12) 64 | activesupport (~> 4.1.0) 65 | bundler (~> 1.1) 66 | erubis 67 | hooks (~> 0.3) 68 | i18n (~> 0.7.0) 69 | listen (>= 2.7.9, < 3.0) 70 | padrino-helpers (~> 0.12.3) 71 | rack (>= 1.4.5, < 2.0) 72 | rack-test (~> 0.6.2) 73 | thor (>= 0.15.2, < 2.0) 74 | tilt (~> 1.4.1, < 2.0) 75 | middleman-deploy (1.0.0) 76 | middleman-core (>= 3.2) 77 | net-sftp 78 | ptools 79 | middleman-gh-pages (0.3.1) 80 | rake (> 0.9.3) 81 | middleman-livereload (3.3.4) 82 | em-websocket (~> 0.5.1) 83 | middleman-core (~> 3.2) 84 | rack-livereload (~> 0.3.15) 85 | middleman-sprockets (3.4.2) 86 | middleman-core (>= 3.3) 87 | sprockets (~> 2.12.1) 88 | sprockets-helpers (~> 1.1.0) 89 | sprockets-sass (~> 1.3.0) 90 | middleman-syntax (2.1.0) 91 | middleman-core (>= 3.2) 92 | rouge (~> 1.0) 93 | minitest (5.9.0) 94 | multi_json (1.15.0) 95 | net-sftp (3.0.0) 96 | net-ssh (>= 5.0.0, < 7.0.0) 97 | net-ssh (6.1.0) 98 | padrino-helpers (0.12.8) 99 | i18n (~> 0.6, >= 0.6.7) 100 | padrino-support (= 0.12.8) 101 | tilt (~> 1.4.1) 102 | padrino-support (0.12.8) 103 | activesupport (>= 3.1) 104 | ptools (1.4.2) 105 | rack (1.6.13) 106 | rack-livereload (0.3.16) 107 | rack 108 | rack-test (0.6.3) 109 | rack (>= 1.0) 110 | rake (12.3.3) 111 | rb-fsevent (0.9.7) 112 | rb-inotify (0.9.7) 113 | ffi (>= 0.5.0) 114 | redcarpet (3.2.3) 115 | ref (2.0.0) 116 | rouge (1.7.2) 117 | ruby18_source_location (0.2) 118 | sass (3.4.22) 119 | sprockets (2.12.5) 120 | hike (~> 1.2) 121 | multi_json (~> 1.0) 122 | rack (~> 1.0) 123 | tilt (~> 1.1, != 1.3.0) 124 | sprockets-helpers (1.1.0) 125 | sprockets (~> 2.0) 126 | sprockets-sass (1.3.1) 127 | sprockets (~> 2.0) 128 | tilt (~> 1.1) 129 | temple (0.8.2) 130 | therubyracer (0.12.2) 131 | libv8 (~> 3.16.14.0) 132 | ref 133 | thor (0.19.1) 134 | thread_safe (0.3.5) 135 | tilt (1.4.1) 136 | timers (4.0.4) 137 | hitimes 138 | tzinfo (1.2.2) 139 | thread_safe (~> 0.1) 140 | uber (0.0.15) 141 | uglifier (2.7.2) 142 | execjs (>= 0.3.0) 143 | json (>= 1.8.0) 144 | 145 | PLATFORMS 146 | ruby 147 | 148 | DEPENDENCIES 149 | middleman (~> 3.3.0) 150 | middleman-deploy (~> 1.0) 151 | middleman-gh-pages 152 | middleman-livereload (~> 3.3.0) 153 | middleman-syntax 154 | rake (~> 12.3.3) 155 | redcarpet (~> 3.2.1) 156 | rouge (= 1.7.2) 157 | ruby18_source_location 158 | therubyracer 159 | wdm (~> 0.1.0) 160 | 161 | BUNDLED WITH 162 | 1.17.3 163 | -------------------------------------------------------------------------------- /source/includes/_plugin_manipulating_contacts.md: -------------------------------------------------------------------------------- 1 | ## Manipulating Contacts (Leads) 2 | 3 | ```php 4 | getModel('lead'); 8 | 9 | /** @var \Mautic\LeadBundle\Entity\Lead $currentLead */ 10 | $currentLead = $leadModel->getCurrentLead(); 11 | 12 | // To obtain the tracking ID as well, pass true 13 | list($currentLead, $trackingId, $trackingIdIsNewlyGenerated) = $leadModel->getCurrentLead(true); 14 | 15 | // To obtain just the tracking ID; pass true as an argument to force regeneration of the tracking ID and cookies 16 | list($trackingId, $trackingIdIsNewlyGenerated) = $leadModel->getTrackingCookie(); 17 | 18 | // Set the currently tracked lead and generate tracking cookies 19 | $lead = new Lead(); 20 | // ... 21 | $leadModel->setCurrentLead($lead); 22 | 23 | // Set a lead for system use purposes (i.e. events that use getCurrentLead()) but without generating tracking cookies 24 | $leadModel->setSystemCurrentLead($lead); 25 | 26 | ``` 27 | 28 | 31 | 32 | Many plugins extending Mautic will be manipulating leads in one way or another. Here is a quick summary of how to obtain the current lead and/or manipulate the leads data. 33 | 34 | #### Lead Tracking 35 | 36 | Leads are tracked by two cookies. The first cookie notes which ID the lead is tracked under Mautic as. The second is to track the lead's activity for the current session (defaults to 30 minutes and resets during each lead interaction). 37 | 38 | `mautic_session_id` holds the value of the lead's current session ID. That value is then name of the cookie that holds the lead's ID. 39 | 40 | Review the sample code on how to obtain the currently tracked lead. 41 | 42 | As of Mautic 2.2.0, a cookie is also placed on any domain with mtc.js embedded (that's allowed by Mautic's CORS settings) as `mtc_id`. This will contain the ID of the currently tracked contact. 43 | 44 |
    45 | 46 | #### Creating New Leads 47 | ```php 48 | getModel('lead'); 51 | $lead = $leadModel->getCurrentLead(); 52 | $leadId = $lead->getId(); 53 | 54 | // OR generate a completely new lead with 55 | $lead = new Lead(); 56 | $lead->setNewlyCreated(true); 57 | $leadId = null; 58 | 59 | // IP address of the request 60 | $ipAdddress = $this->get('mautic.helper.ip_lookup')->getIpAddress(); 61 | 62 | // Updated/new fields 63 | $leadFields = array( 64 | 'firstname' => 'Bob', 65 | //... 66 | ); 67 | 68 | // Optionally check for identifier fields to determine if the lead is unique 69 | $uniqueLeadFields = $this->getModel('lead.field')->getUniqueIdentiferFields(); 70 | $uniqueLeadFieldData = array(); 71 | 72 | // Check if unique identifier fields are included 73 | $inList = array_intersect_key($leadFields, $uniqueLeadFields); 74 | foreach ($inList as $k => $v) { 75 | if (empty($query[$k])) { 76 | unset($inList[$k]); 77 | } 78 | 79 | if (array_key_exists($k, $uniqueLeadFields)) { 80 | $uniqueLeadFieldData[$k] = $v; 81 | } 82 | } 83 | 84 | // If there are unique identifier fields, check for existing leads based on lead data 85 | if (count($inList) && count($uniqueLeadFieldData)) { 86 | $existingLeads = $this->getDoctrine()->getManager()->getRepository('MauticLeadBundle:Lead')->getLeadsByUniqueFields( 87 | $uniqueLeadFieldData, 88 | $leadId // If a currently tracked lead, ignore this ID when searching for duplicates 89 | ); 90 | if (!empty($existingLeads)) { 91 | // Existing found so merge the two leads 92 | $lead = $leadModel->mergeLeads($lead, $existingLeads[0]); 93 | } 94 | 95 | // Get the lead's currently associated IPs 96 | $leadIpAddresses = $lead->getIpAddresses(); 97 | 98 | // If the IP is not already associated, do so (the addIpAddress will automatically handle ignoring 99 | // the IP if it is set to be ignored in the Configuration) 100 | if (!$leadIpAddresses->contains($ipAddress)) { 101 | $lead->addIpAddress($ipAddress); 102 | } 103 | } 104 | 105 | // Set the lead's data 106 | $leadModel->setFieldValues($lead, $leadFields); 107 | 108 | // Save the entity 109 | $leadModel->saveEntity($lead); 110 | 111 | // Set the updated lead 112 | $leadModel->setCurrentLead($lead); 113 | ``` 114 | 115 | To create a new lead, use the `\Mautic\LeadBundle\Entity\Lead` entity. Review the code sample. 116 | -------------------------------------------------------------------------------- /source/includes/_cache.md: -------------------------------------------------------------------------------- 1 | # Cache Bundle 2 | 3 | Enables PSR-6 and PSR-16 caching. Check: [Symfony Cache Component](https://symfony.com/doc/3.4/components/cache.html) 4 | 5 | ## Namespace versus tag 6 | 7 | This bundle introduces tags to cache. All its adapters are fully tag 8 | aware which makes the use of namespace obsolete for daily use. 9 | 10 | Previously if you wanted to keep control on cache section and did not want to hold 11 | the index of all keys to clear you would have to use namespace. 12 | 13 | Disadvantage of this approach is a new adapter being created for each namespace. 14 | 15 | [Symfony 3.4 Cache](https://symfony.com/doc/3.4/components/cache.html) uses tag-aware adapters. If you want to clear all records related to your bundle 16 | or component you just need to tag them. 17 | 18 | ```php 19 | /** @var CacheProvider $cache */ 20 | $cache = $this->get('mautic.cache.provider'); 21 | 22 | /** @var CacheItemInterface $item */ 23 | $item = $cache->getItem('test_tagged_Item'); 24 | $item->set('yesa!!!'); 25 | $item->tag(['firstTag', 'secondTag']); 26 | $item->expiresAfter(20000); 27 | ``` 28 | 29 | All you need to do now is to clear all tagged items: 30 | 31 | ```php 32 | $cache->invalidateTags(['firstTag']); 33 | ``` 34 | 35 | ### Pools clearing 36 | 37 | Removing Cache Items¶ 38 | 39 | Cache Pools include methods to delete a cache item, some of them or all of them. 40 | The most common is `Psr\\Cache\\CacheItemPoolInterface::deleteItem`, which deletes the cache item identified by the given key. 41 | 42 | ```php 43 | $isDeleted = $cache->deleteItem('user_'.$userId); 44 | ``` 45 | 46 | Use the Psr\\Cache\\CacheItemPoolInterface::deleteItems method to delete several cache items simultaneously (it returns true only if all the items have been deleted, even when any or some of them don't exist): 47 | 48 | ## Configuration 49 | 50 | Plugin comes preconfigured to utilize filesystem caching. 51 | 52 | These are the default settings: 53 | ```php 54 | 'cache_adapter' => 'mautic.cache.adapter.filesystem', 55 | 'cache_prefix' => 'app', 56 | 'cache_lifetime' => 86400 57 | ``` 58 | 59 | 60 | They can be overridden in **local.php** like this: 61 | 62 | ```php 63 | 'cache_adapter' => 'mautic.cache.adapter.redis', 64 | 'cache_prefix' => 'app_cache', 65 | 'cache_lifetime' => 86400, 66 | ``` 67 | 68 | 69 | ## Delivered adapters 70 | * mautic.cache.adapter.filesystem 71 | * mautic.cache.adapter.memcached 72 | ```php 73 | 'memcached' => [ 74 | 'servers' => ['memcached://localhost'], 75 | 'options' => [ 76 | 'compression' => true, 77 | 'libketama_compatible' => true, 78 | 'serializer' => 'igbinary', 79 | ], 80 | ], 81 | ``` 82 | * mautic.cache.adapter.redis 83 | Redis configuration in **local.php**: 84 | ```php 85 | 'redis' => [ 86 | 'dsn' => 'redis://localhost', 87 | 'options' => [ 88 | 'lazy' => false, 89 | 'persistent' => 0, 90 | 'persistent_id' => null, 91 | 'timeout' => 30, 92 | 'read_timeout' => 0, 93 | 'retry_interval' => 0, 94 | ] 95 | ], 96 | ``` 97 | 98 | In order to use another adapter just set it up as a service 99 | 100 | ## Clearing the cache 101 | 102 | The cache is cleared when **cache:clear** command is run. The cache can be cleared by running 103 | 104 | ```bash 105 | bin/console mautic:cache:clear 106 | ``` 107 | 108 | ## Features auto pruning on adapter initialization 109 | 110 | ## Usage 111 | 112 | ### PSR-6 113 | 114 | ```php 115 | /** @var CacheProvider $cache */ 116 | $cache = $this->get('mautic.cache.provider'); 117 | 118 | /** @var CacheItemInterface $item */ 119 | $item = $cache->getItem('test_tagged_Item'); 120 | $item->set('yesa!!!'); 121 | $item->tag(['firstTag', 'secondTag']); 122 | $item->expiresAfter(20000); 123 | 124 | $cache->save($item); 125 | 126 | $item = $cache->getItem('test_nottagged_Item2'); 127 | $item->tag(['firstTag']); 128 | $cache->save($item); 129 | 130 | $item = $cache->getItem('test_nottagged_Item3'); 131 | $item->tag(['secondTag']); 132 | $cache->save($item); 133 | 134 | $cache->commit(); 135 | 136 | var_dump($cache->getItem('test_nottagged_Item2')->isHit()); 137 | var_dump($cache->getItem('test_nottagged_Item3')->isHit()); 138 | 139 | $cache->invalidateTags(['firstTag']); 140 | 141 | var_dump($cache->getItem('test_nottagged_Item2')->isHit()); 142 | var_dump($cache->getItem('test_nottagged_Item3')->isHit()); 143 | 144 | $cache->commit(); 145 | ``` 146 | 147 | ### PSR-16 148 | 149 | ```php 150 | /** @var CacheProvider $cache */ 151 | $cache = $this->get('mautic.cache.provider'); 152 | 153 | 154 | $simpleCache = $cache->getSimpleCache(); 155 | $test = $simpleCache->get('test_value'); 156 | 157 | var_dump($test); 158 | ``` 159 | -------------------------------------------------------------------------------- /source/includes/_plugin_integrations_configuration.md: -------------------------------------------------------------------------------- 1 | ### Integration Configuration 2 | The integration plugin provides interfaces to display and store configuration options that can be accessed through the `\Mautic\PluginBundle\Entity\Integration` object. 3 | 4 | #### Registering the Integration for Configuration 5 | To tell the IntegrationsBundle that this integration has configuration options, tag the integration or support class with `mautic.config_integration` in the plugin's `app/config.php`. 6 | 7 | ```php 8 | [ 12 | // ... 13 | 'integrations' => [ 14 | // ... 15 | 'helloworld.integration.configuration' => [ 16 | 'class' => \MauticPlugin\HelloWorldBundle\Integration\Support\ConfigSupport::class, 17 | 'tags' => [ 18 | 'mautic.config_integration', 19 | ], 20 | ], 21 | // ... 22 | ], 23 | // ... 24 | ], 25 | // ... 26 | ]; 27 | ``` 28 | 29 | The `ConfigSupport` class must implement `\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormInterface`. 30 | 31 | ```php 32 | get(HelloWorldIntegration::NAME)->getIntegrationConfiguration()->getApiKeys(); 77 | $username = $apiKeys['username']; 78 | ``` 79 | 80 | ##### `\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface` 81 | If the integration leverages an auth provider that requires a callback URL or something similar, this interface provides a means to return a translation string to display in the UI. For example, OAuth2 requires a redirect URI. If the admin has to configure the OAuth credentials in the 3rd party service and needs to know what URL to use in Mautic as the return URI, or callback URL, use the `getCallbackHelpMessageTranslationKey()` method. 82 | 83 | #### Feature Interfaces 84 | 85 | ##### `\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface` 86 | This interface provides the Symfony form type class that defines the fields to be displayed on the Features tab. These values are not encrypted. 87 | 88 | ```php 89 | get(HelloWorldIntegration::NAME)->getIntegrationConfiguration()->getFeatureSettings(); 91 | $doSomething = $featureSettings['doSomething']; 92 | ``` 93 | 94 | #### `\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface` 95 | Currently the integrations bundle provides default features. To use these features, implement this interface. `getSupportedFeatures` will return an array of supported features. For example, if the integration syncs with Mautic contacts, `getSupportedFeatures()` could `return [ConfigFormFeaturesInterface::FEATURE_SYNC];`. 96 | 97 | ##### Contact/Company Syncing Interfaces 98 | The integrations bundle provides a sync framework for 3rd party services to sync with Mautic's contacts and companies. The `\Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface` determines the configuration options for this sync feature. Refer to the method docblocks in the interface for more details. 99 | 100 | Read more about how to leverage the [sync framework](#integration-sync-engine). 101 | -------------------------------------------------------------------------------- /source/includes/_plugin_misc_events.md: -------------------------------------------------------------------------------- 1 | ### Events 2 | 3 | ```php 4 | array('onLeadPostSave', 0), 27 | LeadEvents::LEAD_POST_DELETE => array('onLeadDelete', 0) 28 | ); 29 | } 30 | 31 | public function onLeadPostSave(LeadEvent $event) 32 | { 33 | $lead = $event->getLead(); 34 | 35 | // do something 36 | } 37 | 38 | public function onLeadDelete(LeadEvent $event) 39 | { 40 | $lead = $event->getLead(); 41 | 42 | $deletedId = $lead->deletedId; 43 | 44 | // do something 45 | } 46 | } 47 | ``` 48 | 49 | Mautic leverages Symfony's EventDispatcher to execute and communicate various actions through Mautic. Plugin's can hook into these to extend the functionality of Mautic. Refer to [Extending Mautic](#extending-mautic) for some of the ways to do this. 50 | 51 | #### Subscribers 52 | 53 | The easiest way to listen to various events is to use an event subscriber. Read more about subscribers in [Symfony's documentation](http://symfony.com/doc/current/components/event_dispatcher/introduction.html#using-event-subscribers). 54 | 55 | Plugin event subscribers can extend `\Mautic\CoreBundle\EventListener\CommonSubscriber` which gives access to commonly used dependencies and also allows registering the subscriber service through the bundles's config file. But, it does not have to and instead See [Services](#services) for more information on registering event services. 56 | 57 | #### Available Events 58 | 59 | There are many events available throughout Mautic. Depending on the desired functionality, look at the core bundle's *Event.php file in the root of the bundle. For example, Lead related events are defined and described in `app\bundles\LeadBundle\LeadEvents.php`. These final classes provide the names of the events to listen to. Always use the event constants to ensure future changes to event names will not break the plugin. 60 | 61 |
    62 | 63 | #### Custom Events 64 | 65 | A plugin can create and dispatch it's own events. 66 | 67 | Custom events require three things: 68 | 69 | ```php 70 | 94 | 95 | ```php 96 | world = $world; 115 | } 116 | 117 | public function shouldPanic() 118 | { 119 | return ('earth' == $this->world->getName()); 120 | } 121 | 122 | public function setIsFalseAlarm() 123 | { 124 | $this->falseAlarm = true; 125 | } 126 | 127 | public function getIsFalseAlarm() 128 | { 129 | return $this->falseAlarm; 130 | } 131 | } 132 | ``` 133 | 134 | 2) The Event class that is received by the listeners. This class should extend `Symfony\Component\EventDispatcher\Event`. It will be created when the event is dispatched and should have any information listeners need to act on it. 135 | 136 |
    137 | 138 | ```php 139 | get('event_dispatcher'); 142 | if ($dispatcher->hasListeners(HelloWorldEvents::ARMAGEDDON)) { 143 | $event = $dispatcher->dispatch(HelloWorldEvents::ARMAGEDDON, new ArmageddonEvent($world)); 144 | 145 | if ($event->shouldPanic()) { 146 | throw new \Exception("Run for the hills!"); 147 | } 148 | } 149 | ``` 150 | 151 | 3) The code that dispatches the event where appropriate using the [`event_dispatcher` service](#event-dispatcher). 152 | -------------------------------------------------------------------------------- /source/includes/_api_endpoint_notes.md: -------------------------------------------------------------------------------- 1 | ## Notes 2 | Use this endpoint to obtain details on Mautic's contact notes. 3 | 4 | ```php 5 | newAuth($settings); 12 | $apiUrl = "https://your-mautic.com"; 13 | $api = new MauticApi(); 14 | $noteApi = $api->newApi("notes", $auth, $apiUrl); 15 | ``` 16 | 17 | ### Get Note 18 | ```php 19 | get($id); 23 | ``` 24 | ```json 25 | { 26 | "note":{ 27 | "id":39, 28 | "text":"Contact note created via API request", 29 | "type":"general", 30 | "dateTime":null, 31 | "lead":{ 32 | "id":1405, 33 | "points":0, 34 | "color":null, 35 | "fields":{ 36 | "core":{ 37 | "firstname":{ 38 | "id":"2", 39 | "label":"First Name", 40 | "alias":"firstname", 41 | "type":"text", 42 | "group":"core", 43 | "field_order":"42", 44 | "object":"lead", 45 | "value":"Note API test" 46 | }, 47 | "lastname":{ 48 | "id":"3", 49 | "label":"Last Name", 50 | "alias":"lastname", 51 | "type":"text", 52 | "group":"core", 53 | "field_order":"44", 54 | "object":"lead", 55 | "value":null 56 | }, 57 | [...] 58 | }, 59 | } 60 | } 61 | } 62 | } 63 | ``` 64 | Get an individual note by ID. 65 | 66 | #### HTTP Request 67 | 68 | `GET /notes/ID` 69 | 70 | #### Response 71 | 72 | `Expected Response Code: 200` 73 | 74 | See JSON code example. 75 | 76 | **Note Properties** 77 | 78 | Name|Type|Description 79 | ----|----|----------- 80 | id|int|ID of the note 81 | lead|array|data of the contact 82 | text|string|Note text 83 | type|string|Note type 84 | datetime|datetime|Date and time related to the note. 85 | 86 | 87 | ### List Contact Notes 88 | 89 | ```php 90 | getList($searchFilter, $start, $limit, $orderBy, $orderByDir, $publishedOnly, $minimal); 94 | ``` 95 | ```json 96 | { 97 | "total":24, 98 | "notes":[ 99 | { 100 | "id":1, 101 | "text":"A test note", 102 | "type":"general", 103 | "dateTime":"2016-06-14T18:07:00+00:00", 104 | "lead":{ 105 | "id":1, 106 | "points":0, 107 | "color":null, 108 | "fields":[] 109 | } 110 | }, 111 | [...] 112 | ] 113 | } 114 | ``` 115 | 116 | #### HTTP Request 117 | 118 | `GET /notes` 119 | 120 | #### Response 121 | 122 | `Expected Response Code: 200` 123 | 124 | See JSON code example. 125 | 126 | **Note Properties** 127 | 128 | Name|Type|Description 129 | ----|----|----------- 130 | id|int|ID of the note 131 | lead|array|data of the contact 132 | text|string|Note text 133 | type|string|Note type 134 | datetime|datetime|Date and time related to the note. 135 | 136 | ### Create Note 137 | ```php 138 | $contactID, 144 | 'text' => 'Note A', 145 | 'type' => 'general', 146 | ); 147 | 148 | $note = $noteApi->create($data); 149 | ``` 150 | Create a new note. 151 | 152 | #### HTTP Request 153 | 154 | `POST /notes/new` 155 | 156 | **Post Parameters** 157 | 158 | Name|Description 159 | ----|----------- 160 | text|string|Note text 161 | type|string|Note type 162 | datetime|datetime|Date and time related to the note. 163 | 164 | #### Response 165 | 166 | `Expected Response Code: 201` 167 | 168 | **Properties** 169 | 170 | Same as [Get Note](#get-note). 171 | 172 | ### Edit Note 173 | ```php 174 | 'Note B', 179 | 'type' => 'general', 180 | ); 181 | 182 | // Create new a note of ID 1 is not found? 183 | $createIfNotFound = true; 184 | 185 | $note = $noteApi->edit($id, $data, $createIfNotFound); 186 | ``` 187 | Edit a new note. Note that this supports PUT or PATCH depending on the desired behavior. 188 | 189 | **PUT** creates a note if the given ID does not exist and clears all the note information, adds the information from the request. 190 | **PATCH** fails if the note with the given ID does not exist and updates the note field values with the values form the request. 191 | 192 | #### HTTP Request 193 | 194 | To edit a note and return a 404 if the note is not found: 195 | 196 | `PATCH /notes/ID/edit` 197 | 198 | To edit a note and create a new one if the note is not found: 199 | 200 | `PUT /notes/ID/edit` 201 | 202 | **Post Parameters** 203 | 204 | Name|Description 205 | ----|----------- 206 | text|string|Note text 207 | type|string|Note type 208 | datetime|datetime|Date and time related to the note. 209 | 210 | #### Response 211 | 212 | If `PUT`, the expected response code is `200` if the note was edited or `201` if created. 213 | 214 | If `PATCH`, the expected response code is `200`. 215 | 216 | **Properties** 217 | 218 | Same as [Get Note](#get-note). 219 | 220 | ### Delete Note 221 | ```php 222 | delete($id); 225 | ``` 226 | Delete a note. 227 | 228 | #### HTTP Request 229 | 230 | `DELETE /notes/ID/delete` 231 | 232 | #### Response 233 | 234 | `Expected Response Code: 200` 235 | 236 | **Properties** 237 | 238 | Same as [Get Note](#get-note). 239 | -------------------------------------------------------------------------------- /source/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /source/includes/_introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Welcome to the Mautic Developer Documentation. This documentation will go over how to build a Mautic Plugin that extends the features of Mautic, how to build custom themes, and how to integrate applications outside of Mautic using its REST API. 4 | 5 | ## Submitting Code to Mautic 6 | 7 | Development is open and available to any member of the Mautic community. All fixes and improvements are done through pull requests to the code on [GitHub](https://github.com/mautic/mautic). This code is open source and publicly available. 8 | 9 | Read all about [contributing to Mautic](https://contribute.mautic.org/contributing-to-mautic/developer) as a Developer. 10 | 11 | Read more about the PR process on the Mautic [Governance](https://www.mautic.org/about/governance) page. 12 | 13 | The code should try to follow [Symfony's Coding Standards](http://symfony.com/doc/current/contributing/code/standards.html) 14 | 15 | ## Symfony 16 | 17 | Mautic is built on [Symfony](http://symfony.com), the popular PHP framework. This document will go over the basics but most of [their documentation](https://symfony.com/doc/4.4/index.html) applies to Mautic as well which can be used to obtain more advanced Symfony functionality. 18 | 19 | There are some structural differences between Mautic and standard Symfony. Below is a list of where you will find some of standard Symfony locations in Mautic: 20 | 21 | Symfony | Mautic 22 | ------- | ------- 23 | src/ | app/bundles (Mautic core) or plugins/ (Mautic plugins) 24 | web/ | / 25 | AcmeBundle/Resources/config | AcmeBundle/Config 26 | AcmeBundle/Resources/views | AcmeBundle/Views 27 | AcmeBundle/Resources/public | AcmeBundle/Assets 28 | AcmeBundle/Resources/translations/domain.en_US.ini | AcmeBundle/Translations/en_US/domain.ini 29 | 30 | Most of Symfony's standard locations, such as the Resources/views and Resources/translations directories, should still function with Mautic. However, it may be required to handle service registration, etc with native Symfony processes if not using the Mautic methods defined in this document. 31 | 32 | ## Development Environment 33 | 34 | ### Setup 35 | It is assumed that the system already has [composer](https://getcomposer.org) and [git](https://git-scm.com) installed and configured. 36 | 37 | To setup the developer environment, simply fork and clone the source from [GitHub](https://github.com/mautic/mautic). Then Run `composer install` on the source. 38 | 39 | Open your browser and complete the installation through the Mautic installer. 40 | You can also execute the install process from command line: 41 | * Add a `local.php` file in app/config 42 | * Edit the `local.php` file using the following template (adapt to your own settings): 43 | ```php 44 | 'pdo_mysql', 47 | 'db_host' => 'localhost', 48 | 'db_table_prefix' => null, 49 | 'db_port' => '3306', 50 | 'db_name' => 'mautic', 51 | 'db_user' => 'root', 52 | 'db_password' => 'root_password', 53 | 'db_backup_tables' => true, 54 | 'db_backup_prefix' => 'bak_', 55 | ); 56 | ``` 57 | * Execute the following command and add your own options: `php bin/console mautic:install http://your.mautic.instance` 58 | 59 | ### Environments 60 | 61 | There are three environments in Mautic: prod, dev, and test. 62 | 63 | **prod** is used when accessing the site through index.php. 64 | 65 | **dev** is used when accessing the site through index_dev.php. Using Mautic in the dev environment will activate Symfony's profiler toolbar, has more strict error handling, will display information about exceptions, and will not cache as much (see below). Note that steps should be taken to ensure index_dev.php is not accessible to the public as it could potentially reveal sensitive information. It is restricted to localhost by default. However, there are two ways to allow access to index_dev.php from a non-localhost. The first option is to set a header from the web-server with the IP addresses assigned to `MAUTIC_DEV_HOSTS`. The second and easier option is to add an array to your installation's `app/config/local.php` file as `'dev_hosts' = ['123.123.123.123', '124.124.124.124'],` then clear the [cache](#cache). **Note**: If you're using PHP-FPM (e.g. when using Docker/DDEV), you need to have `cgi.fix_pathinfo = 1` in your PHP configuration, otherwise `/index_dev.php/*` might not work. Read more about the implications of this setting [here](https://serverfault.com/questions/627903/is-the-php-option-cgi-fix-pathinfo-really-dangerous-with-nginx-php-fpm). 66 | 67 | **test** is used mainly for PHP Unit Tests. 68 | 69 | ### Cache 70 | 71 | Symfony makes heavy use of a filesystem cache. Frequently clearing this cache will be required when developing for Mautic. By default, the cache is located in app/cache/ENV where ENV is the environment currently accessed (i.e. dev or prod). To rebuild the cache, the ENV can just be deleted or run the Symfony command `php app/console cache:clear --env=ENV` If a specific environment is not passed to the command via `--env=ENV`, the dev environment will be used by default. 72 | 73 | In the dev environment, translations, views, and assets are not cached. However, changes to these files will require the cache to be cleared for them to take effect in the prod environment. Changes to Mautic config files, Symfony config files, etc will require the cache to be cleared regardless of environment. 74 | 75 | 78 | 79 | 82 | 83 | -------------------------------------------------------------------------------- /source/includes/_plugin_install.md: -------------------------------------------------------------------------------- 1 | ## Install/Upgrade 2 | 3 | **THIS IS NOW DEPRECATED AND DISCOURAGED FROM USE. USE THE [INTEGRATION FRAMEWORK'S MIGRATION](#plugin-schema) INSTEAD.** 4 | 5 | ```php 6 | getDatabase(); 49 | $platform = $db->getDatabasePlatform()->getName(); 50 | $queries = array(); 51 | $fromVersion = $plugin->getVersion(); 52 | 53 | // Simple example 54 | switch ($fromVersion) { 55 | case '1.0': 56 | switch ($platform) { 57 | case 'mysql': 58 | $queries[] = 'ALTER TABLE ' . MAUTIC_TABLE_PREFIX . 'worlds CHANGE description LONGTEXT DEFAULT NULL'; 59 | break; 60 | 61 | case 'postgresql': 62 | $queries[] = 'ALTER TABLE ' . MAUTIC_TABLE_PREFIX . 'worlds ALTER description ALTER TYPE TEXT'; 63 | break; 64 | } 65 | 66 | break; 67 | } 68 | 69 | if (!empty($queries)) { 70 | 71 | $db->beginTransaction(); 72 | try { 73 | foreach ($queries as $q) { 74 | $db->query($q); 75 | } 76 | 77 | $db->commit(); 78 | } catch (\Exception $e) { 79 | $db->rollback(); 80 | 81 | throw $e; 82 | } 83 | } 84 | } 85 | } 86 | ``` 87 | 88 | Currently, plugins are installed by uploading the plugin to the plugins folder and the cache manually deleted (`app/cache/prod`). Then, the admin must click the Install/Upgrade Plugins button in the Plugin manager. When that happens, new plugins found will have the static method `onPluginInstall()` called from the [plugin's bundle file](#plugin-directory-structure). If the plugin has already been installed, and the version has changed, then `onPluginUpdate()` is called. 89 | 90 | By default, `onPluginInstall()` will execute `installPluginSchema()`. This function will generate and execute schema based on found [Entity](#Database) classes. 91 | 92 | `updatePluginSchema()` is available for use with `onPluginUpdate()`, however it is not called by default. The reason is that it uses Doctrine to generate schema differences and executes the queries. This is not recommended due to the risk of Doctrine making incorrect assumptions that will lead to data loss. If `updatePluginSchema()` is relied upon, it is very important to test thoroughly to ensure the desired outcome. It is recommended to write a migration path for both MySQL and PostgreSQL with native queries instead. 93 | 94 | ### onPluginInstall() 95 | 96 | Executed when a plugin is first installed. By default, the plugin's database schema will be generated and installed based on the Entity class' `loadMetadata` function. 97 | 98 | Argument|Type|Description 99 | --------|----|----------- 100 | $plugin|Mautic\PluginBundle\Entity\Plugin|The plugin entity with information gleaned from the plugin's config file. 101 | $factory|Mautic\CoreBundle\Factory\MauticFactory|Mautic's factory class to provide access to Mautic's services. 102 | $metadata|array\|null|Array of found Entity classes to be used by Doctrine's SchemaTool to generate installation schema. 103 | 104 | ### installPluginSchema() 105 | 106 | Argument|Type|Description 107 | --------|----|----------- 108 | $metadata|array\|null|Array of found Entity classes to be used by Doctrine's SchemaTool to generate installation schema. 109 | $factory|Mautic\CoreBundle\Factory\MauticFactory|Mautic's factory class to provide access to Mautic's services. 110 | 111 | ### onPluginUpdate() 112 | 113 | Argument|Type|Description 114 | --------|----|----------- 115 | $plugin|Mautic\PluginBundle\Entity\Plugin|The plugin entity with information gleaned from the plugin's config file. 116 | $factory|Mautic\CoreBundle\Factory\MauticFactory|Mautic's factory class to provide access to Mautic's services. 117 | $metadata|array\|null|Array of found Entity classes to be used by Doctrine's SchemaTool to generate update schema. 118 | $installedSchema|\Doctrine\DBAL\Schema\Schema|Schema of currently installed tables 119 | 120 | ### updatePluginSchema() 121 | 122 | Argument|Type|Description 123 | --------|----|----------- 124 | $metadata|array\|null|Array of found Entity classes to be used by Doctrine's SchemaTool to generate update schema. 125 | $installedSchema|\Doctrine\DBAL\Schema\Schema|Schema of currently installed tables 126 | $factory|Mautic\CoreBundle\Factory\MauticFactory|Mautic's factory class to provide access to Mautic's services. 127 | -------------------------------------------------------------------------------- /source/javascripts/lib/energize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * energize.js v0.1.0 3 | * 4 | * Speeds up click events on mobile devices. 5 | * https://github.com/davidcalhoun/energize.js 6 | */ 7 | 8 | (function() { // Sandbox 9 | /** 10 | * Don't add to non-touch devices, which don't need to be sped up 11 | */ 12 | if(!('ontouchstart' in window)) return; 13 | 14 | var lastClick = {}, 15 | isThresholdReached, touchstart, touchmove, touchend, 16 | click, closest; 17 | 18 | /** 19 | * isThresholdReached 20 | * 21 | * Compare touchstart with touchend xy coordinates, 22 | * and only fire simulated click event if the coordinates 23 | * are nearby. (don't want clicking to be confused with a swipe) 24 | */ 25 | isThresholdReached = function(startXY, xy) { 26 | return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5; 27 | }; 28 | 29 | /** 30 | * touchstart 31 | * 32 | * Save xy coordinates when the user starts touching the screen 33 | */ 34 | touchstart = function(e) { 35 | this.startXY = [e.touches[0].clientX, e.touches[0].clientY]; 36 | this.threshold = false; 37 | }; 38 | 39 | /** 40 | * touchmove 41 | * 42 | * Check if the user is scrolling past the threshold. 43 | * Have to check here because touchend will not always fire 44 | * on some tested devices (Kindle Fire?) 45 | */ 46 | touchmove = function(e) { 47 | // NOOP if the threshold has already been reached 48 | if(this.threshold) return false; 49 | 50 | this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]); 51 | }; 52 | 53 | /** 54 | * touchend 55 | * 56 | * If the user didn't scroll past the threshold between 57 | * touchstart and touchend, fire a simulated click. 58 | * 59 | * (This will fire before a native click) 60 | */ 61 | touchend = function(e) { 62 | // Don't fire a click if the user scrolled past the threshold 63 | if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) { 64 | return; 65 | } 66 | 67 | /** 68 | * Create and fire a click event on the target element 69 | * https://developer.mozilla.org/en/DOM/event.initMouseEvent 70 | */ 71 | var touch = e.changedTouches[0], 72 | evt = document.createEvent('MouseEvents'); 73 | evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); 74 | evt.simulated = true; // distinguish from a normal (nonsimulated) click 75 | e.target.dispatchEvent(evt); 76 | }; 77 | 78 | /** 79 | * click 80 | * 81 | * Because we've already fired a click event in touchend, 82 | * we need to listed for all native click events here 83 | * and suppress them as necessary. 84 | */ 85 | click = function(e) { 86 | /** 87 | * Prevent ghost clicks by only allowing clicks we created 88 | * in the click event we fired (look for e.simulated) 89 | */ 90 | var time = Date.now(), 91 | timeDiff = time - lastClick.time, 92 | x = e.clientX, 93 | y = e.clientY, 94 | xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)], 95 | target = closest(e.target, 'A') || e.target, // needed for standalone apps 96 | nodeName = target.nodeName, 97 | isLink = nodeName === 'A', 98 | standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href"); 99 | 100 | lastClick.time = time; 101 | lastClick.x = x; 102 | lastClick.y = y; 103 | 104 | /** 105 | * Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire), 106 | * so we have to add more logic to determine the time of the last click. Not perfect... 107 | * 108 | * Older, simpler check: if((!e.simulated) || standAlone) 109 | */ 110 | if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) { 111 | e.preventDefault(); 112 | e.stopPropagation(); 113 | if(!standAlone) return false; 114 | } 115 | 116 | /** 117 | * Special logic for standalone web apps 118 | * See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window 119 | */ 120 | if(standAlone) { 121 | window.location = target.getAttribute("href"); 122 | } 123 | 124 | /** 125 | * Add an energize-focus class to the targeted link (mimics :focus behavior) 126 | * TODO: test and/or remove? Does this work? 127 | */ 128 | if(!target || !target.classList) return; 129 | target.classList.add("energize-focus"); 130 | window.setTimeout(function(){ 131 | target.classList.remove("energize-focus"); 132 | }, 150); 133 | }; 134 | 135 | /** 136 | * closest 137 | * @param {HTMLElement} node current node to start searching from. 138 | * @param {string} tagName the (uppercase) name of the tag you're looking for. 139 | * 140 | * Find the closest ancestor tag of a given node. 141 | * 142 | * Starts at node and goes up the DOM tree looking for a 143 | * matching nodeName, continuing until hitting document.body 144 | */ 145 | closest = function(node, tagName){ 146 | var curNode = node; 147 | 148 | while(curNode !== document.body) { // go up the dom until we find the tag we're after 149 | if(!curNode || curNode.nodeName === tagName) { return curNode; } // found 150 | curNode = curNode.parentNode; // not found, so keep going up 151 | } 152 | 153 | return null; // not found 154 | }; 155 | 156 | /** 157 | * Add all delegated event listeners 158 | * 159 | * All the events we care about bubble up to document, 160 | * so we can take advantage of event delegation. 161 | * 162 | * Note: no need to wait for DOMContentLoaded here 163 | */ 164 | document.addEventListener('touchstart', touchstart, false); 165 | document.addEventListener('touchmove', touchmove, false); 166 | document.addEventListener('touchend', touchend, false); 167 | document.addEventListener('click', click, true); // TODO: why does this use capture? 168 | 169 | })(); -------------------------------------------------------------------------------- /source/javascripts/lib/jquery_ui.js: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.10.3 - 2013-09-16 2 | * http://jqueryui.com 3 | * Includes: jquery.ui.widget.js 4 | * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ 5 | 6 | (function(e,t){var i=0,s=Array.prototype.slice,a=e.cleanData;e.cleanData=function(t){for(var i,s=0;null!=(i=t[s]);s++)try{e(i).triggerHandler("remove")}catch(n){}a(t)},e.widget=function(i,s,a){var n,r,o,h,l={},u=i.split(".")[0];i=i.split(".")[1],n=u+"-"+i,a||(a=s,s=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[u]=e[u]||{},r=e[u][i],o=e[u][i]=function(e,i){return this._createWidget?(arguments.length&&this._createWidget(e,i),t):new o(e,i)},e.extend(o,r,{version:a.version,_proto:e.extend({},a),_childConstructors:[]}),h=new s,h.options=e.widget.extend({},h.options),e.each(a,function(i,a){return e.isFunction(a)?(l[i]=function(){var e=function(){return s.prototype[i].apply(this,arguments)},t=function(e){return s.prototype[i].apply(this,e)};return function(){var i,s=this._super,n=this._superApply;return this._super=e,this._superApply=t,i=a.apply(this,arguments),this._super=s,this._superApply=n,i}}(),t):(l[i]=a,t)}),o.prototype=e.widget.extend(h,{widgetEventPrefix:r?h.widgetEventPrefix:i},l,{constructor:o,namespace:u,widgetName:i,widgetFullName:n}),r?(e.each(r._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete r._childConstructors):s._childConstructors.push(o),e.widget.bridge(i,o)},e.widget.extend=function(i){for(var a,n,r=s.call(arguments,1),o=0,h=r.length;h>o;o++)for(a in r[o])n=r[o][a],r[o].hasOwnProperty(a)&&n!==t&&(i[a]=e.isPlainObject(n)?e.isPlainObject(i[a])?e.widget.extend({},i[a],n):e.widget.extend({},n):n);return i},e.widget.bridge=function(i,a){var n=a.prototype.widgetFullName||i;e.fn[i]=function(r){var o="string"==typeof r,h=s.call(arguments,1),l=this;return r=!o&&h.length?e.widget.extend.apply(null,[r].concat(h)):r,o?this.each(function(){var s,a=e.data(this,n);return a?e.isFunction(a[r])&&"_"!==r.charAt(0)?(s=a[r].apply(a,h),s!==a&&s!==t?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):t):e.error("no such method '"+r+"' for "+i+" widget instance"):e.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+r+"'")}):this.each(function(){var t=e.data(this,n);t?t.option(r||{})._init():e.data(this,n,new a(r,this))}),l}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{disabled:!1,create:null},_createWidget:function(t,s){s=e(s||this.defaultElement||this)[0],this.element=e(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),s!==this&&(e.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===s&&this.destroy()}}),this.document=e(s.style?s.ownerDocument:s.document||s),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(i,s){var a,n,r,o=i;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof i)if(o={},a=i.split("."),i=a.shift(),a.length){for(n=o[i]=e.widget.extend({},this.options[i]),r=0;a.length-1>r;r++)n[a[r]]=n[a[r]]||{},n=n[a[r]];if(i=a.pop(),s===t)return n[i]===t?null:n[i];n[i]=s}else{if(s===t)return this.options[i]===t?null:this.options[i];o[i]=s}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,a){var n,r=this;"boolean"!=typeof i&&(a=s,s=i,i=!1),a?(s=n=e(s),this.bindings=this.bindings.add(s)):(a=s,s=this.element,n=this.widget()),e.each(a,function(a,o){function h(){return i||r.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?r[o]:o).apply(r,arguments):t}"string"!=typeof o&&(h.guid=o.guid=o.guid||h.guid||e.guid++);var l=a.match(/^(\w+)\s*(.*)$/),u=l[1]+r.eventNamespace,c=l[2];c?n.delegate(c,u,h):s.bind(u,h)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var a,n,r=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],n=i.originalEvent)for(a in n)a in i||(i[a]=n[a]);return this.element.trigger(i,s),!(e.isFunction(r)&&r.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,a,n){"string"==typeof a&&(a={effect:a});var r,o=a?a===!0||"number"==typeof a?i:a.effect||i:t;a=a||{},"number"==typeof a&&(a={duration:a}),r=!e.isEmptyObject(a),a.complete=n,a.delay&&s.delay(a.delay),r&&e.effects&&e.effects.effect[o]?s[t](a):o!==t&&s[o]?s[o](a.duration,a.easing,n):s.queue(function(i){e(this)[t](),n&&n.call(s[0]),i()})}})})(jQuery); -------------------------------------------------------------------------------- /source/includes/_api_endpoint_categories.md: -------------------------------------------------------------------------------- 1 | ## Categories 2 | Use this endpoint to obtain details on Mautic's categories or to manipulate category memberships. 3 | 4 | ```php 5 | newAuth($settings); 12 | $apiUrl = "https://your-mautic.com"; 13 | $api = new MauticApi(); 14 | $categoryApi = $api->newApi("categories", $auth, $apiUrl); 15 | ``` 16 | 17 | ### Get Category 18 | ```php 19 | get($id); 23 | ``` 24 | ```json 25 | { 26 | "category":{ 27 | "id":221, 28 | "title":"test", 29 | "alias":"test4", 30 | "description":null, 31 | "color":null, 32 | "bundle":"asset" 33 | } 34 | } 35 | ``` 36 | Get an individual category by ID. 37 | 38 | #### HTTP Request 39 | 40 | `GET /categories/ID` 41 | 42 | #### Response 43 | 44 | `Expected Response Code: 200` 45 | 46 | See JSON code example. 47 | 48 | **Category Properties** 49 | 50 | Name|Type|Description 51 | ----|----|----------- 52 | id|int|ID of the category 53 | isPublished|boolean|Whether the category is published 54 | dateAdded|datetime|Date/time category was created 55 | createdBy|int|ID of the user that created the category 56 | createdByUser|string|Name of the user that created the category 57 | dateModified|datetime/null|Date/time category was last modified 58 | modifiedBy|int|ID of the user that last modified the category 59 | modifiedByUser|string|Name of the user that last modified the category 60 | title|string|The category title 61 | alias|string|The category alias 62 | description|string|The category description 63 | color|string|The category color 64 | bundle|string|The bundle where the category will be available 65 | 66 | ### List Contact Categories 67 | 68 | ```php 69 | getList($searchFilter, $start, $limit, $orderBy, $orderByDir, $publishedOnly, $minimal); 73 | ``` 74 | ```json 75 | { 76 | "total":8, 77 | "categories":[ 78 | { 79 | "id":1, 80 | "title":"Bold", 81 | "alias":"bold", 82 | "description":null, 83 | "color":"b36262", 84 | "bundle":"point" 85 | }, 86 | [...] 87 | ] 88 | } 89 | ``` 90 | Returns a list of contact categories available to the user. This list is not filterable. 91 | 92 | #### HTTP Request 93 | 94 | `GET /categories` 95 | 96 | #### Response 97 | 98 | `Expected Response Code: 200` 99 | 100 | See JSON code example. 101 | 102 | **Category Properties** 103 | 104 | Name|Type|Description 105 | ----|----|----------- 106 | id|int|ID of the category 107 | isPublished|boolean|Whether the category is published 108 | dateAdded|datetime|Date/time category was created 109 | createdBy|int|ID of the user that created the category 110 | createdByUser|string|Name of the user that created the category 111 | dateModified|datetime/null|Date/time category was last modified 112 | modifiedBy|int|ID of the user that last modified the category 113 | modifiedByUser|string|Name of the user that last modified the category 114 | title|string|The category title 115 | alias|string|The category alias 116 | description|string|The category description 117 | color|string|The category color 118 | bundle|string|The bundle where the category will be available 119 | 120 | ### Create Category 121 | ```php 122 | 'test', 126 | 'categoryemail' => 'test@category.com', 127 | 'categorycity' => 'Raleigh', 128 | ); 129 | 130 | $category = $categoryApi->create($data); 131 | ``` 132 | Create a new category. 133 | 134 | #### HTTP Request 135 | 136 | `POST /categories/new` 137 | 138 | **Post Parameters** 139 | 140 | Name|Description 141 | ----|----------- 142 | title|string|The category title 143 | bundle|string|The bundle where the category will be available 144 | 145 | #### Response 146 | 147 | `Expected Response Code: 201` 148 | 149 | **Properties** 150 | 151 | Same as [Get Category](#get-category). 152 | 153 | ### Edit Category 154 | ```php 155 | 'test', 160 | 'bundle' => 'asset' 161 | ); 162 | 163 | // Create new a category of ID 1 is not found? 164 | $createIfNotFound = true; 165 | 166 | $category = $categoryApi->edit($id, $data, $createIfNotFound); 167 | ``` 168 | Edit a new category. Note that this supports PUT or PATCH depending on the desired behavior. 169 | 170 | **PUT** creates a category if the given ID does not exist and clears all the category information, adds the information from the request. 171 | **PATCH** fails if the category with the given ID does not exist and updates the category field values with the values form the request. 172 | 173 | #### HTTP Request 174 | 175 | To edit a category and return a 404 if the category is not found: 176 | 177 | `PATCH /categories/ID/edit` 178 | 179 | To edit a category and create a new one if the category is not found: 180 | 181 | `PUT /categories/ID/edit` 182 | 183 | **Post Parameters** 184 | 185 | Name|Description 186 | ----|----------- 187 | title|string|The category title 188 | bundle|string|The bundle where the category will be available 189 | 190 | #### Response 191 | 192 | If `PUT`, the expected response code is `200` if the category was edited or `201` if created. 193 | 194 | If `PATCH`, the expected response code is `200`. 195 | 196 | **Properties** 197 | 198 | Same as [Get Category](#get-category). 199 | 200 | ### Delete Category 201 | ```php 202 | delete($id); 205 | ``` 206 | Delete a category. 207 | 208 | #### HTTP Request 209 | 210 | `DELETE /categories/ID/delete` 211 | 212 | #### Response 213 | 214 | `Expected Response Code: 200` 215 | 216 | **Properties** 217 | 218 | Same as [Get Category](#get-category). 219 | 220 | ### Assign a Category 221 | 222 | To assign a category to an entity simply set `category = [ID]` to the payload. For example this is how a category 123 can be asssigned to a new Asset: 223 | 224 | ```php 225 | $data = array( 226 | 'title' => 'PDF sent as a API request', 227 | 'storageLocation' => 'remote', 228 | 'file' => 'https://www.mautic.org/media/logos/logo/Mautic_Logo_DB.pdf' 229 | 'category' => 123 230 | ); 231 | 232 | $asset = $assetApi->create($data); 233 | ``` 234 | 235 | The category must exist in the Mautic instance and the entity must support categories, 236 | -------------------------------------------------------------------------------- /source/includes/_api_endpoint_roles.md: -------------------------------------------------------------------------------- 1 | ## Roles 2 | Use this endpoint to obtain details on Mautic's roles (administrators). 3 | 4 | ```php 5 | newAuth($settings); 12 | $apiUrl = "https://your-mautic.com"; 13 | $api = new MauticApi(); 14 | $roleApi = $api->newApi("roles", $auth, $apiUrl); 15 | ``` 16 | 17 | ### Get Role 18 | ```php 19 | get($id); 23 | ``` 24 | ```json 25 | { 26 | "role":{ 27 | "isPublished":true, 28 | "dateAdded":"2016-11-09T15:24:32+00:00", 29 | "createdBy":1, 30 | "createdByUser":"John Doe", 31 | "dateModified":null, 32 | "modifiedBy":null, 33 | "modifiedByUser":null, 34 | "id":13, 35 | "name":"API test role", 36 | "description":"created via AIP", 37 | "isAdmin":false, 38 | "rawPermissions":{ 39 | "email:emails":[ 40 | "viewown", 41 | "viewother" 42 | ] 43 | } 44 | } 45 | } 46 | ``` 47 | Get an individual role by ID. 48 | 49 | #### HTTP Request 50 | 51 | `GET /roles/ID` 52 | 53 | #### Response 54 | 55 | `Expected Response Code: 200` 56 | 57 | See JSON code example. 58 | 59 | **Role Properties** 60 | 61 | Name|Type|Description 62 | ----|----|----------- 63 | id|int|ID of the contact 64 | dateAdded|datetime|Date/time contact was created 65 | createdBy|int|ID of the role that created the contact 66 | createdByRole|string|Name of the role that created the contact 67 | dateModified|datetime/null|Date/time contact was last modified 68 | modifiedBy|int|ID of the role that last modified the contact 69 | modifiedByRole|string|Name of the role that last modified the contact 70 | name|string|Name of the role 71 | description|string|Description of the role 72 | isAdmin|boolean|Whether the role has full access or only some 73 | rawPermissions|array|List of roles 74 | 75 | ### List Contact Roles 76 | 77 | ```php 78 | getList($searchFilter, $start, $limit, $orderBy, $orderByDir, $publishedOnly, $minimal); 82 | ``` 83 | ```json 84 | { 85 | "total":9, 86 | "roles":[ 87 | { 88 | "isPublished":true, 89 | "dateAdded":"2016-08-01T11:51:32+00:00", 90 | "createdBy":1, 91 | "createdByUser":"John Doe", 92 | "dateModified":null, 93 | "modifiedBy":null, 94 | "modifiedByUser":null, 95 | "id":2, 96 | "name":"view email", 97 | "description":null, 98 | "isAdmin":false, 99 | "rawPermissions":{ 100 | "email:emails":[ 101 | "viewown", 102 | "viewother" 103 | ] 104 | } 105 | }, 106 | [...] 107 | ] 108 | } 109 | ``` 110 | 111 | #### HTTP Request 112 | 113 | `GET /roles` 114 | 115 | #### Response 116 | 117 | `Expected Response Code: 200` 118 | 119 | See JSON code example. 120 | 121 | **Role Properties** 122 | 123 | Name|Type|Description 124 | ----|----|----------- 125 | id|int|ID of the contact 126 | dateAdded|datetime|Date/time contact was created 127 | createdBy|int|ID of the role that created the contact 128 | createdByRole|string|Name of the role that created the contact 129 | dateModified|datetime/null|Date/time contact was last modified 130 | modifiedBy|int|ID of the role that last modified the contact 131 | modifiedByRole|string|Name of the role that last modified the contact 132 | name|string|Name of the role 133 | description|string|Description of the role 134 | isAdmin|boolean|Whether the role has full access or only some 135 | rawPermissions|array|List of roles 136 | 137 | ### Create Role 138 | ```php 139 | 'API test role', 143 | 'description' => 'created via AIP', 144 | 'rawPermissions' => array ( 145 | 'email:emails' => 146 | array ( 147 | 'viewown', 148 | 'viewother', 149 | ), 150 | ) 151 | ); 152 | 153 | $role = $roleApi->create($data); 154 | ``` 155 | Create a new role. 156 | 157 | #### HTTP Request 158 | 159 | `POST /roles/new` 160 | 161 | **Post Parameters** 162 | 163 | Name|Description 164 | ----|----------- 165 | name|string|Name of the role 166 | description|string|Description of the role 167 | isAdmin|boolean|Whether the role has full access or only some 168 | rawPermissions|array|List of roles 169 | 170 | #### Response 171 | 172 | `Expected Response Code: 201` 173 | 174 | **Properties** 175 | 176 | Same as [Get Role](#get-role). 177 | 178 | ### Edit Role 179 | ```php 180 | 'API test role', 185 | 'description' => 'created via AIP', 186 | 'rawPermissions' => array ( 187 | 'email:emails' => 188 | array ( 189 | 'editown', 190 | 'editother', 191 | ), 192 | ) 193 | ); 194 | 195 | // Create new a role of ID 1 is not found? 196 | $createIfNotFound = true; 197 | 198 | $role = $roleApi->edit($id, $data, $createIfNotFound); 199 | ``` 200 | Edit a new role. Role that this supports PUT or PATCH depending on the desired behavior. 201 | 202 | **PUT** creates a role if the given ID does not exist and clears all the role information, adds the information from the request. 203 | **PATCH** fails if the role with the given ID does not exist and updates the role field values with the values form the request. 204 | 205 | #### HTTP Request 206 | 207 | To edit a role and return a 404 if the role is not found: 208 | 209 | `PATCH /roles/ID/edit` 210 | 211 | To edit a role and create a new one if the role is not found: 212 | 213 | `PUT /roles/ID/edit` 214 | 215 | **Post Parameters** 216 | 217 | Name|Description 218 | ----|----------- 219 | name|string|Name of the role 220 | description|string|Description of the role 221 | isAdmin|boolean|Whether the role has full access or only some 222 | rawPermissions|array|List of roles 223 | 224 | #### Response 225 | 226 | If `PUT`, the expected response code is `200` if the role was edited or `201` if created. 227 | 228 | If `PATCH`, the expected response code is `200`. 229 | 230 | **Properties** 231 | 232 | Same as [Get Role](#get-role). 233 | 234 | ### Delete Role 235 | ```php 236 | delete($id); 239 | ``` 240 | Delete a role. 241 | 242 | #### HTTP Request 243 | 244 | `DELETE /roles/ID/delete` 245 | 246 | #### Response 247 | 248 | `Expected Response Code: 200` 249 | 250 | **Properties** 251 | 252 | Same as [Get Role](#get-role). 253 | -------------------------------------------------------------------------------- /source/includes/_plugin_misc_forms.md: -------------------------------------------------------------------------------- 1 | ### Forms 2 | 3 | Mautic leverages Symfony's Form component and form classes. Refer to [Symfony's documentation](http://symfony.com/doc/current/book/forms.html#creating-form-classes) for more information. 4 | 5 | #### Form Types 6 | 7 | As stated in Symfony's documentation referenced above, form type classes are the best way to go. Mautic makes it easy to register [form type services](http://symfony.com/doc/2.8/cookbook/form/create_custom_field_type.html#defining-the-field-type) through the bundle's config file. Refer to the [Services](#services) section. 8 | 9 | #### Data Sanitization 10 | 11 | ```php 12 | addEventSubscriber( 22 | new CleanFormSubscriber( 23 | [ 24 | 'content' => 'html', 25 | 'customHtml' => 'html' 26 | ] 27 | ) 28 | ); 29 | 30 | // ... 31 | } 32 | // ... 33 | ``` 34 | Form data is not automatically sanitized. Mautic provides a form event subscriber to handle this. 35 | 36 | In your [form type class](http://symfony.com/doc/2.8/cookbook/form/create_custom_field_type.html#defining-the-field-type), register the `Mautic\CoreBundle\Form\EventListener\CleanFormSubscriber` event subscriber. 37 | 38 | The array provided to `CleanFormSubscriber` should contain the names of the form fields as keys and the values the masks to use to sanitize the data. Any un-specified form field will use the `clean` mask by default. 39 | 40 | #### Manipulating Forms 41 | 42 | A form event listener must be used if a form needs to be manipulated based on submitted data such as changing defined fields, adjust constraints, or changing select choices based on submitted values. Refer to [Symfony's documentation](http://symfony.com/doc/2.8/cookbook/form/dynamic_form_modification.html) on this. 43 | 44 | #### Validating Data 45 | 46 | Review [Symfony's form validation documentation](http://symfony.com/doc/2.8/book/forms.html#form-validation) for a general overview. 47 | 48 | There are two common means of validating form data. 49 | 50 | ##### Using Entity Static Callback 51 | ```php 52 | addPropertyConstraint( 67 | 'name', 68 | new NotBlank( 69 | array( 70 | 'message' => 'mautic.core.name.required' 71 | ) 72 | ) 73 | ); 74 | 75 | $metadata->addPropertyConstraint( 76 | 'population', 77 | new NotBlank( 78 | array( 79 | 'message' => 'mautic.core.value.required', 80 | 'groups' => array('VisitedWorld') 81 | ) 82 | 83 | ) 84 | ); 85 | } 86 | 87 | /** 88 | * @param Form $form 89 | * 90 | * @return array 91 | */ 92 | public static function determineValidationGroups (Form $form) 93 | { 94 | $data = $form->getData(); 95 | $groups = array('AllWorlds'); 96 | 97 | if (!$data->getId() || ($data->getId() && $data->getVisitCount() > 0)) { 98 | $groups[] = 'VisitedWorld'; 99 | } 100 | 101 | return $groups; 102 | } 103 | // ... 104 | ``` 105 | 106 | If the underlying data of a form is an Entity object, a static method `loadValidatorMetadata` can be defined in the Entity class. This will automatically be called when Symfony is processing form data. 107 | 108 | A form can also use [`validation_groups`](http://symfony.com/doc/2.8/book/forms.html#validation-groups) to change the order of data to be validated or only validate if certain criteria is true. For example, only validate a password confirmation field if the first password field passes validation. When registering a validation group in the form type class, one can use static callback that can be used to determine what validation group(s) should be used. 109 | 110 | ```php 111 | setDefaults(array( 121 | 'data_class' => 'MauticPlugin\HelloWorld\Entity\World', 122 | 'validation_groups' => array( 123 | 'MauticPlugin\HelloWorld\Entity\World', 124 | 'determineValidationGroups', 125 | ) 126 | )); 127 | } 128 | // ... 129 | ``` 130 | 131 |
    132 | 133 | ##### Using Constraints 134 | 135 | A [form type service](http://symfony.com/doc/2.8/cookbook/form/create_custom_field_type.html#defining-the-field-type) can also register [constraints](http://symfony.com/doc/2.8/reference/constraints.html) when defining the form fields. 136 | 137 | ```php 138 | add( 150 | 'name', 151 | 'text', 152 | array( 153 | 'label' => 'mautic.core.name', 154 | 'label_attr' => array('class' => 'control-label'), 155 | 'attr' => array( 156 | 'class' => 'form-control' 157 | ), 158 | 'constraints' => array( 159 | new NotBlank( 160 | array( 161 | 'message' => 'mautic.core.value.required' 162 | ) 163 | ) 164 | ) 165 | ) 166 | ); 167 | } 168 | 169 | // ... 170 | ``` -------------------------------------------------------------------------------- /source/includes/_plugin_configuration.md: -------------------------------------------------------------------------------- 1 | ## Custom Config Params 2 | 3 | Mautic's configuration is stored in app/config/local.php. Plugin's can leverage custom config parameters to use within it's code. 4 | 5 | Each configuration option desired should be defined and have a default set in the [plugin's config file](#parameters). This prevents Symfony from throwing errors if the parameter is used during cache compilation or if accessed directly from the container without checking if it exists first. Defining the parameters in the plugin's config file will ensure that it always exists. 6 | 7 | To add config options to the Configuration page, an [event subscriber](#subscribers), a config [form type](#forms), and a [specific view](#views) are required. 8 | 9 | 12 | 13 | ### Config Event Subscriber 14 | ```php 15 | array('onConfigGenerate', 0), 38 | ConfigEvents::CONFIG_PRE_SAVE => array('onConfigSave', 0) 39 | ); 40 | } 41 | 42 | /** 43 | * @param ConfigBuilderEvent $event 44 | */ 45 | public function onConfigGenerate(ConfigBuilderEvent $event) 46 | { 47 | $event->addForm( 48 | array( 49 | 'formAlias' => 'helloworld_config', 50 | 'formTheme' => 'HelloWorldBundle:FormTheme\Config', 51 | 'parameters' => $event->getParametersFromConfig('HelloWorldBundle') 52 | ) 53 | ); 54 | } 55 | 56 | /** 57 | * @param ConfigEvent $event 58 | */ 59 | public function onConfigSave(ConfigEvent $event) 60 | { 61 | /** @var array $values */ 62 | $values = $event->getConfig(); 63 | 64 | // Manipulate the values 65 | if (!empty($values['helloworld_config']['custom_config_option'])) { 66 | $values['helloworld_config']['custom_config_option'] = htmlspecialchars($values['helloworld_config']['custom_config_option']); 67 | } 68 | 69 | // Set updated values 70 | $event->setConfig($values); 71 | } 72 | } 73 | ``` 74 | 75 | The event subscriber will listen to the `ConfigEvents::CONFIG_ON_GENERATE` and `ConfigEvents::CONFIG_PRE_SAVE` events. 76 | 77 | The `ConfigEvents::CONFIG_ON_GENERATE` is dispatched when the configuration form is built giving the plugin an opportunity to inject it's own tab and config options. 78 | 79 | To do this, the plugin must register it's configuration details through the method assigned to the `ConfigEvents::CONFIG_ON_GENERATE` event. The \Mautic\ConfigBundle\Event\ConfigBuilderEvent object is passed into the method and expects the method to call `addForm()`. `addForm()` expects an array with the following elements: 80 | 81 | Key|Description 82 | ---|----------- 83 | **formAlias**|Alias of the form type class that sets the expected form elements 84 | **formTheme**|View to format the configuration form elements, i.e, `HelloWorldBundle:FormTheme\Config` 85 | **parameters**|Array of custom config elements. `$event->getParametersFromConfig('HelloWorldBundle')))` can be used to glean them from the plugin's config file. 86 | 87 | The `ConfigEvents::CONFIG_PRE_SAVE` is called before the values from the form are rendered and saved to the local.php file. This gives the plugin an opportunity to clean up or manipulate the data before it is written. 88 | 89 | Remember that the subscriber must be registered through the plugin's config in the `services[events]` [section](#services). 90 | 91 | ###Config Form 92 | 93 | ```php 94 | add( 114 | 'custom_config_option', 115 | 'text', 116 | array( 117 | 'label' => 'plugin.helloworld.config.custom_config_option', 118 | 'data' => $options['data']['custom_config_option'], 119 | 'attr' => array( 120 | 'tooltip' => 'plugin.helloworld.config.custom_config_option_tooltip' 121 | ) 122 | ) 123 | ); 124 | } 125 | 126 | /** 127 | * {@inheritdoc} 128 | */ 129 | public function getName() 130 | { 131 | return 'helloworld_config'; 132 | } 133 | } 134 | ``` 135 | 136 | The form type is used to generate the form fields in the main configuration form. Refer to [Forms](#forms) for more information on using form types. 137 | 138 | Remember that the form type must be registered through the plugin's config in the `services[forms]` [section](#services). 139 | 140 | ###Config Template 141 | 142 | ```php 143 | 147 | 148 |
    149 |
    150 |

    trans('mautic.config.tab.helloworld_config'); ?>

    151 |
    152 |
    153 | children as $f): ?> 154 |
    155 |
    156 | row($f); ?> 157 |
    158 |
    159 | 160 |
    161 |
    162 | ``` 163 | 164 | Registering a formTheme as `HelloWorldBundle:FormTheme\Config` in the event listener told the ConfigBundle to look in the HelloWorldBundle's Views/FormTheme/Config folder for templates. Specifically, it will look for a template named `_config_{formAlias}_widget.html.php` where `{formAlias}` is the same as `formAlias` set in the plugin's `ConfigEvents::CONFIG_ON_GENERATE` event listener. 165 | 166 | The template should be in a panel format to match the rest of the config UI. --------------------------------------------------------------------------------