First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

View file

@ -0,0 +1,383 @@
<?php
namespace Civi\Core;
use Civi\Core\Exception\UnknownAssetException;
/**
* Class AssetBuilder
* @package Civi\Core
*
* The AssetBuilder is used to manage semi-dynamic assets.
* In normal production use, these assets are built on first
* reference and then stored in a public-facing cache folder.
* (In debug mode, these assets are constructed during every request.)
*
* There are generally two aspects to usage -- creating a URL
* for the asset, and defining the content of the asset.
*
* For example, suppose we wanted to define a static file
* named "api-fields.json" which lists all the fields of
* all the API entities.
*
* @code
* // Build a URL to `api-fields.json`.
* $url = \Civi::service('asset_builder')->getUrl('api-fields.json');
*
* // Define the content of `api-fields.json`.
* function hook_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
* if ($asset !== 'api-fields.json') return;
*
* $entities = civicrm_api3('Entity', 'get', array());
* $fields = array();
* foreach ($entities['values'] as $entity) {
* $fields[$entity] = civicrm_api3($entity, 'getfields');
* }
*
* $mimeType = 'application/json';
* $content = json_encode($fields);
* }
* @endCode
*
* Assets can be parameterized. Each combination of ($asset,$params)
* will be cached separately. For example, we might want a copy of
* 'api-fields.json' which only includes a handful of chosen entities.
* Simply pass the chosen entities into `getUrl()`, then update
* the definition to use `$params['entities']`, as in:
*
* @code
* // Build a URL to `api-fields.json`.
* $url = \Civi::service('asset_builder')->getUrl('api-fields.json', array(
* 'entities' => array('Contact', 'Phone', 'Email', 'Address'),
* ));
*
* // Define the content of `api-fields.json`.
* function hook_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
* if ($asset !== 'api-fields.json') return;
*
* $fields = array();
* foreach ($params['entities'] as $entity) {
* $fields[$entity] = civicrm_api3($entity, 'getfields');
* }
*
* $mimeType = 'application/json';
* $content = json_encode($fields);
* }
* @endCode
*
* Note: These assets are designed to hold non-sensitive data, such as
* aggregated JS or common metadata. There probably are ways to
* secure it (e.g. alternative digest() calculations), but the
* current implementation is KISS.
*/
class AssetBuilder {
/**
* @return array
* Array(string $value => string $label).
*/
public static function getCacheModes() {
return array(
'0' => ts('Disable'),
'1' => ts('Enable'),
'auto' => ts('Auto'),
);
}
protected $cacheEnabled;
/**
* AssetBuilder constructor.
* @param $cacheEnabled
*/
public function __construct($cacheEnabled = NULL) {
if ($cacheEnabled === NULL) {
$cacheEnabled = \Civi::settings()->get('assetCache');
if ($cacheEnabled === 'auto') {
$cacheEnabled = !\CRM_Core_Config::singleton()->debug;
}
$cacheEnabled = (bool) $cacheEnabled;
}
$this->cacheEnabled = $cacheEnabled;
}
/**
* Determine if $name is a well-formed asset name.
*
* @param string $name
* @return bool
*/
public function isValidName($name) {
return preg_match(';^[a-zA-Z0-9\.\-_/]+$;', $name)
&& strpos($name, '..') === FALSE
&& strpos($name, '.') !== FALSE;
}
/**
* @param string $name
* Ex: 'angular.json'.
* @param array $params
* @return string
* URL.
* Ex: 'http://example.org/files/civicrm/dyn/angular.abcd1234abcd1234.json'.
*/
public function getUrl($name, $params = array()) {
if (!$this->isValidName($name)) {
throw new \RuntimeException("Invalid dynamic asset name");
}
if ($this->isCacheEnabled()) {
$fileName = $this->build($name, $params);
return $this->getCacheUrl($fileName);
}
else {
return \CRM_Utils_System::url('civicrm/asset/builder', array(
'an' => $name,
'ap' => $this->encode($params),
'ad' => $this->digest($name, $params),
), TRUE, NULL, FALSE);
}
}
/**
* @param string $name
* Ex: 'angular.json'.
* @param array $params
* @return string
* URL.
* Ex: '/var/www/files/civicrm/dyn/angular.abcd1234abcd1234.json'.
*/
public function getPath($name, $params = array()) {
if (!$this->isValidName($name)) {
throw new \RuntimeException("Invalid dynamic asset name");
}
$fileName = $this->build($name, $params);
return $this->getCachePath($fileName);
}
/**
* Build the cached copy of an $asset.
*
* @param string $name
* Ex: 'angular.json'.
* @param array $params
* @param bool $force
* Build the asset anew, even if it already exists.
* @return string
* File name (relative to cache folder).
* Ex: 'angular.abcd1234abcd1234.json'.
* @throws UnknownAssetException
*/
public function build($name, $params, $force = FALSE) {
if (!$this->isValidName($name)) {
throw new UnknownAssetException("Asset name is malformed");
}
$nameParts = explode('.', $name);
array_splice($nameParts, -1, 0, array($this->digest($name, $params)));
$fileName = implode('.', $nameParts);
if ($force || !file_exists($this->getCachePath($fileName))) {
// No file locking, but concurrent writers should produce
// the same data, so we'll just plow ahead.
if (!file_exists($this->getCachePath())) {
mkdir($this->getCachePath());
}
$rendered = $this->render($name, $params);
file_put_contents($this->getCachePath($fileName), $rendered['content']);
return $fileName;
}
return $fileName;
}
/**
* Generate the content for a dynamic asset.
*
* @param string $name
* @param array $params
* @return array
* Array with keys:
* - statusCode: int, ex: 200.
* - mimeType: string, ex: 'text/html'.
* - content: string, ex: '<body>Hello world</body>'.
* @throws \CRM_Core_Exception
*/
public function render($name, $params = array()) {
if (!$this->isValidName($name)) {
throw new UnknownAssetException("Asset name is malformed");
}
\CRM_Utils_Hook::buildAsset($name, $params, $mimeType, $content);
if ($mimeType === NULL && $content === NULL) {
throw new UnknownAssetException("Unrecognized asset name: $name");
}
// Beg your pardon, sir. Please may I have an HTTP response class instead?
return array(
'statusCode' => 200,
'mimeType' => $mimeType,
'content' => $content,
);
}
/**
* Clear out any cache files.
*/
public function clear() {
\CRM_Utils_File::cleanDir($this->getCachePath());
}
/**
* Determine the local path of a cache file.
*
* @param string|NULL $fileName
* Ex: 'angular.abcd1234abcd1234.json'.
* @return string
* URL.
* Ex: '/var/www/files/civicrm/dyn/angular.abcd1234abcd1234.json'.
*/
protected function getCachePath($fileName = NULL) {
// imageUploadDir has the correct functional properties but a wonky name.
$suffix = ($fileName === NULL) ? '' : (DIRECTORY_SEPARATOR . $fileName);
return
\CRM_Utils_File::addTrailingSlash(\CRM_Core_Config::singleton()->imageUploadDir)
. 'dyn' . $suffix;
}
/**
* Determine the URL of a cache file.
*
* @param string|NULL $fileName
* Ex: 'angular.abcd1234abcd1234.json'.
* @return string
* URL.
* Ex: 'http://example.org/files/civicrm/dyn/angular.abcd1234abcd1234.json'.
*/
protected function getCacheUrl($fileName = NULL) {
// imageUploadURL has the correct functional properties but a wonky name.
$suffix = ($fileName === NULL) ? '' : ('/' . $fileName);
return
\CRM_Utils_File::addTrailingSlash(\CRM_Core_Config::singleton()->imageUploadURL, '/')
. 'dyn' . $suffix;
}
/**
* Create a unique identifier for the $params.
*
* This identifier is designed to avoid accidental cache collisions.
*
* @param string $name
* @param array $params
* @return string
*/
protected function digest($name, $params) {
// WISHLIST: For secure digest, generate+persist privatekey & call hash_hmac.
ksort($params);
$digest = md5(
$name .
\CRM_Core_Resources::singleton()->getCacheCode() .
\CRM_Core_Config_Runtime::getId() .
json_encode($params)
);
return $digest;
}
/**
* Encode $params in a format that's optimized for shorter URLs.
*
* @param array $params
* @return string
*/
protected function encode($params) {
if (empty($params)) {
return '';
}
$str = json_encode($params);
if (function_exists('gzdeflate')) {
$str = gzdeflate($str);
}
return base64_encode($str);
}
/**
* @param string $str
* @return array
*/
protected function decode($str) {
if ($str === NULL || $str === FALSE || $str === '') {
return array();
}
$str = base64_decode($str);
if (function_exists('gzdeflate')) {
$str = gzinflate($str);
}
return json_decode($str, TRUE);
}
/**
* @return bool
*/
public function isCacheEnabled() {
return $this->cacheEnabled;
}
/**
* @param bool|null $cacheEnabled
* @return AssetBuilder
*/
public function setCacheEnabled($cacheEnabled) {
$this->cacheEnabled = $cacheEnabled;
return $this;
}
/**
* (INTERNAL ONLY)
*
* Execute a page-request for `civicrm/asset/builder`.
*/
public static function pageRun() {
// Beg your pardon, sir. Please may I have an HTTP response class instead?
$asset = self::pageRender($_GET);
if (function_exists('http_response_code')) {
// PHP 5.4+
http_response_code($asset['statusCode']);
}
else {
header('X-PHP-Response-Code: ' . $asset['statusCode'], TRUE, $asset['statusCode']);
}
header('Content-Type: ' . $asset['mimeType']);
echo $asset['content'];
\CRM_Utils_System::civiExit();
}
/**
* (INTERNAL ONLY)
*
* Execute a page-request for `civicrm/asset/builder`.
*
* @param array $get
* The _GET values.
* @return array
* Array with keys:
* - statusCode: int, ex 200.
* - mimeType: string, ex 'text/html'.
* - content: string, ex '<body>Hello world</body>'.
*/
public static function pageRender($get) {
// Beg your pardon, sir. Please may I have an HTTP response class instead?
try {
$assets = \Civi::service('asset_builder');
return $assets->render($get['an'], $assets->decode($get['ap']));
}
catch (UnknownAssetException $e) {
return array(
'statusCode' => 404,
'mimeType' => 'text/plain',
'content' => $e->getMessage(),
);
}
}
}

View file

@ -0,0 +1,135 @@
<?php
namespace Civi\Core;
use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Symfony\Component\EventDispatcher\Event;
/**
* Class CiviEventDispatcher
* @package Civi\Core
*
* The CiviEventDispatcher is a Symfony dispatcher. Additionally, if an event
* follows the naming convention of "hook_*", then it will also be dispatched
* through CRM_Utils_Hook::invoke().
*
* @see \CRM_Utils_Hook
*/
class CiviEventDispatcher extends ContainerAwareEventDispatcher {
const DEFAULT_HOOK_PRIORITY = -100;
/**
* Track the list of hook-events for which we have autoregistered
* the hook adapter.
*
* @var array
* Array(string $eventName => trueish).
*/
private $autoListeners = array();
/**
* Determine whether $eventName should delegate to the CMS hook system.
*
* @param string $eventName
* Ex: 'civi.token.eval', 'hook_civicrm_post`.
* @return bool
*/
protected function isHookEvent($eventName) {
return (substr($eventName, 0, 5) === 'hook_') && (strpos($eventName, '::') === FALSE);
}
/**
* @inheritDoc
*/
public function dispatch($eventName, Event $event = NULL) {
$this->bindPatterns($eventName);
return parent::dispatch($eventName, $event);
}
/**
* @inheritDoc
*/
public function getListeners($eventName = NULL) {
$this->bindPatterns($eventName);
return parent::getListeners($eventName);
}
/**
* @inheritDoc
*/
public function hasListeners($eventName = NULL) {
// All hook_* events have default listeners, so hasListeners(NULL) is a truism.
return ($eventName === NULL || $this->isHookEvent($eventName))
? TRUE : parent::hasListeners($eventName);
}
/**
* Invoke hooks using an event object.
*
* @param \Civi\Core\Event\GenericHookEvent $event
* @param string $eventName
* Ex: 'hook_civicrm_dashboard'.
*/
public static function delegateToUF($event, $eventName) {
$hookName = substr($eventName, 5);
$hooks = \CRM_Utils_Hook::singleton();
$params = $event->getHookValues();
$count = count($params);
switch ($count) {
case 0:
$fResult = $hooks->invokeViaUF($count, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, $hookName);
break;
case 1:
$fResult = $hooks->invokeViaUF($count, $params[0], \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, $hookName);
break;
case 2:
$fResult = $hooks->invokeViaUF($count, $params[0], $params[1], \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, $hookName);
break;
case 3:
$fResult = $hooks->invokeViaUF($count, $params[0], $params[1], $params[2], \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, $hookName);
break;
case 4:
$fResult = $hooks->invokeViaUF($count, $params[0], $params[1], $params[2], $params[3], \CRM_Utils_Hook::$_nullObject, \CRM_Utils_Hook::$_nullObject, $hookName);
break;
case 5:
$fResult = $hooks->invokeViaUF($count, $params[0], $params[1], $params[2], $params[3], $params[4], \CRM_Utils_Hook::$_nullObject, $hookName);
break;
case 6:
$fResult = $hooks->invokeViaUF($count, $params[0], $params[1], $params[2], $params[3], $params[4], $params[5], $hookName);
break;
default:
throw new \RuntimeException("hook_{$hookName} cannot support more than 6 parameters");
}
$event->addReturnValues($fResult);
}
/**
* @param string $eventName
* Ex: 'civi.api.resolve' or 'hook_civicrm_dashboard'.
*/
protected function bindPatterns($eventName) {
if ($eventName !== NULL && !isset($this->autoListeners[$eventName])) {
$this->autoListeners[$eventName] = 1;
if ($this->isHookEvent($eventName)) {
// WISHLIST: For native extensions (and possibly D6/D7/D8/BD), enumerate
// the listeners and list them one-by-one. This would make it easier to
// inspect via "cv debug:event-dispatcher".
$this->addListener($eventName, array(
'\Civi\Core\CiviEventDispatcher',
'delegateToUF',
), self::DEFAULT_HOOK_PRIORITY);
}
}
}
}

View file

@ -0,0 +1,227 @@
<?php
namespace Civi\Core;
/**
* Class CiviEventInspector
*
* The event inspector is a development tool which provides metadata about events.
* It can be used for code-generators and documentation-generators.
*
* @code
* $i = new CiviEventInspector();
* print_r(CRM_Utils_Array::collect('name', $i->getAll()));
* @endCode
*
* An event definition includes these fields:
* - type: string, required. Ex: 'hook' or 'object'
* - name: string, required. Ex: 'hook_civicrm_post' or 'civi.dao.postInsert'
* - class: string, required. Ex: 'Civi\Core\Event\GenericHookEvent'.
* - signature: string, required FOR HOOKS. Ex: '$first, &$second'.
* - fields: array, required FOR HOOKS. List of hook parameters.
* - stub: ReflectionMethod, optional. An example function with docblocks/inputs.
*
* Note: The inspector is only designed for use in developer workflows, such
* as code-generation and inspection. It should be not called by regular
* runtime logic.
*/
class CiviEventInspector {
/**
* Register the default hooks defined by 'CRM_Utils_Hook'.
*
* @param \Civi\Core\Event\GenericHookEvent $e
* @see \CRM_Utils_Hook::eventDefs()
*/
public static function findBuiltInEvents(\Civi\Core\Event\GenericHookEvent $e) {
$skipList = array('singleton');
$e->inspector->addStaticStubs('CRM_Utils_Hook', 'hook_civicrm_',
function ($eventDef, $method) use ($skipList) {
return in_array($method->name, $skipList) ? NULL : $eventDef;
});
}
/**
* @var array
* Array(string $name => array $eventDef).
*
* Ex: $eventDefs['hook_civicrm_foo']['description_html'] = 'Hello world';
*/
protected $eventDefs;
/**
* Perform a scan to identify/describe all events.
*
* @param bool $force
* @return CiviEventInspector
*/
public function build($force = FALSE) {
if ($force || $this->eventDefs === NULL) {
$this->eventDefs = array();
\CRM_Utils_Hook::eventDefs($this);
ksort($this->eventDefs);
}
return $this;
}
/**
* Get a list of all events.
*
* @return array
* Array(string $name => array $eventDef).
* Ex: $result['hook_civicrm_foo']['description_html'] = 'Hello world';
*/
public function getAll() {
$this->build();
return $this->eventDefs;
}
/**
* Find any events that match a pattern.
*
* @param string $regex
* @return array
* Array(string $name => array $eventDef).
* Ex: $result['hook_civicrm_foo']['description_html'] = 'Hello world';
*/
public function find($regex) {
$this->build();
return array_filter($this->eventDefs, function ($e) use ($regex) {
return preg_match($regex, $e['name']);
});
}
/**
* Get the definition of one event.
*
* @param string $name
* Ex: 'hook_civicrm_alterSettingsMetaData'.
* @return array
* Ex: $result['description_html'] = 'Hello world';
*/
public function get($name) {
$this->build();
return $this->eventDefs[$name];
}
/**
* @param $eventDef
* @return bool
* TRUE if valid.
*/
public function validate($eventDef) {
if (!is_array($eventDef) || empty($eventDef['name']) || !isset($eventDef['type'])) {
return FALSE;
}
if (!in_array($eventDef['type'], array('hook', 'object'))) {
return FALSE;
}
if ($eventDef['type'] === 'hook') {
if (!isset($eventDef['signature']) || !is_array($eventDef['fields'])) {
return FALSE;
}
}
return TRUE;
}
/**
* Add a new event definition.
*
* @param array $eventDef
* @return CiviEventInspector
*/
public function add($eventDef) {
$name = isset($eventDef['name']) ? $eventDef['name'] : NULL;
if (!isset($eventDef['type'])) {
$eventDef['type'] = preg_match('/^hook_/', $eventDef['name']) ? 'hook' : 'object';
}
if ($eventDef['type'] === 'hook' && empty($eventDef['signature'])) {
$eventDef['signature'] = implode(', ', array_map(
function ($field) {
$sigil = $field['ref'] ? '&$' : '$';
return $sigil . $field['name'];
},
$eventDef['fields']
));
}
if (TRUE !== $this->validate($eventDef)) {
throw new \CRM_Core_Exception("Failed to register event ($name). Invalid definition.");
}
$this->eventDefs[$name] = $eventDef;
return $this;
}
/**
* Scan a Symfony event class for metadata, and add it.
*
* @param string $event
* Ex: 'civi.api.authorize'.
* @param string $className
* Ex: 'Civi\API\Event\AuthorizeEvent'.
* @return CiviEventInspector
*/
public function addEventClass($event, $className) {
$this->add(array(
'name' => $event,
'class' => $className,
));
return $this;
}
/**
* Scan a class for hook stubs, and add all of them.
*
* @param string $className
* The name of a class which contains static stub functions.
* Ex: 'CRM_Utils_Hook'.
* @param string $prefix
* A prefix to apply to all hook names.
* Ex: 'hook_civicrm_'.
* @param null|callable $filter
* An optional function to filter/rewrite the metadata for each hook.
* @return CiviEventInspector
*/
public function addStaticStubs($className, $prefix, $filter = NULL) {
$class = new \ReflectionClass($className);
foreach ($class->getMethods(\ReflectionMethod::IS_STATIC) as $method) {
if (!isset($method->name)) {
continue;
}
$eventDef = array(
'name' => $prefix . $method->name,
'description_html' => $method->getDocComment() ? \CRM_Admin_Page_APIExplorer::formatDocBlock($method->getDocComment()) : '',
'fields' => array(),
'class' => 'Civi\Core\Event\GenericHookEvent',
'stub' => $method,
);
foreach ($method->getParameters() as $parameter) {
$eventDef['fields'][$parameter->getName()] = array(
'name' => $parameter->getName(),
'ref' => (bool) $parameter->isPassedByReference(),
// WISHLIST: 'type' => 'mixed',
);
}
if ($filter !== NULL) {
$eventDef = $filter($eventDef, $method);
if ($eventDef === NULL) {
continue;
}
}
$this->add($eventDef);
}
return $this;
}
}

View file

@ -0,0 +1,460 @@
<?php
namespace Civi\Core;
use Civi\API\Provider\ActionObjectProvider;
use Civi\Core\Event\SystemInstallEvent;
use Civi\Core\Lock\LockManager;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\FileCacheReader;
use Doctrine\Common\Cache\FilesystemCache;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Tools\Setup;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
// TODO use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
/**
* Class Container
* @package Civi\Core
*/
class Container {
const SELF = 'civi_container_factory';
/**
* @param bool $reset
* Whether to forcibly rebuild the entire container.
* @return \Symfony\Component\DependencyInjection\TaggedContainerInterface
*/
public static function singleton($reset = FALSE) {
if ($reset || !isset(\Civi::$statics[__CLASS__]['container'])) {
self::boot(TRUE);
}
return \Civi::$statics[__CLASS__]['container'];
}
/**
* Find a cached container definition or construct a new one.
*
* There are many weird contexts in which Civi initializes (eg different
* variations of multitenancy and different permutations of CMS/CRM bootstrap),
* and hook_container may fire a bit differently in each context. To mitigate
* risk of leaks between environments, we compute a unique envID
* (md5(DB_NAME, HTTP_HOST, SCRIPT_FILENAME, etc)) and use separate caches for
* each (eg "templates_c/CachedCiviContainer.$ENVID.php").
*
* Constants:
* - CIVICRM_CONTAINER_CACHE -- 'always' [default], 'never', 'auto'
* - CIVICRM_DSN
* - CIVICRM_DOMAIN_ID
* - CIVICRM_TEMPLATE_COMPILEDIR
*
* @return ContainerInterface
*/
public function loadContainer() {
// Note: The container's raison d'etre is to manage construction of other
// services. Consequently, we assume a minimal service available -- the classloader
// has been setup, and civicrm.settings.php is loaded, but nothing else works.
$cacheMode = defined('CIVICRM_CONTAINER_CACHE') ? CIVICRM_CONTAINER_CACHE : 'always';
// In pre-installation environments, don't bother with caching.
if (!defined('CIVICRM_TEMPLATE_COMPILEDIR') || !defined('CIVICRM_DSN') || $cacheMode === 'never' || \CRM_Utils_System::isInUpgradeMode()) {
$containerBuilder = $this->createContainer();
$containerBuilder->compile();
return $containerBuilder;
}
$envId = \CRM_Core_Config_Runtime::getId();
$file = CIVICRM_TEMPLATE_COMPILEDIR . "/CachedCiviContainer.{$envId}.php";
$containerConfigCache = new ConfigCache($file, $cacheMode === 'auto');
if (!$containerConfigCache->isFresh()) {
$containerBuilder = $this->createContainer();
$containerBuilder->compile();
$dumper = new PhpDumper($containerBuilder);
$containerConfigCache->write(
$dumper->dump(array('class' => 'CachedCiviContainer')),
$containerBuilder->getResources()
);
}
require_once $file;
$c = new \CachedCiviContainer();
return $c;
}
/**
* Construct a new container.
*
* @var ContainerBuilder
* @return \Symfony\Component\DependencyInjection\ContainerBuilder
*/
public function createContainer() {
$civicrm_base_path = dirname(dirname(__DIR__));
$container = new ContainerBuilder();
$container->addCompilerPass(new RegisterListenersPass('dispatcher'));
$container->addObjectResource($this);
$container->setParameter('civicrm_base_path', $civicrm_base_path);
//$container->set(self::SELF, $this);
$container->addResource(new \Symfony\Component\Config\Resource\FileResource(__FILE__));
$container->setDefinition(self::SELF, new Definition(
'Civi\Core\Container',
array()
));
// TODO Move configuration to an external file; define caching structure
// if (empty($configDirectories)) {
// throw new \Exception(__CLASS__ . ': Missing required properties (civicrmRoot, configDirectories)');
// }
// $locator = new FileLocator($configDirectories);
// $loaderResolver = new LoaderResolver(array(
// new YamlFileLoader($container, $locator)
// ));
// $delegatingLoader = new DelegatingLoader($loaderResolver);
// foreach (array('services.yml') as $file) {
// $yamlUserFiles = $locator->locate($file, NULL, FALSE);
// foreach ($yamlUserFiles as $file) {
// $delegatingLoader->load($file);
// }
// }
$container->setDefinition('angular', new Definition(
'Civi\Angular\Manager',
array()
))
->setFactory(array(new Reference(self::SELF), 'createAngularManager'));
$container->setDefinition('dispatcher', new Definition(
'Civi\Core\CiviEventDispatcher',
array(new Reference('service_container'))
))
->setFactory(array(new Reference(self::SELF), 'createEventDispatcher'));
$container->setDefinition('magic_function_provider', new Definition(
'Civi\API\Provider\MagicFunctionProvider',
array()
));
$container->setDefinition('civi_api_kernel', new Definition(
'Civi\API\Kernel',
array(new Reference('dispatcher'), new Reference('magic_function_provider'))
))
->setFactory(array(new Reference(self::SELF), 'createApiKernel'));
$container->setDefinition('cxn_reg_client', new Definition(
'Civi\Cxn\Rpc\RegistrationClient',
array()
))
->setFactory('CRM_Cxn_BAO_Cxn::createRegistrationClient');
$container->setDefinition('psr_log', new Definition('CRM_Core_Error_Log', array()));
foreach (array('js_strings', 'community_messages') as $cacheName) {
$container->setDefinition("cache.{$cacheName}", new Definition(
'CRM_Utils_Cache_Interface',
array(
array(
'name' => $cacheName,
'type' => array('*memory*', 'SqlGroup', 'ArrayCache'),
),
)
))->setFactory('CRM_Utils_Cache::create');
}
$container->setDefinition('sql_triggers', new Definition(
'Civi\Core\SqlTriggers',
array()
));
$container->setDefinition('asset_builder', new Definition(
'Civi\Core\AssetBuilder',
array()
));
$container->setDefinition('pear_mail', new Definition('Mail'))
->setFactory('CRM_Utils_Mail::createMailer');
if (empty(\Civi::$statics[__CLASS__]['boot'])) {
throw new \RuntimeException("Cannot initialize container. Boot services are undefined.");
}
foreach (\Civi::$statics[__CLASS__]['boot'] as $bootService => $def) {
$container->setDefinition($bootService, new Definition())->setSynthetic(TRUE);
}
// Expose legacy singletons as services in the container.
$singletons = array(
'resources' => 'CRM_Core_Resources',
'httpClient' => 'CRM_Utils_HttpClient',
'cache.default' => 'CRM_Utils_Cache',
'i18n' => 'CRM_Core_I18n',
// Maybe? 'config' => 'CRM_Core_Config',
// Maybe? 'smarty' => 'CRM_Core_Smarty',
);
foreach ($singletons as $name => $class) {
$container->setDefinition($name, new Definition(
$class
))
->setFactory(array($class, 'singleton'));
}
$container->setDefinition('civi.mailing.triggers', new Definition(
'Civi\Core\SqlTrigger\TimestampTriggers',
array('civicrm_mailing', 'Mailing')
))->addTag('kernel.event_listener', array('event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo'));
$container->setDefinition('civi.activity.triggers', new Definition(
'Civi\Core\SqlTrigger\TimestampTriggers',
array('civicrm_activity', 'Activity')
))->addTag('kernel.event_listener', array('event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo'));
$container->setDefinition('civi.case.triggers', new Definition(
'Civi\Core\SqlTrigger\TimestampTriggers',
array('civicrm_case', 'Case')
))->addTag('kernel.event_listener', array('event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo'));
$container->setDefinition('civi.case.staticTriggers', new Definition(
'Civi\Core\SqlTrigger\StaticTriggers',
array(
array(
array(
'upgrade_check' => array('table' => 'civicrm_case', 'column' => 'modified_date'),
'table' => 'civicrm_case_activity',
'when' => 'AFTER',
'event' => array('INSERT'),
'sql' => "\nUPDATE civicrm_case SET modified_date = CURRENT_TIMESTAMP WHERE id = NEW.case_id;\n",
),
array(
'upgrade_check' => array('table' => 'civicrm_case', 'column' => 'modified_date'),
'table' => 'civicrm_activity',
'when' => 'BEFORE',
'event' => array('UPDATE', 'DELETE'),
'sql' => "\nUPDATE civicrm_case SET modified_date = CURRENT_TIMESTAMP WHERE id IN (SELECT ca.case_id FROM civicrm_case_activity ca WHERE ca.activity_id = OLD.id);\n",
),
),
)
))
->addTag('kernel.event_listener', array('event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo'));
$container->setDefinition('civi_token_compat', new Definition(
'Civi\Token\TokenCompatSubscriber',
array()
))->addTag('kernel.event_subscriber');
$container->setDefinition("crm_mailing_action_tokens", new Definition(
"CRM_Mailing_ActionTokens",
array()
))->addTag('kernel.event_subscriber');
foreach (array('Activity', 'Contribute', 'Event', 'Mailing', 'Member') as $comp) {
$container->setDefinition("crm_" . strtolower($comp) . "_tokens", new Definition(
"CRM_{$comp}_Tokens",
array()
))->addTag('kernel.event_subscriber');
}
if (\CRM_Utils_Constant::value('CIVICRM_FLEXMAILER_HACK_SERVICES')) {
\Civi\Core\Resolver::singleton()->call(CIVICRM_FLEXMAILER_HACK_SERVICES, array($container));
}
\CRM_Utils_Hook::container($container);
return $container;
}
/**
* @return \Civi\Angular\Manager
*/
public function createAngularManager() {
return new \Civi\Angular\Manager(\CRM_Core_Resources::singleton());
}
/**
* @param ContainerInterface $container
* @return \Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
*/
public function createEventDispatcher($container) {
$dispatcher = new CiviEventDispatcher($container);
$dispatcher->addListener(SystemInstallEvent::EVENT_NAME, array('\Civi\Core\InstallationCanary', 'check'));
$dispatcher->addListener(SystemInstallEvent::EVENT_NAME, array('\Civi\Core\DatabaseInitializer', 'initialize'));
$dispatcher->addListener(SystemInstallEvent::EVENT_NAME, array('\Civi\Core\LocalizationInitializer', 'initialize'));
$dispatcher->addListener('hook_civicrm_pre', array('\Civi\Core\Event\PreEvent', 'dispatchSubevent'), 100);
$dispatcher->addListener('hook_civicrm_post', array('\Civi\Core\Event\PostEvent', 'dispatchSubevent'), 100);
$dispatcher->addListener('hook_civicrm_post::Activity', array('\Civi\CCase\Events', 'fireCaseChange'));
$dispatcher->addListener('hook_civicrm_post::Case', array('\Civi\CCase\Events', 'fireCaseChange'));
$dispatcher->addListener('hook_civicrm_caseChange', array('\Civi\CCase\Events', 'delegateToXmlListeners'));
$dispatcher->addListener('hook_civicrm_caseChange', array('\Civi\CCase\SequenceListener', 'onCaseChange_static'));
$dispatcher->addListener('hook_civicrm_eventDefs', array('\Civi\Core\CiviEventInspector', 'findBuiltInEvents'));
// TODO We need a better code-convention for metadata about non-hook events.
$dispatcher->addListener('hook_civicrm_eventDefs', array('\Civi\API\Events', 'hookEventDefs'));
$dispatcher->addListener('hook_civicrm_eventDefs', array('\Civi\Core\Event\SystemInstallEvent', 'hookEventDefs'));
$dispatcher->addListener('hook_civicrm_buildAsset', array('\Civi\Angular\Page\Modules', 'buildAngularModules'));
$dispatcher->addListener('hook_civicrm_buildAsset', array('\CRM_Utils_VisualBundle', 'buildAssetJs'));
$dispatcher->addListener('hook_civicrm_buildAsset', array('\CRM_Utils_VisualBundle', 'buildAssetCss'));
$dispatcher->addListener('civi.dao.postInsert', array('\CRM_Core_BAO_RecurringEntity', 'triggerInsert'));
$dispatcher->addListener('civi.dao.postUpdate', array('\CRM_Core_BAO_RecurringEntity', 'triggerUpdate'));
$dispatcher->addListener('civi.dao.postDelete', array('\CRM_Core_BAO_RecurringEntity', 'triggerDelete'));
$dispatcher->addListener('hook_civicrm_unhandled_exception', array(
'CRM_Core_LegacyErrorHandler',
'handleException',
), -200);
$dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Activity_ActionMapping', 'onRegisterActionMappings'));
$dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Contact_ActionMapping', 'onRegisterActionMappings'));
$dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Contribute_ActionMapping_ByPage', 'onRegisterActionMappings'));
$dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Contribute_ActionMapping_ByType', 'onRegisterActionMappings'));
$dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Event_ActionMapping', 'onRegisterActionMappings'));
$dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Member_ActionMapping', 'onRegisterActionMappings'));
if (\CRM_Utils_Constant::value('CIVICRM_FLEXMAILER_HACK_LISTENERS')) {
\Civi\Core\Resolver::singleton()->call(CIVICRM_FLEXMAILER_HACK_LISTENERS, array($dispatcher));
}
return $dispatcher;
}
/**
* @return LockManager
*/
public static function createLockManager() {
// Ideally, downstream implementers could override any definitions in
// the container. For now, we'll make-do with some define()s.
$lm = new LockManager();
$lm
->register('/^cache\./', defined('CIVICRM_CACHE_LOCK') ? CIVICRM_CACHE_LOCK : array('CRM_Core_Lock', 'createScopedLock'))
->register('/^data\./', defined('CIVICRM_DATA_LOCK') ? CIVICRM_DATA_LOCK : array('CRM_Core_Lock', 'createScopedLock'))
->register('/^worker\.mailing\.send\./', defined('CIVICRM_WORK_LOCK') ? CIVICRM_WORK_LOCK : array('CRM_Core_Lock', 'createCivimailLock'))
->register('/^worker\./', defined('CIVICRM_WORK_LOCK') ? CIVICRM_WORK_LOCK : array('CRM_Core_Lock', 'createScopedLock'));
// Registrations may use complex resolver expressions, but (as a micro-optimization)
// the default factory is specified as an array.
return $lm;
}
/**
* @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
* @param $magicFunctionProvider
*
* @return \Civi\API\Kernel
*/
public function createApiKernel($dispatcher, $magicFunctionProvider) {
$dispatcher->addSubscriber(new \Civi\API\Subscriber\ChainSubscriber());
$dispatcher->addSubscriber(new \Civi\API\Subscriber\TransactionSubscriber());
$dispatcher->addSubscriber(new \Civi\API\Subscriber\I18nSubscriber());
$dispatcher->addSubscriber($magicFunctionProvider);
$dispatcher->addSubscriber(new \Civi\API\Subscriber\PermissionCheck());
$dispatcher->addSubscriber(new \Civi\API\Subscriber\APIv3SchemaAdapter());
$dispatcher->addSubscriber(new \Civi\API\Subscriber\WrapperAdapter(array(
\CRM_Utils_API_HTMLInputCoder::singleton(),
\CRM_Utils_API_NullOutputCoder::singleton(),
\CRM_Utils_API_ReloadOption::singleton(),
\CRM_Utils_API_MatchOption::singleton(),
)));
$dispatcher->addSubscriber(new \Civi\API\Subscriber\XDebugSubscriber());
$kernel = new \Civi\API\Kernel($dispatcher);
$reflectionProvider = new \Civi\API\Provider\ReflectionProvider($kernel);
$dispatcher->addSubscriber($reflectionProvider);
$dispatcher->addSubscriber(new \Civi\API\Subscriber\DynamicFKAuthorization(
$kernel,
'Attachment',
array('create', 'get', 'delete'),
// Given a file ID, determine the entity+table it's attached to.
'SELECT if(cf.id,1,0) as is_valid, cef.entity_table, cef.entity_id
FROM civicrm_file cf
LEFT JOIN civicrm_entity_file cef ON cf.id = cef.file_id
WHERE cf.id = %1',
// Get a list of custom fields (field_name,table_name,extends)
'SELECT concat("custom_",fld.id) as field_name,
grp.table_name as table_name,
grp.extends as extends
FROM civicrm_custom_field fld
INNER JOIN civicrm_custom_group grp ON fld.custom_group_id = grp.id
WHERE fld.data_type = "File"
',
array('civicrm_activity', 'civicrm_mailing', 'civicrm_contact', 'civicrm_grant')
));
$kernel->setApiProviders(array(
$reflectionProvider,
$magicFunctionProvider,
));
return $kernel;
}
/**
* Get a list of boot services.
*
* These are services which must be setup *before* the container can operate.
*
* @param bool $loadFromDB
* @throws \CRM_Core_Exception
*/
public static function boot($loadFromDB) {
// Array(string $serviceId => object $serviceInstance).
$bootServices = array();
\Civi::$statics[__CLASS__]['boot'] = &$bootServices;
$bootServices['runtime'] = $runtime = new \CRM_Core_Config_Runtime();
$runtime->initialize($loadFromDB);
$bootServices['paths'] = new \Civi\Core\Paths();
$class = $runtime->userFrameworkClass;
$bootServices['userSystem'] = $userSystem = new $class();
$userSystem->initialize();
$userPermissionClass = 'CRM_Core_Permission_' . $runtime->userFramework;
$bootServices['userPermissionClass'] = new $userPermissionClass();
$bootServices['cache.settings'] = \CRM_Utils_Cache::create(array(
'name' => 'settings',
'type' => array('*memory*', 'SqlGroup', 'ArrayCache'),
));
$bootServices['settings_manager'] = new \Civi\Core\SettingsManager($bootServices['cache.settings']);
$bootServices['lockManager'] = self::createLockManager();
if ($loadFromDB && $runtime->dsn) {
\CRM_Core_DAO::init($runtime->dsn);
\CRM_Utils_Hook::singleton(TRUE);
\CRM_Extension_System::singleton(TRUE);
\CRM_Extension_System::singleton(TRUE)->getClassLoader()->register();
$runtime->includeCustomPath();
$c = new self();
$container = $c->loadContainer();
foreach ($bootServices as $name => $obj) {
$container->set($name, $obj);
}
\Civi::$statics[__CLASS__]['container'] = $container;
}
}
public static function getBootService($name) {
return \Civi::$statics[__CLASS__]['boot'][$name];
}
/**
* Determine whether the container services are available.
*
* @return bool
*/
public static function isContainerBooted() {
return isset(\Civi::$statics[__CLASS__]['container']);
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\DAO\Event;
/**
* Class PostUpdate
* @package Civi\Core\DAO\Event
*/
class PostDelete extends \Symfony\Component\EventDispatcher\Event {
/**
* @var DAO Object
*/
public $object;
/**
* @var DAO delete result
*/
public $result;
/**
* @param $object
* @param $result
*/
public function __construct($object, $result) {
$this->object = $object;
$this->result = $result;
}
}

View file

@ -0,0 +1,47 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\DAO\Event;
/**
* Class PostUpdate
* @package Civi\Core\DAO\Event
*/
class PostUpdate extends \Symfony\Component\EventDispatcher\Event {
/**
* @var DAO Object
*/
public $object;
/**
* @param $object
*/
public function __construct($object) {
$this->object = $object;
}
}

View file

@ -0,0 +1,47 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\DAO\Event;
/**
* Class PreDelete
* @package Civi\Core\DAO\Event
*/
class PreDelete extends \Symfony\Component\EventDispatcher\Event {
/**
* @var DAO Object
*/
public $object;
/**
* @param $object
*/
public function __construct($object) {
$this->object = $object;
}
}

View file

@ -0,0 +1,53 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core;
use Civi\Core\Event\SystemInstallEvent;
/**
* Class DatabaseInitializer
* @package Civi\Core
*/
class DatabaseInitializer {
/**
* Flush system to build the menu and MySQL triggers
*
* @param \Civi\Core\Event\SystemInstallEvent $event
* @throws \CRM_Core_Exception
*/
public static function initialize(SystemInstallEvent $event) {
$api_params = array(
'version' => 3,
'triggers' => 1,
'session' => 1,
);
civicrm_api('System', 'flush', $api_params);
}
}

View file

@ -0,0 +1,255 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Event;
/**
* Class GenericHookEvent
* @package Civi\API\Event
*
* The GenericHookEvent is used to expose all traditional hooks to the
* Symfony EventDispatcher.
*
* The traditional notation for a hook is based on a function signature:
*
* function hook_civicrm_foo($bar, &$whiz, &$bang);
*
* The notation for Symfony Events is based on a class with properties
* and methods. This requires some kind of mapping. `GenericHookEvent`
* maps each parameter to a field (using magic methods):
*
* @code
* // Creating an event object.
* $event = GenericHookEvent::create(array(
* 'bar' => 'abc',
* 'whiz' => &$whiz,
* 'bang' => &$bang,
* );
*
* // Accessing event properties.
* echo $event->bar;
* $event->whiz['array_field'] = 123;
* $event->bang->objProperty = 'abcd';
*
* // Dispatching an event.
* Civi::service('dispatcher')->dispatch('hook_civicrm_foo', $event);
* @endCode
*
* Design Discussion:
*
* 1. Implementing new event classes for every hook would produce a
* large amount of boilerplate. Symfony Events have an interesting solution to
* that problem: use `GenericEvent` instead of custom event classes.
* `GenericHookEvent` is conceptually similar to `GenericEvent`, but it adds
* support for (a) altering properties and (b) mapping properties to hook notation
* (an ordered parameter list).
*
* 2. A handful of hooks define a return-value. The return-value is treated
* as an array, and all the returned values are merged into one big array.
* You can add and retrieve return-values using these methods:
*
* @code
* $event->addReturnValues(array(...));
* foreach ($event->getReturnValues() as $retVal) { ... }
* @endCode
*/
class GenericHookEvent extends \Symfony\Component\EventDispatcher\Event {
/**
* @var array
* Ex: array(0 => &$contactID, 1 => &$contentPlacement).
*/
protected $hookValues;
/**
* @var array
* Ex: array(0 => 'contactID', 1 => 'contentPlacement').
*/
protected $hookFields;
/**
* @var array
* Ex: array('contactID' => 0, 'contentPlacement' => 1).
*/
protected $hookFieldsFlip;
/**
* Some legacy hooks expect listener-functions to return a value.
* OOP listeners may set the $returnValue.
*
* This field is not recommended for use in new hooks. The return-value
* convention is not portable across different implementations of the hook
* system. Instead, it's more portable to provide an alterable, named field.
*
* @var mixed
* @deprecated
*/
private $returnValues = array();
/**
* List of field names that are prohibited due to conflicts
* in the class-hierarchy.
*
* @var array
*/
private static $BLACKLIST = array(
'name',
'dispatcher',
'propagationStopped',
'hookBlacklist',
'hookValues',
'hookFields',
'hookFieldsFlip',
);
/**
* Create a GenericHookEvent using key-value pairs.
*
* @param array $params
* Ex: array('contactID' => &$contactID, 'contentPlacement' => &$contentPlacement).
* @return \Civi\Core\Event\GenericHookEvent
*/
public static function create($params) {
$e = new static();
$e->hookValues = array_values($params);
$e->hookFields = array_keys($params);
$e->hookFieldsFlip = array_flip($e->hookFields);
self::assertValidHookFields($e->hookFields);
return $e;
}
/**
* Create a GenericHookEvent using ordered parameters.
*
* @param array $hookFields
* Ex: array(0 => 'contactID', 1 => 'contentPlacement').
* @param array $hookValues
* Ex: array(0 => &$contactID, 1 => &$contentPlacement).
* @return \Civi\Core\Event\GenericHookEvent
*/
public static function createOrdered($hookFields, $hookValues) {
$e = new static();
if (count($hookValues) > count($hookFields)) {
$hookValues = array_slice($hookValues, 0, count($hookFields));
}
$e->hookValues = $hookValues;
$e->hookFields = $hookFields;
$e->hookFieldsFlip = array_flip($e->hookFields);
self::assertValidHookFields($e->hookFields);
return $e;
}
/**
* @param array $fields
* List of field names.
*/
private static function assertValidHookFields($fields) {
$bad = array_intersect($fields, self::$BLACKLIST);
if ($bad) {
throw new \RuntimeException("Hook relies on conflicted field names: "
. implode(', ', $bad));
}
}
/**
* @return array
* Ex: array(0 => &$contactID, 1 => &$contentPlacement).
*/
public function getHookValues() {
return $this->hookValues;
}
/**
* @return mixed
* @deprecated
*/
public function getReturnValues() {
return empty($this->returnValues) ? TRUE : $this->returnValues;
}
/**
* @param mixed $fResult
* @return GenericHookEvent
* @deprecated
*/
public function addReturnValues($fResult) {
if (!empty($fResult) && is_array($fResult)) {
$this->returnValues = array_merge($this->returnValues, $fResult);
}
return $this;
}
/**
* @inheritDoc
*/
public function &__get($name) {
if (isset($this->hookFieldsFlip[$name])) {
return $this->hookValues[$this->hookFieldsFlip[$name]];
}
}
/**
* @inheritDoc
*/
public function __set($name, $value) {
if (isset($this->hookFieldsFlip[$name])) {
$this->hookValues[$this->hookFieldsFlip[$name]] = $value;
}
}
/**
* @inheritDoc
*/
public function __isset($name) {
return isset($this->hookFieldsFlip[$name])
&& isset($this->hookValues[$this->hookFieldsFlip[$name]]);
}
/**
* @inheritDoc
*/
public function __unset($name) {
if (isset($this->hookFieldsFlip[$name])) {
// Unset while preserving order.
$this->hookValues[$this->hookFieldsFlip[$name]] = NULL;
}
}
/**
* Determine whether the hook supports the given field.
*
* The field may or may not be empty. Use isset() or empty() to
* check that.
*
* @param string $name
* @return bool
*/
public function hasField($name) {
return isset($this->hookFieldsFlip[$name]);
}
}

View file

@ -0,0 +1,87 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Event;
/**
* Class AuthorizeEvent
* @package Civi\API\Event
*/
class PostEvent extends GenericHookEvent {
/**
* This adapter automatically emits a narrower event.
*
* For example, `hook_civicrm_pre(Contact, ...)` will also dispatch `hook_civicrm_pre::Contact`.
*
* @param \Civi\Core\Event\PostEvent $event
*/
public static function dispatchSubevent(PostEvent $event) {
\Civi::service('dispatcher')->dispatch("hook_civicrm_post::" . $event->entity, $event);
}
/**
* @var string 'create'|'edit'|'delete' etc
*/
public $action;
/**
* @var string
*/
public $entity;
/**
* @var int|NULL
*/
public $id;
/**
* @var Object
*/
public $object;
/**
* @param $action
* @param $entity
* @param $id
* @param $object
*/
public function __construct($action, $entity, $id, &$object) {
$this->action = $action;
$this->entity = $entity;
$this->id = $id;
$this->object = &$object;
}
/**
* @inheritDoc
*/
public function getHookValues() {
return array($this->action, $this->entity, $this->id, &$this->object);
}
}

View file

@ -0,0 +1,87 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Event;
/**
* Class AuthorizeEvent
* @package Civi\API\Event
*/
class PreEvent extends GenericHookEvent {
/**
* This adapter automatically emits a narrower event.
*
* For example, `hook_civicrm_pre(Contact, ...)` will also dispatch `hook_civicrm_pre::Contact`.
*
* @param \Civi\Core\Event\PreEvent $event
*/
public static function dispatchSubevent(PreEvent $event) {
\Civi::service('dispatcher')->dispatch("hook_civicrm_pre::" . $event->entity, $event);
}
/**
* @var string 'create'|'edit'|'delete' etc
*/
public $action;
/**
* @var string
*/
public $entity;
/**
* @var int|NULL
*/
public $id;
/**
* @var array
*/
public $params;
/**
* @param $action
* @param $entity
* @param $id
* @param $params
*/
public function __construct($action, $entity, $id, &$params) {
$this->action = $action;
$this->entity = $entity;
$this->id = $id;
$this->params = &$params;
}
/**
* @inheritDoc
*/
public function getHookValues() {
return array($this->action, $this->entity, $this->id, &$this->params);
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Event;
/**
* Class SystemInstallEvent
* @package Civi\API\Event
*/
class SystemInstallEvent extends \Symfony\Component\EventDispatcher\Event {
/**
* The SystemInstallEvent fires once after installation - during the first page-view.
*/
const EVENT_NAME = 'civi.core.install';
/**
* @param \Civi\Core\Event\GenericHookEvent $e
* @see \CRM_Utils_Hook::eventDefs
*/
public static function hookEventDefs($e) {
$e->inspector->addEventClass(self::EVENT_NAME, __CLASS__);
}
}

View file

@ -0,0 +1,62 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Event;
/**
* Class UnhandledExceptionEvent
* @package Civi\API\Event
*/
class UnhandledExceptionEvent extends GenericHookEvent {
/**
* @var \Exception
*/
public $exception;
/**
* @var mixed reserved for future use
*/
public $request;
/**
* @param $e
* @param $request
*/
public function __construct($e, $request) {
$this->request = $request;
$this->exception = $e;
}
/**
* @inheritDoc
*/
public function getHookValues() {
return array($this->exception, $this->request);
}
}

View file

@ -0,0 +1,6 @@
<?php
namespace Civi\Core\Exception;
class UnknownAssetException extends \CRM_Core_Exception {
}

View file

@ -0,0 +1,59 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core;
use Civi\Core\Event\SystemInstallEvent;
/**
* Class InstallationCanary
* @package Civi\Core
*/
class InstallationCanary {
/**
* Check whether the install has run before.
*
* Circa v4.7.betaX, we introduced a new mechanism for tracking installation
* and firing a post-install event. However, it's fairly difficult to test the
* edge-cases directly, so this canary should fire if there are any problems
* in the design/implementation of the installation-tracker.
*
* This should not exist. It should be removed in a future version.
*
* @param \Civi\Core\Event\SystemInstallEvent $event
* @throws \CRM_Core_Exception
*/
public static function check(SystemInstallEvent $event) {
if (\CRM_Core_DAO::checkTableExists('civicrm_install_canary')) {
throw new \CRM_Core_Exception("Found installation canary. This suggests that something went wrong with tracking installation process. Please post to forum or JIRA.");
}
\Civi::log()->info('Creating canary table');
\CRM_Core_DAO::executeQuery('CREATE TABLE civicrm_install_canary (id int(10) unsigned NOT NULL) ENGINE=InnoDB');
}
}

View file

@ -0,0 +1,104 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2015 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core;
use Civi;
use Civi\Core\Event\SystemInstallEvent;
/**
* Class LocalizationInitializer
* @package Civi\Core
*/
class LocalizationInitializer {
/**
* Load the locale settings based on the installation language
*
* @param \Civi\Core\Event\SystemInstallEvent $event
* @throws \CRM_Core_Exception
*/
public static function initialize(SystemInstallEvent $event) {
// get the current installation language
global $tsLocale;
$seedLanguage = $tsLocale;
if (!$seedLanguage) {
return;
}
// get the corresponding settings file if any
$localeDir = \CRM_Core_I18n::getResourceDir();
$fileName = $localeDir . $seedLanguage . DIRECTORY_SEPARATOR . 'settings.default.json';
// initalization
$settingsParams = array();
if (file_exists($fileName)) {
// load the file and parse it
$json = file_get_contents($fileName);
$settings = json_decode($json, TRUE);
if (!empty($settings)) {
// get all valid settings
$results = civicrm_api3('Setting', 'getfields', array());
$validSettings = array_keys($results['values']);
// add valid settings to params to send to api
foreach ($settings as $setting => $value) {
if (in_array($setting, $validSettings)) {
$settingsParams[$setting] = $value;
}
}
// ensure we don't mess with multilingual
unset($settingsParams['languageLimit']);
// support for enabled languages (option group)
if (isset($settings['languagesOption']) && count($settings['languagesOption']) > 0) {
\CRM_Core_BAO_OptionGroup::setActiveValues('languages', $settings['languagesOption']);
}
// set default currency in currencies_enabled (option group)
if (isset($settings['defaultCurrency'])) {
\CRM_Admin_Form_Setting_Localization::updateEnabledCurrencies(array($settings['defaultCurrency']), $settings['defaultCurrency']);
}
}
}
// in any case, enforce the seedLanguage as the default language
$settingsParams['lcMessages'] = $seedLanguage;
// apply the config
civicrm_api3('Setting', 'create', $settingsParams);
}
}

View file

@ -0,0 +1,64 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Lock;
/**
* Lock interface.
*/
interface LockInterface {
/**
* Acquire lock.
*
* @param int|NULL $timeout
* The number of seconds to wait to get the lock.
* For a default value, use NULL.
* @return bool
*/
public function acquire($timeout = NULL);
/**
* @return bool|null|string
* Trueish/falsish.
*/
public function release();
/**
* @return bool|null|string
* Trueish/falsish.
* @deprecated
* Not supported by some locking strategies. If you need to poll, better
* to use acquire(0).
*/
public function isFree();
/**
* @return bool
*/
public function isAcquired();
}

View file

@ -0,0 +1,121 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Lock;
use Civi\Core\Resolver;
/**
* Class LockManager
* @package Civi\Core\Lock
*
* The lock-manager allows one to define the lock policy -- i.e. given a
* specific lock, how does one acquire the lock?
*/
class LockManager {
private $rules = array();
/**
* @param string $name
* Symbolic name for the lock. Names generally look like
* "worker.mailing.EmailProcessor" ("{category}.{component}.{AdhocName}").
*
* Categories: worker|data|cache|...
* Component: core|mailing|member|contribute|...
* @return LockInterface
* @throws \CRM_Core_Exception
*/
public function create($name) {
$factory = $this->getFactory($name);
if ($factory) {
/** @var LockInterface $lock */
$lock = call_user_func_array($factory, array($name));
return $lock;
}
else {
throw new \CRM_Core_Exception("Lock \"$name\" does not match any rules. Use register() to add more rules.");
}
}
/**
* Create and attempt to acquire a lock.
*
* Note: Be sure to check $lock->isAcquired() to determine whether
* acquisition was successful.
*
* @param string $name
* Symbolic name for the lock. Names generally look like
* "worker.mailing.EmailProcessor" ("{category}.{component}.{AdhocName}").
*
* Categories: worker|data|cache|...
* Component: core|mailing|member|contribute|...
* @param int|NULL $timeout
* The number of seconds to wait to get the lock.
* For a default value, use NULL.
* @return LockInterface
* @throws \CRM_Core_Exception
*/
public function acquire($name, $timeout = NULL) {
$lock = $this->create($name);
$lock->acquire($timeout);
return $lock;
}
/**
* @param string $name
* Symbolic name for the lock.
* @return callable|NULL
*/
public function getFactory($name) {
foreach ($this->rules as $rule) {
if (preg_match($rule['pattern'], $name)) {
return Resolver::singleton()->get($rule['factory']);
}
}
return NULL;
}
/**
* Register the lock-factory to use for specific lock-names.
*
* @param string $pattern
* A regex to match against the lock name.
* @param string|array $factory
* A callback. The callback should accept a $name parameter.
* Callbacks will be located using the resolver.
* @return LockManager
* @see Resolver
*/
public function register($pattern, $factory) {
$this->rules[] = array(
'pattern' => $pattern,
'factory' => $factory,
);
return $this;
}
}

View file

@ -0,0 +1,92 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Lock;
/**
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class NullLock implements LockInterface {
private $hasLock = FALSE;
/**
* Create lock.
*
* @param string $name
*
* @return static
*/
public static function create($name) {
return new static();
}
/**
* Acquire lock.
*
* @param int|NULL $timeout
* The number of seconds to wait to get the lock.
* For a default value, use NULL.
*
* @return bool
*/
public function acquire($timeout = NULL) {
$this->hasLock = TRUE;
return TRUE;
}
/**
* Release lock.
*
* @return bool|null|string
* Trueish/falsish.
*/
public function release() {
$this->hasLock = FALSE;
return TRUE;
}
/**
* @return bool|null|string
* Trueish/falsish.
* @deprecated
* Not supported by some locking strategies. If you need to poll, better
* to use acquire(0).
*/
public function isFree() {
return !$this->hasLock;
}
/**
* @return bool
*/
public function isAcquired() {
return $this->hasLock;
}
}

View file

@ -0,0 +1,211 @@
<?php
namespace Civi\Core;
/**
* Class Paths
* @package Civi\Core
*
* This paths class translates path-expressions into local file paths and
* URLs. Path-expressions may take a few forms:
*
* - Paths and URLs may use a variable prefix. For example, '[civicrm.files]/upload'
* - Paths and URLS may be absolute.
* - Paths may be relative (base dir: [civicrm.files]).
* - URLs may be relative (base dir: [cms.root]).
*/
class Paths {
const DEFAULT_URL = 'cms.root';
const DEFAULT_PATH = 'civicrm.files';
/**
* @var array
* Array(string $name => array(url => $, path => $)).
*/
private $variables = array();
private $variableFactory = array();
/**
* Class constructor.
*/
public function __construct() {
$paths = $this;
$this
->register('civicrm.root', function () {
return \CRM_Core_Config::singleton()->userSystem->getCiviSourceStorage();
})
->register('civicrm.packages', function () {
return array(
'path' => \Civi::paths()->getPath('[civicrm.root]/packages/'),
'url' => \Civi::paths()->getUrl('[civicrm.root]/packages/'),
);
})
->register('civicrm.vendor', function () {
return array(
'path' => \Civi::paths()->getPath('[civicrm.root]/vendor/'),
'url' => \Civi::paths()->getUrl('[civicrm.root]/vendor/'),
);
})
->register('civicrm.bower', function () {
return array(
'path' => \Civi::paths()->getPath('[civicrm.root]/bower_components/'),
'url' => \Civi::paths()->getUrl('[civicrm.root]/bower_components/'),
);
})
->register('civicrm.files', function () {
return \CRM_Core_Config::singleton()->userSystem->getDefaultFileStorage();
})
->register('wp.frontend.base', function () {
return array('url' => rtrim(CIVICRM_UF_BASEURL, '/') . '/');
})
->register('wp.frontend', function () use ($paths) {
$config = \CRM_Core_Config::singleton();
$suffix = defined('CIVICRM_UF_WP_BASEPAGE') ? CIVICRM_UF_WP_BASEPAGE : $config->wpBasePage;
return array(
'url' => $paths->getVariable('wp.frontend.base', 'url') . $suffix,
);
})
->register('wp.backend.base', function () {
return array('url' => rtrim(CIVICRM_UF_BASEURL, '/') . '/wp-admin/');
})
->register('wp.backend', function () use ($paths) {
return array(
'url' => $paths->getVariable('wp.backend.base', 'url') . 'admin.php',
);
})
->register('cms', function () {
return array(
'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(),
'url' => \CRM_Utils_System::baseCMSURL(),
);
})
->register('cms.root', function () {
return array(
'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(),
// Misleading: this *removes* the language part of the URL, producing a pristine base URL.
'url' => \CRM_Utils_System::languageNegotiationURL(\CRM_Utils_System::baseCMSURL(), FALSE, TRUE),
);
});
}
/**
* Register a new URL/file path mapping.
*
* @param string $name
* The name of the variable.
* @param callable $factory
* Function which returns an array with keys:
* - path: string.
* - url: string.
* @return Paths
*/
public function register($name, $factory) {
$this->variableFactory[$name] = $factory;
return $this;
}
/**
* @param string $name
* Ex: 'civicrm.root'.
* @param string $attr
* Ex: 'url', 'path'.
* @return mixed
*/
public function getVariable($name, $attr) {
if (!isset($this->variables[$name])) {
$this->variables[$name] = call_user_func($this->variableFactory[$name]);
if (isset($GLOBALS['civicrm_paths'][$name])) {
$this->variables[$name] = array_merge($this->variables[$name], $GLOBALS['civicrm_paths'][$name]);
}
}
if (!isset($this->variables[$name][$attr])) {
throw new \RuntimeException("Cannot resolve path using \"$name.$attr\"");
}
return $this->variables[$name][$attr];
}
/**
* Does the variable exist.
*
* @param string $name
*
* @return bool
*/
public function hasVariable($name) {
return isset($this->variableFactory[$name]);
}
/**
* Determine the absolute path to a file, given that the file is most likely
* in a given particular variable.
*
* @param string $value
* The file path.
* Use "." to reference to default file root.
* Values may begin with a variable, e.g. "[civicrm.files]/upload".
* @return mixed|string
*/
public function getPath($value) {
$defaultContainer = self::DEFAULT_PATH;
if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\]/(.*);', $value, $matches)) {
$defaultContainer = $matches[1];
$value = $matches[2];
}
if (empty($value)) {
return FALSE;
}
if ($value === '.') {
$value = '';
}
return \CRM_Utils_File::absoluteDirectory($value, $this->getVariable($defaultContainer, 'path'));
}
/**
* Determine the URL to a file.
*
* @param string $value
* The file path. The path may begin with a variable, e.g. "[civicrm.files]/upload".
* @param string $preferFormat
* The preferred format ('absolute', 'relative').
* The result data may not meet the preference -- if the setting
* refers to an external domain, then the result will be
* absolute (regardless of preference).
* @param bool|NULL $ssl
* NULL to autodetect. TRUE to force to SSL.
* @return mixed|string
*/
public function getUrl($value, $preferFormat = 'relative', $ssl = NULL) {
$defaultContainer = self::DEFAULT_URL;
if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\](/(.*))$;', $value, $matches)) {
$defaultContainer = $matches[1];
$value = empty($matches[3]) ? '.' : $matches[3];
}
if (empty($value)) {
return FALSE;
}
if ($value === '.') {
$value = '';
}
if (substr($value, 0, 4) == 'http') {
return $value;
}
$value = $this->getVariable($defaultContainer, 'url') . $value;
if ($preferFormat === 'relative') {
$parsed = parse_url($value);
if (isset($_SERVER['HTTP_HOST']) && isset($parsed['host']) && $_SERVER['HTTP_HOST'] == $parsed['host']) {
$value = $parsed['path'];
}
}
if ($ssl || ($ssl === NULL && \CRM_Utils_System::isSSL())) {
$value = str_replace('http://', 'https://', $value);
}
return $value;
}
}

View file

@ -0,0 +1,287 @@
<?php
namespace Civi\Core;
/**
* The resolver takes a string expression and returns an object or callable.
*
* The following patterns will resolve to objects:
* - 'obj://objectName' - An object from Civi\Core\Container
* - 'ClassName' - An instance of ClassName (with default constructor).
* If you need more control over construction, then register with the
* container.
*
* The following patterns will resolve to callables:
* - 'function_name' - A function(callable).
* - 'ClassName::methodName" - A static method of a class.
* - 'call://objectName/method' - A method on an object from Civi\Core\Container.
* - 'api3://EntityName/action' - A method call on an API.
* (Performance note: Requires full setup/teardown of API subsystem.)
* - 'api3://EntityName/action?first=@1&second=@2' - Call an API method, mapping the
* first & second args to named parameters.
* (Performance note: Requires parsing/interpolating arguments).
* - 'global://Variable/Key2/Key3?getter' - A dummy which looks up a global variable.
* - 'global://Variable/Key2/Key3?setter' - A dummy which updates a global variable.
* - '0' or '1' - A dummy which returns the constant '0' or '1'.
*
* Note: To differentiate classes and functions, there is a hard requirement that
* class names begin with an uppercase letter.
*
* Note: If you are working in a context which requires a callable, it is legitimate to use
* an object notation ("obj://objectName" or "ClassName") if the object supports __invoke().
*
* @package Civi\Core
*/
class Resolver {
protected static $_singleton;
/**
* Singleton function.
*
* @return Resolver
*/
public static function singleton() {
if (self::$_singleton === NULL) {
self::$_singleton = new Resolver();
}
return self::$_singleton;
}
/**
* Convert a callback expression to a valid PHP callback.
*
* @param string|array $id
* A callback expression; any of the following.
*
* @return array|callable
* A PHP callback. Do not serialize (b/c it may include an object).
* @throws \RuntimeException
*/
public function get($id) {
if (!is_string($id)) {
// An array or object does not need to be further resolved.
return $id;
}
if (strpos($id, '::') !== FALSE) {
// Callback: Static method.
return explode('::', $id);
}
elseif (strpos($id, '://') !== FALSE) {
$url = parse_url($id);
switch ($url['scheme']) {
case 'obj':
// Object: Lookup in container.
return \Civi::service($url['host']);
case 'call':
// Callback: Object/method in container.
$obj = \Civi::service($url['host']);
return array($obj, ltrim($url['path'], '/'));
case 'api3':
// Callback: API.
return new ResolverApi($url);
case 'global':
// Lookup in a global variable.
return new ResolverGlobalCallback($url['query'], $url['host'] . (isset($url['path']) ? rtrim($url['path'], '/') : ''));
default:
throw new \RuntimeException("Unsupported callback scheme: " . $url['scheme']);
}
}
elseif (in_array($id, array('0', '1'))) {
// Callback: Constant value.
return new ResolverConstantCallback((int) $id);
}
elseif ($id{0} >= 'A' && $id{0} <= 'Z') {
// Object: New/default instance.
return new $id();
}
else {
// Callback: Function.
return $id;
}
}
/**
* Invoke a callback expression.
*
* @param string|callable $id
* @param array $args
* Ordered parameters. To call-by-reference, set an array-parameter by reference.
*
* @return mixed
*/
public function call($id, $args) {
$cb = $this->get($id);
return $cb ? call_user_func_array($cb, $args) : NULL;
}
}
/**
* Private helper which produces a dummy callback.
*
* @package Civi\Core
*/
class ResolverConstantCallback {
/**
* @var mixed
*/
private $value;
/**
* Class constructor.
*
* @param mixed $value
* The value to be returned by the dummy callback.
*/
public function __construct($value) {
$this->value = $value;
}
/**
* Invoke function.
*
* @return mixed
*/
public function __invoke() {
return $this->value;
}
}
/**
* Private helper which treats an API as a callable function.
*
* @package Civi\Core
*/
class ResolverApi {
/**
* @var array
* - string scheme
* - string host
* - string path
* - string query (optional)
*/
private $url;
/**
* Class constructor.
*
* @param array $url
* Parsed URL (e.g. "api3://EntityName/action?foo=bar").
*
* @see parse_url
*/
public function __construct($url) {
$this->url = $url;
}
/**
* Fire an API call.
*/
public function __invoke() {
$apiParams = array();
if (isset($this->url['query'])) {
parse_str($this->url['query'], $apiParams);
}
if (count($apiParams)) {
$args = func_get_args();
if (count($args)) {
$this->interpolate($apiParams, $this->createPlaceholders('@', $args));
}
}
$result = civicrm_api3($this->url['host'], ltrim($this->url['path'], '/'), $apiParams);
return isset($result['values']) ? $result['values'] : NULL;
}
/**
* Create placeholders.
*
* @param string $prefix
* @param array $args
* Positional arguments.
*
* @return array
* Named placeholders based on the positional arguments
* (e.g. "@1" => "firstValue").
*/
protected function createPlaceholders($prefix, $args) {
$result = array();
foreach ($args as $offset => $arg) {
$result[$prefix . (1 + $offset)] = $arg;
}
return $result;
}
/**
* Recursively interpolate values.
*
* @code
* $params = array('foo' => '@1');
* $this->interpolate($params, array('@1'=> $object))
* assert $data['foo'] == $object;
* @endcode
*
* @param array $array
* Array which may or many not contain a mix of tokens.
* @param array $replacements
* A list of tokens to substitute.
*/
protected function interpolate(&$array, $replacements) {
foreach (array_keys($array) as $key) {
if (is_array($array[$key])) {
$this->interpolate($array[$key], $replacements);
continue;
}
foreach ($replacements as $oldVal => $newVal) {
if ($array[$key] === $oldVal) {
$array[$key] = $newVal;
}
}
}
}
}
class ResolverGlobalCallback {
private $mode, $path;
/**
* Class constructor.
*
* @param string $mode
* 'getter' or 'setter'.
* @param string $path
*/
public function __construct($mode, $path) {
$this->mode = $mode;
$this->path = $path;
}
/**
* Invoke function.
*
* @param mixed $arg1
*
* @return mixed
*/
public function __invoke($arg1 = NULL) {
if ($this->mode === 'getter') {
return \CRM_Utils_Array::pathGet($GLOBALS, explode('/', $this->path));
}
elseif ($this->mode === 'setter') {
\CRM_Utils_Array::pathSet($GLOBALS, explode('/', $this->path), $arg1);
return NULL;
}
else {
throw new \RuntimeException("Resolver failed: global:// must specify getter or setter mode.");
}
}
}

View file

@ -0,0 +1,403 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core;
/**
* Class SettingsBag
* @package Civi\Core
*
* Read and write settings for a given domain (or contact).
*
* If the target entity does not already have a value for the setting, then
* the defaults will be used. If mandatory values are provided, they will
* override any defaults or custom settings.
*
* It's expected that the SettingsBag will have O(50-250) settings -- and that
* we'll load the full bag on many page requests. Consequently, we don't
* want the full metadata (help text and version history and HTML widgets)
* for all 250 settings, but we do need the default values.
*
* This class is not usually instantiated directly. Instead, use SettingsManager
* or Civi::settings().
*
* @see \Civi::settings()
* @see SettingsManagerTest
*/
class SettingsBag {
protected $domainId;
protected $contactId;
/**
* @var array
* Array(string $settingName => mixed $value).
*/
protected $defaults;
/**
* @var array
* Array(string $settingName => mixed $value).
*/
protected $mandatory;
/**
* The result of combining default values, mandatory
* values, and user values.
*
* @var array|NULL
* Array(string $settingName => mixed $value).
*/
protected $combined;
/**
* @var array
*/
protected $values;
/**
* @param int $domainId
* The domain for which we want settings.
* @param int|NULL $contactId
* The contact for which we want settings. Use NULL for domain settings.
*/
public function __construct($domainId, $contactId) {
$this->domainId = $domainId;
$this->contactId = $contactId;
$this->values = array();
$this->combined = NULL;
}
/**
* Set/replace the default values.
*
* @param array $defaults
* Array(string $settingName => mixed $value).
* @return SettingsBag
*/
public function loadDefaults($defaults) {
$this->defaults = $defaults;
$this->combined = NULL;
return $this;
}
/**
* Set/replace the mandatory values.
*
* @param array $mandatory
* Array(string $settingName => mixed $value).
* @return SettingsBag
*/
public function loadMandatory($mandatory) {
$this->mandatory = $mandatory;
$this->combined = NULL;
return $this;
}
/**
* Load all explicit settings that apply to this domain or contact.
*
* @return SettingsBag
*/
public function loadValues() {
// Note: Don't use DAO child classes. They require fields() which require
// translations -- which are keyed off settings!
$this->values = array();
$this->combined = NULL;
// Ordinarily, we just load values from `civicrm_setting`. But upgrades require care.
// In v4.0 and earlier, all values were stored in `civicrm_domain.config_backend`.
// In v4.1-v4.6, values were split between `civicrm_domain` and `civicrm_setting`.
// In v4.7+, all values are stored in `civicrm_setting`.
// Whenever a value is available in civicrm_setting, it will take precedence.
$isUpgradeMode = \CRM_Core_Config::isUpgradeMode();
if ($isUpgradeMode && empty($this->contactId) && \CRM_Core_DAO::checkFieldExists('civicrm_domain', 'config_backend', FALSE)) {
$config_backend = \CRM_Core_DAO::singleValueQuery('SELECT config_backend FROM civicrm_domain WHERE id = %1',
array(1 => array($this->domainId, 'Positive')));
$oldSettings = \CRM_Upgrade_Incremental_php_FourSeven::convertBackendToSettings($this->domainId, $config_backend);
\CRM_Utils_Array::extend($this->values, $oldSettings);
}
// Normal case. Aside: Short-circuit prevents unnecessary query.
if (!$isUpgradeMode || \CRM_Core_DAO::checkTableExists('civicrm_setting')) {
$dao = \CRM_Core_DAO::executeQuery($this->createQuery()->toSQL());
while ($dao->fetch()) {
$this->values[$dao->name] = ($dao->value !== NULL) ? unserialize($dao->value) : NULL;
}
}
return $this;
}
/**
* Add a batch of settings. Save them.
*
* @param array $settings
* Array(string $settingName => mixed $settingValue).
* @return SettingsBag
*/
public function add(array $settings) {
foreach ($settings as $key => $value) {
$this->set($key, $value);
}
return $this;
}
/**
* Get a list of all effective settings.
*
* @return array
* Array(string $settingName => mixed $settingValue).
*/
public function all() {
if ($this->combined === NULL) {
$this->combined = $this->combine(
array($this->defaults, $this->values, $this->mandatory)
);
}
return $this->combined;
}
/**
* Determine the effective value.
*
* @param string $key
* @return mixed
*/
public function get($key) {
$all = $this->all();
return isset($all[$key]) ? $all[$key] : NULL;
}
/**
* Determine the default value of a setting.
*
* @param string $key
* The simple name of the setting.
* @return mixed|NULL
*/
public function getDefault($key) {
return isset($this->defaults[$key]) ? $this->defaults[$key] : NULL;
}
/**
* Determine the explicitly designated value, regardless of
* any default or mandatory values.
*
* @param string $key
* The simple name of the setting.
* @return mixed|NULL
*/
public function getExplicit($key) {
return (isset($this->values[$key]) ? $this->values[$key] : NULL);
}
/**
* Determine the mandatory value of a setting.
*
* @param string $key
* The simple name of the setting.
* @return mixed|NULL
*/
public function getMandatory($key) {
return isset($this->mandatory[$key]) ? $this->mandatory[$key] : NULL;
}
/**
* Determine if the entity has explicitly designated a value.
*
* Note that get() may still return other values based on
* mandatory values or defaults.
*
* @param string $key
* The simple name of the setting.
* @return bool
*/
public function hasExplict($key) {
// NULL means no designated value.
return isset($this->values[$key]);
}
/**
* Removes any explicit settings. This restores the default.
*
* @param string $key
* The simple name of the setting.
* @return SettingsBag
*/
public function revert($key) {
// It might be better to DELETE (to avoid long-term leaks),
// but setting NULL is simpler for now.
return $this->set($key, NULL);
}
/**
* Add a single setting. Save it.
*
* @param string $key
* The simple name of the setting.
* @param mixed $value
* The new, explicit value of the setting.
* @return SettingsBag
*/
public function set($key, $value) {
$this->setDb($key, $value);
$this->values[$key] = $value;
$this->combined = NULL;
return $this;
}
/**
* @return \CRM_Utils_SQL_Select
*/
protected function createQuery() {
$select = \CRM_Utils_SQL_Select::from('civicrm_setting')
->select('id, name, value, domain_id, contact_id, is_domain, component_id, created_date, created_id')
->where('domain_id = #id', array(
'id' => $this->domainId,
));
if ($this->contactId === NULL) {
$select->where('is_domain = 1');
}
else {
$select->where('contact_id = #id', array(
'id' => $this->contactId,
));
$select->where('is_domain = 0');
}
return $select;
}
/**
* Combine a series of arrays, excluding any
* null values. Later values override earlier
* values.
*
* @param array $arrays
* List of arrays to combine.
* @return array
*/
protected function combine($arrays) {
$combined = array();
foreach ($arrays as $array) {
foreach ($array as $k => $v) {
if ($v !== NULL) {
$combined[$k] = $v;
}
}
}
return $combined;
}
/**
* Update the DB record for this setting.
*
* @param string $name
* The simple name of the setting.
* @param mixed $value
* The new value of the setting.
*/
protected function setDb($name, $value) {
if (\CRM_Core_BAO_Setting::isUpgradeFromPreFourOneAlpha1()) {
// civicrm_setting table is not going to be present.
return;
}
$fields = array();
$fieldsToSet = \CRM_Core_BAO_Setting::validateSettingsInput(array($name => $value), $fields);
//We haven't traditionally validated inputs to setItem, so this breaks things.
//foreach ($fieldsToSet as $settingField => &$settingValue) {
// self::validateSetting($settingValue, $fields['values'][$settingField]);
//}
$metadata = $fields['values'][$name];
$dao = new \CRM_Core_DAO_Setting();
$dao->name = $name;
$dao->domain_id = $this->domainId;
if ($this->contactId) {
$dao->contact_id = $this->contactId;
$dao->is_domain = 0;
}
else {
$dao->is_domain = 1;
}
$dao->find(TRUE);
// Call 'on_change' listeners. It would be nice to only fire when there's
// a genuine change in the data. However, PHP developers have mixed
// expectations about whether 0, '0', '', NULL, and FALSE represent the same
// value, so there's no universal way to determine if a change is genuine.
if (isset($metadata['on_change'])) {
foreach ($metadata['on_change'] as $callback) {
call_user_func(
\Civi\Core\Resolver::singleton()->get($callback),
unserialize($dao->value),
$value,
$metadata,
$this->domainId
);
}
}
if (!is_array($value) && \CRM_Utils_System::isNull($value)) {
$dao->value = 'null';
}
else {
$dao->value = serialize($value);
}
if (!isset(\Civi::$statics[__CLASS__]['upgradeMode'])) {
\Civi::$statics[__CLASS__]['upgradeMode'] = \CRM_Core_Config::isUpgradeMode();
}
if (\Civi::$statics[__CLASS__]['upgradeMode'] && \CRM_Core_DAO::checkFieldExists('civicrm_setting', 'group_name')) {
$dao->group_name = 'placeholder';
}
$dao->created_date = \CRM_Utils_Time::getTime('YmdHis');
$session = \CRM_Core_Session::singleton();
if (\CRM_Contact_BAO_Contact_Utils::isContactId($session->get('userID'))) {
$dao->created_id = $session->get('userID');
}
if ($dao->id) {
$dao->save();
}
else {
// Cannot use $dao->save(); in upgrade mode (eg WP + Civi 4.4=>4.7), the DAO will refuse
// to save the field `group_name`, which is required in older schema.
\CRM_Core_DAO::executeQuery(\CRM_Utils_SQL_Insert::dao($dao)->toSQL());
}
$dao->free();
}
}

View file

@ -0,0 +1,354 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core;
/**
* Class SettingsManager
* @package Civi\Core
*
* The SettingsManager is responsible for tracking settings across various
* domains and users.
*
* Generally, for any given setting, there are three levels where values
* can be declared:
*
* - Mandatory values (which come from a global $civicrm_setting).
* - Explicit values (which are chosen by the user and stored in the DB).
* - Default values (which come from the settings metadata).
*
* Note: During the early stages of bootstrap, default values are not be available.
* Loading the defaults requires loading metadata from various sources. However,
* near the end of bootstrap, one calls SettingsManager::useDefaults() to fetch
* and merge the defaults.
*
* Note: In a typical usage, there will only be one active domain and one
* active contact (each having its own bag) within a given request. However,
* in some edge-cases, you may need to work with multiple domains/contacts
* at the same time.
*
* Note: The global $civicrm_setting is meant to provide sysadmins with a way
* to override settings in `civicrm.settings.php`, but it has traditionally been
* possible for extensions to manipulate $civicrm_setting in a hook. If you do
* this, please call `useMandatory()` to tell SettingsManager to re-scan
* $civicrm_setting.
*
* @see SettingsManagerTest
*/
class SettingsManager {
/**
* @var \CRM_Utils_Cache_Interface
*/
protected $cache;
/**
* @var
* Array (int $id => SettingsBag $bag).
*/
protected $bagsByDomain = array(), $bagsByContact = array();
/**
* @var array|NULL
* Array(string $entity => array(string $settingName => mixed $value)).
* Ex: $mandatory['domain']['uploadDir'].
* NULL means "autoload from $civicrm_setting".
*/
protected $mandatory = NULL;
/**
* Whether to use defaults.
*
* @var bool
*/
protected $useDefaults = FALSE;
/**
* @param \CRM_Utils_Cache_Interface $cache
* A semi-durable location to store metadata.
*/
public function __construct($cache) {
$this->cache = $cache;
}
/**
* Ensure that all defaults values are included with
* all current and future bags.
*
* @return SettingsManager
*/
public function useDefaults() {
if (!$this->useDefaults) {
$this->useDefaults = TRUE;
if (!empty($this->bagsByDomain)) {
foreach ($this->bagsByDomain as $bag) {
/** @var SettingsBag $bag */
$bag->loadDefaults($this->getDefaults('domain'));
}
}
if (!empty($this->bagsByContact)) {
foreach ($this->bagsByContact as $bag) {
/** @var SettingsBag $bag */
$bag->loadDefaults($this->getDefaults('contact'));
}
}
}
return $this;
}
/**
* Ensure that mandatory values are included with
* all current and future bags.
*
* If you call useMandatory multiple times, it will
* re-scan the global $civicrm_setting.
*
* @return SettingsManager
*/
public function useMandatory() {
$this->mandatory = NULL;
foreach ($this->bagsByDomain as $bag) {
/** @var SettingsBag $bag */
$bag->loadMandatory($this->getMandatory('domain'));
}
foreach ($this->bagsByContact as $bag) {
/** @var SettingsBag $bag */
$bag->loadMandatory($this->getMandatory('contact'));
}
return $this;
}
/**
* @param int|NULL $domainId
* @return SettingsBag
*/
public function getBagByDomain($domainId) {
if ($domainId === NULL) {
$domainId = \CRM_Core_Config::domainID();
}
if (!isset($this->bagsByDomain[$domainId])) {
$this->bagsByDomain[$domainId] = new SettingsBag($domainId, NULL);
if (\CRM_Core_Config::singleton()->dsn) {
$this->bagsByDomain[$domainId]->loadValues();
}
$this->bagsByDomain[$domainId]
->loadMandatory($this->getMandatory('domain'))
->loadDefaults($this->getDefaults('domain'));
}
return $this->bagsByDomain[$domainId];
}
/**
* @param int|NULL $domainId
* @param int|NULL $contactId
* @return SettingsBag
*/
public function getBagByContact($domainId, $contactId) {
if ($domainId === NULL) {
$domainId = \CRM_Core_Config::domainID();
}
$key = "$domainId:$contactId";
if (!isset($this->bagsByContact[$key])) {
$this->bagsByContact[$key] = new SettingsBag($domainId, $contactId);
if (\CRM_Core_Config::singleton()->dsn) {
$this->bagsByContact[$key]->loadValues();
}
$this->bagsByContact[$key]
->loadDefaults($this->getDefaults('contact'))
->loadMandatory($this->getMandatory('contact'));
}
return $this->bagsByContact[$key];
}
/**
* Determine the default settings.
*
* @param string $entity
* Ex: 'domain' or 'contact'.
* @return array
* Array(string $settingName => mixed $value).
*/
protected function getDefaults($entity) {
if (!$this->useDefaults) {
return self::getSystemDefaults($entity);
}
$cacheKey = 'defaults:' . $entity;
$defaults = $this->cache->get($cacheKey);
if (!is_array($defaults)) {
$specs = SettingsMetadata::getMetadata(array(
'is_contact' => ($entity === 'contact' ? 1 : 0),
));
$defaults = array();
foreach ($specs as $key => $spec) {
$defaults[$key] = \CRM_Utils_Array::value('default', $spec);
}
\CRM_Utils_Array::extend($defaults, self::getSystemDefaults($entity));
$this->cache->set($cacheKey, $defaults);
}
return $defaults;
}
/**
* Get a list of mandatory/overriden settings.
*
* @param string $entity
* Ex: 'domain' or 'contact'.
* @return array
* Array(string $settingName => mixed $value).
*/
protected function getMandatory($entity) {
if ($this->mandatory === NULL) {
$this->mandatory = self::parseMandatorySettings(\CRM_Utils_Array::value('civicrm_setting', $GLOBALS));
}
return $this->mandatory[$entity];
}
/**
* Parse mandatory settings.
*
* In previous versions, settings were broken down into verbose+dynamic group names, e.g.
*
* $civicrm_settings['Foo Bar Preferences']['foo'] = 'bar';
*
* We now simplify to two simple groups, 'domain' and 'contact'.
*
* $civicrm_settings['domain']['foo'] = 'bar';
*
* However, the old groups are grand-fathered in as aliases.
*
* @param array $civicrm_setting
* Ex: $civicrm_setting['Group Name']['field'] = 'value'.
* Group names are an historical quirk; ignore them.
* @return array
*/
public static function parseMandatorySettings($civicrm_setting) {
$result = array(
'domain' => array(),
'contact' => array(),
);
$rewriteGroups = array(
//\CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::CAMPAIGN_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::EVENT_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::MEMBER_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::MULTISITE_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::PERSONAL_PREFERENCES_NAME => 'contact',
'Personal Preferences' => 'contact',
//\CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME => 'domain',
//\CRM_Core_BAO_Setting::URL_PREFERENCES_NAME => 'domain',
'domain' => 'domain',
'contact' => 'contact',
);
if (is_array($civicrm_setting)) {
foreach ($civicrm_setting as $oldGroup => $values) {
$newGroup = isset($rewriteGroups[$oldGroup]) ? $rewriteGroups[$oldGroup] : 'domain';
$result[$newGroup] = array_merge($result[$newGroup], $values);
}
}
return $result;
}
/**
* Flush all in-memory and persistent caches related to settings.
*
* @return SettingsManager
*/
public function flush() {
$this->mandatory = NULL;
$this->cache->flush();
\Civi::cache('settings')->flush(); // SettingsMetadata; not guaranteed to use same cache.
foreach ($this->bagsByDomain as $bag) {
/** @var SettingsBag $bag */
$bag->loadValues();
$bag->loadDefaults($this->getDefaults('domain'));
$bag->loadMandatory($this->getMandatory('domain'));
}
foreach ($this->bagsByContact as $bag) {
/** @var SettingsBag $bag */
$bag->loadValues();
$bag->loadDefaults($this->getDefaults('contact'));
$bag->loadMandatory($this->getMandatory('contact'));
}
return $this;
}
/**
* Get a list of critical system defaults.
*
* The setting system can be modified by extensions, which means that it's not fully available
* during bootstrap -- in particular, defaults cannot be loaded. For a very small number of settings,
* we must define defaults before the system bootstraps.
*
* @param string $entity
*
* @return array
*/
private static function getSystemDefaults($entity) {
$defaults = array();
switch ($entity) {
case 'domain':
$defaults = array(
'installed' => FALSE,
'enable_components' => array('CiviEvent', 'CiviContribute', 'CiviMember', 'CiviMail', 'CiviReport', 'CiviPledge'),
'customFileUploadDir' => '[civicrm.files]/custom/',
'imageUploadDir' => '[civicrm.files]/persist/contribute/',
'uploadDir' => '[civicrm.files]/upload/',
'imageUploadURL' => '[civicrm.files]/persist/contribute/',
'extensionsDir' => '[civicrm.files]/ext/',
'extensionsURL' => '[civicrm.files]/ext/',
'resourceBase' => '[civicrm.root]/',
'userFrameworkResourceURL' => '[civicrm.root]/',
);
break;
}
return $defaults;
}
}

View file

@ -0,0 +1,165 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core;
/**
* Class SettingsMetadata
* @package Civi\Core
*/
class SettingsMetadata {
const ALL = 'all';
/**
* WARNING: This interface may change.
*
* This provides information about the setting - similar to the fields concept for DAO information.
* As the setting is serialized code creating validation setting input needs to know the data type
* This also helps move information out of the form layer into the data layer where people can interact with
* it via the API or other mechanisms. In order to keep this consistent it is important the form layer
* also leverages it.
*
* Note that this function should never be called when using the runtime getvalue function. Caching works
* around the expectation it will be called during setting administration
*
* Function is intended for configuration rather than runtime access to settings
*
* The following params will filter the result. If none are passed all settings will be returns
*
* @param array $filters
* @param int $domainID
*
* @return array
* the following information as appropriate for each setting
* - name
* - type
* - default
* - add (CiviCRM version added)
* - is_domain
* - is_contact
* - description
* - help_text
*/
public static function getMetadata($filters = array(), $domainID = NULL) {
if ($domainID === NULL) {
$domainID = \CRM_Core_Config::domainID();
}
$cache = \Civi::cache('settings');
$cacheString = 'settingsMetadata_' . $domainID . '_';
// the caching into 'All' seems to be a duplicate of caching to
// settingsMetadata__ - I think the reason was to cache all settings as defined & then those altered by a hook
$settingsMetadata = $cache->get($cacheString);
$cached = is_array($settingsMetadata);
if (!$cached) {
$settingsMetadata = $cache->get(self::ALL);
if (empty($settingsMetadata)) {
global $civicrm_root;
$metaDataFolders = array($civicrm_root . '/settings');
\CRM_Utils_Hook::alterSettingsFolders($metaDataFolders);
$settingsMetadata = self::loadSettingsMetaDataFolders($metaDataFolders);
$cache->set(self::ALL, $settingsMetadata);
}
}
\CRM_Utils_Hook::alterSettingsMetaData($settingsMetadata, $domainID, NULL);
if (!$cached) {
$cache->set($cacheString, $settingsMetadata);
}
self::_filterSettingsSpecification($filters, $settingsMetadata);
return $settingsMetadata;
}
/**
* Load the settings files defined in a series of folders.
* @param array $metaDataFolders
* List of folder paths.
* @return array
*/
protected static function loadSettingsMetaDataFolders($metaDataFolders) {
$settingsMetadata = array();
$loadedFolders = array();
foreach ($metaDataFolders as $metaDataFolder) {
$realFolder = realpath($metaDataFolder);
if (is_dir($realFolder) && !isset($loadedFolders[$realFolder])) {
$loadedFolders[$realFolder] = TRUE;
$settingsMetadata = $settingsMetadata + self::loadSettingsMetadata($metaDataFolder);
}
}
return $settingsMetadata;
}
/**
* Load up settings metadata from files.
*
* @param array $metaDataFolder
*
* @return array
*/
protected static function loadSettingsMetadata($metaDataFolder) {
$settingMetaData = array();
$settingsFiles = \CRM_Utils_File::findFiles($metaDataFolder, '*.setting.php');
foreach ($settingsFiles as $file) {
$settings = include $file;
$settingMetaData = array_merge($settingMetaData, $settings);
}
return $settingMetaData;
}
/**
* Filter the settings metadata according to filters passed in. This is a convenience filter
* and allows selective reverting / filling of settings
*
* @param array $filters
* Filters to match against data.
* @param array $settingSpec
* Metadata to filter.
*/
protected static function _filterSettingsSpecification($filters, &$settingSpec) {
if (empty($filters)) {
return;
}
elseif (array_keys($filters) == array('name')) {
$settingSpec = array($filters['name'] => \CRM_Utils_Array::value($filters['name'], $settingSpec, ''));
return;
}
else {
foreach ($settingSpec as $field => $fieldValues) {
if (array_intersect_assoc($fieldValues, $filters) != $filters) {
unset($settingSpec[$field]);
}
}
return;
}
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Civi\Core;
/**
* Class SettingsStack
*
* The settings stack allows you to temporarily change (then restore) settings. It's intended
* primarily for use in testing.
*
* Like the global `$civicrm_setting` variable, it works best with typical inert settings that
* do not trigger extra activation logic. A handful of settings (such as `enable_components`
* and ~5 others) should be avoided, but most settings should work.
*
* @package Civi\Core
*/
class SettingsStack {
/**
* @var array
* Ex: $stack[0] == ['settingName', 'oldSettingValue'];
*/
protected $stack = array();
/**
* Temporarily apply a setting.
*
* @param $settingValue
* @param $setting
*/
public function push($setting, $settingValue) {
if (isset($GLOBALS['civicrm_setting']['domain'][$setting])) {
$this->stack[] = array($setting, $GLOBALS['civicrm_setting']['domain'][$setting]);
}
else {
$this->stack[] = array($setting, NULL);
}
$GLOBALS['civicrm_setting']['domain'][$setting] = $settingValue;
\Civi::service('settings_manager')->useMandatory();
}
/**
* Restore original settings.
*/
public function popAll() {
while ($frame = array_pop($this->stack)) {
list($setting, $value) = $frame;
if ($value === NULL) {
unset($GLOBALS['civicrm_setting']['domain'][$setting]);
}
else {
$GLOBALS['civicrm_setting']['domain'][$setting] = $value;
}
}
\Civi::service('settings_manager')->useMandatory();
}
}

View file

@ -0,0 +1,127 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\SqlTrigger;
/**
* Build a set of simple, literal SQL triggers.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class StaticTriggers {
/**
* @var array
* A list of triggers, in the same format as hook_civicrm_triggerInfo.
* Additionally, you may specify `upgrade_check` to ensure that the trigger
* is *not* installed during early upgrade steps (before key dependencies are met).
*
* Ex: $triggers[0]['upgrade_check'] = array('table' => 'civicrm_case', 'column'=> 'modified_date');
*
* @see \CRM_Utils_Hook::triggerInfo
*/
private $triggers;
/**
* StaticTriggers constructor.
* @param $triggers
*/
public function __construct($triggers) {
$this->triggers = $triggers;
}
/**
* Add our list of triggers to the global list.
*
* @param \Civi\Core\Event\GenericHookEvent $e
* @see \CRM_Utils_Hook::triggerInfo
*/
public function onTriggerInfo($e) {
$this->alterTriggerInfo($e->info, $e->tableName);
}
/**
* Add our list of triggers to the global list.
*
* @see \CRM_Utils_Hook::triggerInfo
* @see \CRM_Core_DAO::triggerRebuild
*
* @param array $info
* See hook_civicrm_triggerInfo.
* @param string|NULL $tableFilter
* See hook_civicrm_triggerInfo.
*/
public function alterTriggerInfo(&$info, $tableFilter = NULL) {
foreach ($this->getTriggers() as $trigger) {
if ($tableFilter !== NULL) {
// Because sadism.
if (in_array($tableFilter, (array) $trigger['table'])) {
$trigger['table'] = $tableFilter;
}
}
if (\CRM_Core_Config::isUpgradeMode() && isset($trigger['upgrade_check'])) {
$uc = $trigger['upgrade_check'];
if (!\CRM_Core_DAO::checkFieldExists($uc['table'], $uc['column'])
) {
continue;
}
}
unset($trigger['upgrade_check']);
$info[] = $trigger;
}
}
/**
* @return mixed
*/
public function getTriggers() {
return $this->triggers;
}
/**
* @param mixed $triggers
* @return StaticTriggers
*/
public function setTriggers($triggers) {
$this->triggers = $triggers;
return $this;
}
/**
* @param $trigger
* @return StaticTriggers
*/
public function addTrigger($trigger) {
$this->triggers[] = $trigger;
return $this;
}
}

View file

@ -0,0 +1,334 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\SqlTrigger;
use Civi\Core\Event\GenericHookEvent;
/**
* Build a set of SQL triggers for tracking timestamps on an entity.
*
* This class is a generalization of CRM-10554 with the aim of enabling CRM-20958.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class TimestampTriggers {
/**
* @var string
* SQL table name.
* Ex: 'civicrm_contact', 'civicrm_activity'.
*/
private $tableName;
/**
* @var string
* An entity name (from civicrm_custom_group.extends).
* Ex: 'Contact', 'Activity'.
*/
private $customDataEntity;
/**
* @var string
* SQL column name.
* Ex: 'created_date'.
*/
private $createdDate;
/**
* @var string
* SQL column name.
* Ex: 'modified_date'.
*/
private $modifiedDate;
/**
* @var array
* Ex: $relations[0] == array('table' => 'civicrm_bar', 'column' => 'foo_id');
*/
private $relations;
/**
* @param string $tableName
* SQL table name.
* Ex: 'civicrm_contact', 'civicrm_activity'.
* @param string $customDataEntity
* An entity name (from civicrm_custom_group.extends).
* Ex: 'Contact', 'Activity'.
* @return TimestampTriggers
*/
public static function create($tableName, $customDataEntity) {
return new static($tableName, $customDataEntity);
}
/**
* TimestampTriggers constructor.
*
* @param string $tableName
* SQL table name.
* Ex: 'civicrm_contact', 'civicrm_activity'.
* @param string $customDataEntity
* An entity name (from civicrm_custom_group.extends).
* Ex: 'Contact', 'Activity'.
* @param string $createdDate
* SQL column name.
* Ex: 'created_date'.
* @param string $modifiedDate
* SQL column name.
* Ex: 'modified_date'.
* @param array $relations
* Ex: $relations[0] == array('table' => 'civicrm_bar', 'column' => 'foo_id');
*/
public function __construct(
$tableName,
$customDataEntity,
$createdDate = 'created_date',
$modifiedDate = 'modified_date',
$relations = array()
) {
$this->tableName = $tableName;
$this->customDataEntity = $customDataEntity;
$this->createdDate = $createdDate;
$this->modifiedDate = $modifiedDate;
$this->relations = $relations;
}
/**
* Add our list of triggers to the global list.
*
* @param \Civi\Core\Event\GenericHookEvent $e
* @see \CRM_Utils_Hook::triggerInfo
*/
public function onTriggerInfo($e) {
$this->alterTriggerInfo($e->info, $e->tableName);
}
/**
* Add our list of triggers to the global list.
*
* @see \CRM_Utils_Hook::triggerInfo
* @see \CRM_Core_DAO::triggerRebuild
*
* @param array $info
* See hook_civicrm_triggerInfo.
* @param string|NULL $tableFilter
* See hook_civicrm_triggerInfo.
*/
public function alterTriggerInfo(&$info, $tableFilter = NULL) {
// If we haven't upgraded yet, then the created_date/modified_date may not exist.
// In the past, this was a version-based check, but checkFieldExists()
// seems more robust.
if (\CRM_Core_Config::isUpgradeMode()) {
if (!\CRM_Core_DAO::checkFieldExists($this->getTableName(),
$this->getCreatedDate())
) {
return;
}
}
if ($tableFilter == NULL || $tableFilter == $this->getTableName()) {
$info[] = array(
'table' => array($this->getTableName()),
'when' => 'BEFORE',
'event' => array('INSERT'),
'sql' => "\nSET NEW.{$this->getCreatedDate()} = CURRENT_TIMESTAMP;\n",
);
}
// Update timestamp when modifying closely related tables
$relIdx = \CRM_Utils_Array::index(
array('column', 'table'),
$this->getAllRelations()
);
foreach ($relIdx as $column => $someRelations) {
$this->generateTimestampTriggers($info, $tableFilter,
array_keys($someRelations), $column);
}
}
/**
* Generate triggers to update the timestamp.
*
* The corresponding civicrm_FOO row is updated on insert/update/delete
* to a table that extends civicrm_FOO.
* Don't regenerate triggers for all such tables if only asked for one table.
*
* @param array $info
* Reference to the array where generated trigger information is being stored
* @param string|null $tableFilter
* Name of the table for which triggers are being generated, or NULL if all tables
* @param array $relatedTableNames
* Array of all core or all custom table names extending civicrm_FOO
* @param string $contactRefColumn
* 'contact_id' if processing core tables, 'entity_id' if processing custom tables
*
* @link https://issues.civicrm.org/jira/browse/CRM-15602
* @see triggerInfo
*/
public function generateTimestampTriggers(
&$info,
$tableFilter,
$relatedTableNames,
$contactRefColumn
) {
// Safety
$contactRefColumn = \CRM_Core_DAO::escapeString($contactRefColumn);
// If specific related table requested, just process that one.
// (Reply: This feels fishy.)
if (in_array($tableFilter, $relatedTableNames)) {
$relatedTableNames = array($tableFilter);
}
// If no specific table requested (include all related tables),
// or a specific related table requested (as matched above)
if (empty($tableFilter) || isset($relatedTableNames[$tableFilter])) {
$info[] = array(
'table' => $relatedTableNames,
'when' => 'AFTER',
'event' => array('INSERT', 'UPDATE'),
'sql' => "\nUPDATE {$this->getTableName()} SET {$this->getModifiedDate()} = CURRENT_TIMESTAMP WHERE id = NEW.$contactRefColumn;\n",
);
$info[] = array(
'table' => $relatedTableNames,
'when' => 'AFTER',
'event' => array('DELETE'),
'sql' => "\nUPDATE {$this->getTableName()} SET {$this->getModifiedDate()} = CURRENT_TIMESTAMP WHERE id = OLD.$contactRefColumn;\n",
);
}
}
/**
* @return string
*/
public function getTableName() {
return $this->tableName;
}
/**
* @param string $tableName
* @return TimestampTriggers
*/
public function setTableName($tableName) {
$this->tableName = $tableName;
return $this;
}
/**
* @return string
*/
public function getCustomDataEntity() {
return $this->customDataEntity;
}
/**
* @param string $customDataEntity
* @return TimestampTriggers
*/
public function setCustomDataEntity($customDataEntity) {
$this->customDataEntity = $customDataEntity;
return $this;
}
/**
* @return string
*/
public function getCreatedDate() {
return $this->createdDate;
}
/**
* @param string $createdDate
* @return TimestampTriggers
*/
public function setCreatedDate($createdDate) {
$this->createdDate = $createdDate;
return $this;
}
/**
* @return string
*/
public function getModifiedDate() {
return $this->modifiedDate;
}
/**
* @param string $modifiedDate
* @return TimestampTriggers
*/
public function setModifiedDate($modifiedDate) {
$this->modifiedDate = $modifiedDate;
return $this;
}
/**
* @return array
* Each item is an array('table' => string, 'column' => string)
*/
public function getRelations() {
return $this->relations;
}
/**
* @param array $relations
* @return TimestampTriggers
*/
public function setRelations($relations) {
$this->relations = $relations;
return $this;
}
/**
* Get a list of all tracked relations.
*
* This is basically the curated list (`$this->relations`) plus any custom data.
*
* @return array
* Each item is an array('table' => string, 'column' => string)
*/
public function getAllRelations() {
$relations = $this->getRelations();
if ($this->getCustomDataEntity()) {
$customGroupDAO = \CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity($this->getCustomDataEntity());
$customGroupDAO->is_multiple = 0;
$customGroupDAO->find();
while ($customGroupDAO->fetch()) {
$relations[] = array(
'table' => $customGroupDAO->table_name,
'column' => 'entity_id',
);
}
}
return $relations;
}
}

View file

@ -0,0 +1,241 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core;
/**
* Class SqlTriggers
* @package Civi\Core
*
* This class manages creation and destruction of SQL triggers.
*/
class SqlTriggers {
/**
* The name of the output file.
*
* @var string|NULL
*/
private $file = NULL;
/**
* Build a list of triggers via hook and add them to (err, reconcile them
* with) the database.
*
* @param string $tableName
* the specific table requiring a rebuild; or NULL to rebuild all tables.
* @param bool $force
*
* @see CRM-9716
*/
public function rebuild($tableName = NULL, $force = FALSE) {
$info = array();
$logging = new \CRM_Logging_Schema();
$logging->triggerInfo($info, $tableName, $force);
\CRM_Core_I18n_Schema::triggerInfo($info, $tableName);
\CRM_Contact_BAO_Contact::triggerInfo($info, $tableName);
\CRM_Utils_Hook::triggerInfo($info, $tableName);
// drop all existing triggers on all tables
$logging->dropTriggers($tableName);
// now create the set of new triggers
$this->createTriggers($info, $tableName);
}
/**
* @param array $info
* per hook_civicrm_triggerInfo.
* @param string $onlyTableName
* the specific table requiring a rebuild; or NULL to rebuild all tables.
*/
public function createTriggers(&$info, $onlyTableName = NULL) {
// Validate info array, should probably raise errors?
if (is_array($info) == FALSE) {
return;
}
$triggers = array();
// now enumerate the tables and the events and collect the same set in a different format
foreach ($info as $value) {
// clean the incoming data, skip malformed entries
// TODO: malformed entries should raise errors or get logged.
if (isset($value['table']) == FALSE ||
isset($value['event']) == FALSE ||
isset($value['when']) == FALSE ||
isset($value['sql']) == FALSE
) {
continue;
}
if (is_string($value['table']) == TRUE) {
$tables = array($value['table']);
}
else {
$tables = $value['table'];
}
if (is_string($value['event']) == TRUE) {
$events = array(strtolower($value['event']));
}
else {
$events = array_map('strtolower', $value['event']);
}
$whenName = strtolower($value['when']);
foreach ($tables as $tableName) {
if (!isset($triggers[$tableName])) {
$triggers[$tableName] = array();
}
foreach ($events as $eventName) {
$template_params = array('{tableName}', '{eventName}');
$template_values = array($tableName, $eventName);
$sql = str_replace($template_params,
$template_values,
$value['sql']
);
$variables = str_replace($template_params,
$template_values,
\CRM_Utils_Array::value('variables', $value)
);
if (!isset($triggers[$tableName][$eventName])) {
$triggers[$tableName][$eventName] = array();
}
if (!isset($triggers[$tableName][$eventName][$whenName])) {
// We're leaving out cursors, conditions, and handlers for now
// they are kind of dangerous in this context anyway
// better off putting them in stored procedures
$triggers[$tableName][$eventName][$whenName] = array(
'variables' => array(),
'sql' => array(),
);
}
if ($variables) {
$triggers[$tableName][$eventName][$whenName]['variables'][] = $variables;
}
$triggers[$tableName][$eventName][$whenName]['sql'][] = $sql;
}
}
}
// now spit out the sql
foreach ($triggers as $tableName => $tables) {
if ($onlyTableName != NULL && $onlyTableName != $tableName) {
continue;
}
foreach ($tables as $eventName => $events) {
foreach ($events as $whenName => $parts) {
$varString = implode("\n", $parts['variables']);
$sqlString = implode("\n", $parts['sql']);
$validName = \CRM_Core_DAO::shortenSQLName($tableName, 48, TRUE);
$triggerName = "{$validName}_{$whenName}_{$eventName}";
$triggerSQL = "CREATE TRIGGER $triggerName $whenName $eventName ON $tableName FOR EACH ROW BEGIN $varString $sqlString END";
$this->enqueueQuery("DROP TRIGGER IF EXISTS $triggerName");
$this->enqueueQuery($triggerSQL);
}
}
}
}
/**
* Wrapper function to drop triggers.
*
* @param string $tableName
* the specific table requiring a rebuild; or NULL to rebuild all tables.
*/
public function dropTriggers($tableName = NULL) {
$info = array();
$logging = new \CRM_Logging_Schema();
$logging->triggerInfo($info, $tableName);
// drop all existing triggers on all tables
$logging->dropTriggers($tableName);
}
/**
* Enqueue a query which alters triggers.
*
* As this requires a high permission level we funnel the queries through here to
* facilitate them being taken 'offline'.
*
* @param string $triggerSQL
* The sql to run to create or drop the triggers.
* @param array $params
* Optional parameters to interpolate into the string.
*/
public function enqueueQuery($triggerSQL, $params = array()) {
if (\Civi::settings()->get('logging_no_trigger_permission')) {
if (!file_exists($this->getFile())) {
// Ugh. Need to let user know somehow. This is the first change.
\CRM_Core_Session::setStatus(ts('The mysql commands you need to run are stored in %1', array(
1 => $this->getFile(),
)),
'',
'alert',
array('expires' => 0)
);
}
$buf = "\n";
$buf .= "DELIMITER //\n";
$buf .= \CRM_Core_DAO::composeQuery($triggerSQL, $params) . " //\n";
$buf .= "DELIMITER ;\n";
file_put_contents($this->getFile(), $buf, FILE_APPEND);
}
else {
\CRM_Core_DAO::executeQuery($triggerSQL, $params, TRUE, NULL, FALSE, FALSE);
}
}
/**
* @return NULL|string
*/
public function getFile() {
if ($this->file === NULL) {
$prefix = 'trigger' . \CRM_Utils_Request::id();
$config = \CRM_Core_Config::singleton();
$this->file = "{$config->configAndLogDir}CiviCRM." . $prefix . md5($config->dsn) . '.sql';
}
return $this->file;
}
}

View file

@ -0,0 +1,213 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Transaction;
/**
* A "frame" is a layer in a series of nested transactions. Generally,
* the outermost frame is a normal SQL transaction (BEGIN/ROLLBACK/COMMIT)
* and any nested frames are SQL savepoints (SAVEPOINT foo/ROLLBACK TO SAVEPOINT).
*
* @package Civi
* @copyright CiviCRM LLC (c) 2004-2017
*/
class Frame {
const F_NEW = 0, F_ACTIVE = 1, F_DONE = 2, F_FORCED = 3;
/**
* @var \CRM_Core_DAO
*/
private $dao;
/**
* @var string|null e.g. "BEGIN" or "SAVEPOINT foo"
*/
private $beginStmt;
/**
* @var string|null e.g. "COMMIT"
*/
private $commitStmt;
/**
* @var string|null e.g. "ROLLBACK" or "ROLLBACK TO SAVEPOINT foo"
*/
private $rollbackStmt;
/**
* @var int
*/
private $refCount = 0;
private $callbacks;
private $doCommit = TRUE;
/**
* @var int
*/
private $state = self::F_NEW;
/**
* @param \CRM_Core_DAO $dao
* @param string|null $beginStmt e.g. "BEGIN" or "SAVEPOINT foo"
* @param string|null $commitStmt e.g. "COMMIT"
* @param string|null $rollbackStmt e.g. "ROLLBACK" or "ROLLBACK TO SAVEPOINT foo"
*/
public function __construct($dao, $beginStmt, $commitStmt, $rollbackStmt) {
$this->dao = $dao;
$this->beginStmt = $beginStmt;
$this->commitStmt = $commitStmt;
$this->rollbackStmt = $rollbackStmt;
$this->callbacks = array(
\CRM_Core_Transaction::PHASE_PRE_COMMIT => array(),
\CRM_Core_Transaction::PHASE_POST_COMMIT => array(),
\CRM_Core_Transaction::PHASE_PRE_ROLLBACK => array(),
\CRM_Core_Transaction::PHASE_POST_ROLLBACK => array(),
);
}
public function inc() {
$this->refCount++;
}
public function dec() {
$this->refCount--;
}
/**
* @return bool
*/
public function isEmpty() {
return ($this->refCount == 0);
}
/**
* @return bool
*/
public function isRollbackOnly() {
return !$this->doCommit;
}
public function setRollbackOnly() {
$this->doCommit = FALSE;
}
/**
* Begin frame processing.
*
* @throws \CRM_Core_Exception
*/
public function begin() {
if ($this->state !== self::F_NEW) {
throw new \CRM_Core_Exception('State is not F_NEW');
};
$this->state = self::F_ACTIVE;
if ($this->beginStmt) {
$this->dao->query($this->beginStmt);
}
}
/**
* Finish frame processing.
*
* @param int $newState
*
* @throws \CRM_Core_Exception
*/
public function finish($newState = self::F_DONE) {
if ($this->state == self::F_FORCED) {
return;
}
if ($this->state !== self::F_ACTIVE) {
throw new \CRM_Core_Exception('State is not F_ACTIVE');
};
$this->state = $newState;
if ($this->doCommit) {
$this->invokeCallbacks(\CRM_Core_Transaction::PHASE_PRE_COMMIT);
if ($this->commitStmt) {
$this->dao->query($this->commitStmt);
}
$this->invokeCallbacks(\CRM_Core_Transaction::PHASE_POST_COMMIT);
}
else {
$this->invokeCallbacks(\CRM_Core_Transaction::PHASE_PRE_ROLLBACK);
if ($this->rollbackStmt) {
$this->dao->query($this->rollbackStmt);
}
$this->invokeCallbacks(\CRM_Core_Transaction::PHASE_POST_ROLLBACK);
}
}
public function forceRollback() {
$this->setRollbackOnly();
$this->finish(self::F_FORCED);
}
/**
* Add a transaction callback.
*
* Pre-condition: isActive()
*
* @param int $phase
* A constant; one of: self::PHASE_{PRE,POST}_{COMMIT,ROLLBACK}.
* @param mixed $callback
* A PHP callback.
* @param array|NULL $params Optional values to pass to callback.
* See php manual call_user_func_array for details.
* @param null $id
*/
public function addCallback($phase, $callback, $params = NULL, $id = NULL) {
if ($id) {
$this->callbacks[$phase][$id] = array(
'callback' => $callback,
'parameters' => (is_array($params) ? $params : array($params)),
);
}
else {
$this->callbacks[$phase][] = array(
'callback' => $callback,
'parameters' => (is_array($params) ? $params : array($params)),
);
}
}
/**
* @param int $phase
*/
public function invokeCallbacks($phase) {
if (is_array($this->callbacks[$phase])) {
foreach ($this->callbacks[$phase] as $cb) {
call_user_func_array($cb['callback'], $cb['parameters']);
}
}
}
}

View file

@ -0,0 +1,177 @@
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
namespace Civi\Core\Transaction;
/**
*
* @package Civi
* @copyright CiviCRM LLC (c) 2004-2017
*/
class Manager {
private static $singleton = NULL;
/**
* @var \CRM_Core_DAO
*/
private $dao;
/**
* @var array<Frame> stack of SQL transactions/savepoints
*/
private $frames = array();
/**
* @var int
*/
private $savePointCount = 0;
/**
* @param bool $fresh
* @return Manager
*/
public static function singleton($fresh = FALSE) {
if (NULL === self::$singleton || $fresh) {
self::$singleton = new Manager(new \CRM_Core_DAO());
}
return self::$singleton;
}
/**
* @param \CRM_Core_DAO $dao
* Handle for the DB connection that will execute transaction statements.
* (all we really care about is the query() function)
*/
public function __construct($dao) {
$this->dao = $dao;
}
/**
* Increment the transaction count / add a new transaction level
*
* @param bool $nest
* Determines what to do if there's currently an active transaction:.
* - If true, then make a new nested transaction ("SAVEPOINT")
* - If false, then attach to the existing transaction
*/
public function inc($nest = FALSE) {
if (!isset($this->frames[0])) {
$frame = $this->createBaseFrame();
array_unshift($this->frames, $frame);
$frame->inc();
$frame->begin();
}
elseif ($nest) {
$frame = $this->createSavePoint();
array_unshift($this->frames, $frame);
$frame->inc();
$frame->begin();
}
else {
$this->frames[0]->inc();
}
}
/**
* Decrement the transaction count / close out a transaction level
*
* @throws \CRM_Core_Exception
*/
public function dec() {
if (!isset($this->frames[0]) || $this->frames[0]->isEmpty()) {
throw new \CRM_Core_Exception('Transaction integrity error: Expected to find active frame');
}
$this->frames[0]->dec();
if ($this->frames[0]->isEmpty()) {
// Callbacks may cause additional work (such as new transactions),
// and it would be confusing if the old frame was still active.
// De-register it before calling finish().
$oldFrame = array_shift($this->frames);
$oldFrame->finish();
}
}
/**
* Force an immediate rollback, regardless of how many
* transaction or frame objects exist.
*
* This is only appropriate when it is _certain_ that the
* callstack will not wind-down normally -- e.g. before
* a call to exit().
*/
public function forceRollback() {
// we take the long-way-round (rolling back each frame) so that the
// internal state of each frame is consistent with its outcome
$oldFrames = $this->frames;
$this->frames = array();
foreach ($oldFrames as $oldFrame) {
$oldFrame->forceRollback();
}
}
/**
* Get the (innermost) SQL transaction.
*
* @return \Civi\Core\Transaction\Frame
*/
public function getFrame() {
return isset($this->frames[0]) ? $this->frames[0] : NULL;
}
/**
* Get the (outermost) SQL transaction (i.e. the one
* demarcated by BEGIN/COMMIT/ROLLBACK)
*
* @return \Civi\Core\Transaction\Frame
*/
public function getBaseFrame() {
if (empty($this->frames)) {
return NULL;
}
return $this->frames[count($this->frames) - 1];
}
/**
* @return \Civi\Core\Transaction\Frame
*/
protected function createBaseFrame() {
return new Frame($this->dao, 'BEGIN', 'COMMIT', 'ROLLBACK');
}
/**
* @return \Civi\Core\Transaction\Frame
*/
protected function createSavePoint() {
$spId = $this->savePointCount++;
return new Frame($this->dao, "SAVEPOINT civi_{$spId}", NULL, "ROLLBACK TO SAVEPOINT civi_{$spId}");
}
}