First commit
This commit is contained in:
commit
c6e2478c40
13918 changed files with 2303184 additions and 0 deletions
383
sites/all/modules/civicrm/Civi/Core/AssetBuilder.php
Normal file
383
sites/all/modules/civicrm/Civi/Core/AssetBuilder.php
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
135
sites/all/modules/civicrm/Civi/Core/CiviEventDispatcher.php
Normal file
135
sites/all/modules/civicrm/Civi/Core/CiviEventDispatcher.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
227
sites/all/modules/civicrm/Civi/Core/CiviEventInspector.php
Normal file
227
sites/all/modules/civicrm/Civi/Core/CiviEventInspector.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
460
sites/all/modules/civicrm/Civi/Core/Container.php
Normal file
460
sites/all/modules/civicrm/Civi/Core/Container.php
Normal 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']);
|
||||
}
|
||||
|
||||
}
|
54
sites/all/modules/civicrm/Civi/Core/DAO/Event/PostDelete.php
Normal file
54
sites/all/modules/civicrm/Civi/Core/DAO/Event/PostDelete.php
Normal 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;
|
||||
}
|
||||
}
|
47
sites/all/modules/civicrm/Civi/Core/DAO/Event/PostUpdate.php
Normal file
47
sites/all/modules/civicrm/Civi/Core/DAO/Event/PostUpdate.php
Normal 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;
|
||||
}
|
||||
}
|
47
sites/all/modules/civicrm/Civi/Core/DAO/Event/PreDelete.php
Normal file
47
sites/all/modules/civicrm/Civi/Core/DAO/Event/PreDelete.php
Normal 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;
|
||||
}
|
||||
}
|
53
sites/all/modules/civicrm/Civi/Core/DatabaseInitializer.php
Normal file
53
sites/all/modules/civicrm/Civi/Core/DatabaseInitializer.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
255
sites/all/modules/civicrm/Civi/Core/Event/GenericHookEvent.php
Normal file
255
sites/all/modules/civicrm/Civi/Core/Event/GenericHookEvent.php
Normal 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]);
|
||||
}
|
||||
|
||||
}
|
87
sites/all/modules/civicrm/Civi/Core/Event/PostEvent.php
Normal file
87
sites/all/modules/civicrm/Civi/Core/Event/PostEvent.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
87
sites/all/modules/civicrm/Civi/Core/Event/PreEvent.php
Normal file
87
sites/all/modules/civicrm/Civi/Core/Event/PreEvent.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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__);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Civi\Core\Exception;
|
||||
|
||||
class UnknownAssetException extends \CRM_Core_Exception {
|
||||
|
||||
}
|
59
sites/all/modules/civicrm/Civi/Core/InstallationCanary.php
Normal file
59
sites/all/modules/civicrm/Civi/Core/InstallationCanary.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
104
sites/all/modules/civicrm/Civi/Core/LocalizationInitializer.php
Normal file
104
sites/all/modules/civicrm/Civi/Core/LocalizationInitializer.php
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
64
sites/all/modules/civicrm/Civi/Core/Lock/LockInterface.php
Normal file
64
sites/all/modules/civicrm/Civi/Core/Lock/LockInterface.php
Normal 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();
|
||||
|
||||
}
|
121
sites/all/modules/civicrm/Civi/Core/Lock/LockManager.php
Normal file
121
sites/all/modules/civicrm/Civi/Core/Lock/LockManager.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
92
sites/all/modules/civicrm/Civi/Core/Lock/NullLock.php
Normal file
92
sites/all/modules/civicrm/Civi/Core/Lock/NullLock.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
211
sites/all/modules/civicrm/Civi/Core/Paths.php
Normal file
211
sites/all/modules/civicrm/Civi/Core/Paths.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
287
sites/all/modules/civicrm/Civi/Core/Resolver.php
Normal file
287
sites/all/modules/civicrm/Civi/Core/Resolver.php
Normal 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.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
403
sites/all/modules/civicrm/Civi/Core/SettingsBag.php
Normal file
403
sites/all/modules/civicrm/Civi/Core/SettingsBag.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
354
sites/all/modules/civicrm/Civi/Core/SettingsManager.php
Normal file
354
sites/all/modules/civicrm/Civi/Core/SettingsManager.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
165
sites/all/modules/civicrm/Civi/Core/SettingsMetadata.php
Normal file
165
sites/all/modules/civicrm/Civi/Core/SettingsMetadata.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
sites/all/modules/civicrm/Civi/Core/SettingsStack.php
Normal file
57
sites/all/modules/civicrm/Civi/Core/SettingsStack.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
241
sites/all/modules/civicrm/Civi/Core/SqlTriggers.php
Normal file
241
sites/all/modules/civicrm/Civi/Core/SqlTriggers.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
213
sites/all/modules/civicrm/Civi/Core/Transaction/Frame.php
Normal file
213
sites/all/modules/civicrm/Civi/Core/Transaction/Frame.php
Normal 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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
177
sites/all/modules/civicrm/Civi/Core/Transaction/Manager.php
Normal file
177
sites/all/modules/civicrm/Civi/Core/Transaction/Manager.php
Normal 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}");
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue