518 lines
15 KiB
PHP
518 lines
15 KiB
PHP
<?php
|
|
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
namespace FacebookPixelPlugin\FacebookAds\Object;
|
|
use FacebookPixelPlugin\FacebookAds\Api;
|
|
use FacebookPixelPlugin\FacebookAds\Cursor;
|
|
use FacebookPixelPlugin\FacebookAds\Http\RequestInterface;
|
|
use FacebookPixelPlugin\FacebookAds\Http\ResponseInterface;
|
|
class AbstractCrudObject extends AbstractObject {
|
|
/**
|
|
* @var string
|
|
*/
|
|
const FIELD_ID = 'id';
|
|
/**
|
|
* @var string[] set of fields to read by default
|
|
*/
|
|
protected static $defaultReadFields = array();
|
|
/**
|
|
* @var array set of fields that have been mutated
|
|
*/
|
|
protected $changedFields = array();
|
|
/**
|
|
* @var Api instance of the Api used by this object
|
|
*/
|
|
protected $api;
|
|
/**
|
|
* @var string ID of the adaccount this object belongs to
|
|
*/
|
|
protected $parentId;
|
|
|
|
/**
|
|
* @deprecated deprecate constructor with null and parent_id
|
|
* @param string $id Optional (do not set for new objects)
|
|
* @param string $parent_id Optional, needed for creating new objects.
|
|
* @param Api $api The Api instance this object should use to make calls
|
|
*/
|
|
public function __construct($id = null, $parent_id = null, ?Api $api = null) {
|
|
parent::__construct();
|
|
|
|
// check that $id is an integer or a string integer or a string of
|
|
// two integer connected by an underscore, like "123_456"
|
|
|
|
$int_id = $id;
|
|
if ($id !== null && strpos($id, 'act_') === 0) {
|
|
$int_id = substr($id, 4);
|
|
}
|
|
$split_by_underscore = explode('_', (string) $id);
|
|
$is_regular_id = sizeof($split_by_underscore) == 2 &&
|
|
ctype_digit($split_by_underscore[0]) &&
|
|
ctype_digit($split_by_underscore[1]);
|
|
if (!is_null($int_id) && !ctype_digit((string) $int_id) && !$is_regular_id) {
|
|
$extra_message = '';
|
|
if (is_numeric($int_id)) {
|
|
$extra_message = ' Please use an integer string'
|
|
.' to prevent integer overflow.';
|
|
}
|
|
throw new \InvalidArgumentException(
|
|
'Object ID must be an integer or integer string but was passed "'
|
|
.(string)$id.'" ('.gettype($id).').'.(string)$extra_message);
|
|
}
|
|
$this->data[static::FIELD_ID] = $id;
|
|
|
|
if (!is_null($parent_id)) {
|
|
$warning_message = "\$parent_id as a parameter of constructor is being " .
|
|
"deprecated, please try not to use this in new code.\n";
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
}
|
|
$this->parentId = $parent_id;
|
|
|
|
$this->api = static::assureApi($api);
|
|
}
|
|
|
|
/**
|
|
* @param string $id
|
|
*/
|
|
public function setId($id) {
|
|
$this->data[static::FIELD_ID] = $id;
|
|
return $this;
|
|
}
|
|
/**
|
|
* @deprecated deprecate parent_id in AbstractCrudObject
|
|
* @param string $parent_id
|
|
*/
|
|
public function setParentId($parent_id) {
|
|
$warning_message = sprintf('%s is being deprecated, please try not to use'.
|
|
' this in new code.',__FUNCTION__);
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
$this->parentId = $parent_id;
|
|
return $this;
|
|
}
|
|
/**
|
|
* @param Api $api The Api instance this object should use to make calls
|
|
*/
|
|
public function setApi(Api $api) {
|
|
$this->api = static::assureApi($api);
|
|
return $this;
|
|
}
|
|
/**
|
|
* @deprecated getEndpoint function is deprecated
|
|
* @return string
|
|
*/
|
|
protected function getEndpoint() {
|
|
return null;
|
|
}
|
|
/**
|
|
* @param Api|null $instance
|
|
* @return Api
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
protected static function assureApi(?Api $instance = null) {
|
|
$instance = $instance ?: Api::instance();
|
|
if (!$instance) {
|
|
throw new \InvalidArgumentException(
|
|
'An Api instance must be provided as argument or '.
|
|
'set as instance in the \FacebookAds\Api');
|
|
}
|
|
return $instance;
|
|
}
|
|
/**
|
|
* @deprecated deprecate parent_id in AbstractCrudObject
|
|
* @return string|null
|
|
*/
|
|
public function getParentId() {
|
|
$warning_message = sprintf('%s is being deprecated, please try not to use'.
|
|
' this in new code.',__FUNCTION__);
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
return $this->parentId;
|
|
}
|
|
/**
|
|
* @deprecated deprecate parent_id in AbstractCrudObject
|
|
* @return string
|
|
* @throws \Exception
|
|
*/
|
|
protected function assureParentId() {
|
|
$warning_message = sprintf('%s is being deprecated, please try not to use'.
|
|
' this in new code.',__FUNCTION__);
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
if (!$this->parentId) {
|
|
throw new \Exception("A parent ID is required.");
|
|
}
|
|
return $this->parentId;
|
|
}
|
|
/**
|
|
* @return string
|
|
* @throws \Exception
|
|
*/
|
|
protected function assureId() {
|
|
if (!$this->data[static::FIELD_ID]) {
|
|
throw new \Exception("field '".static::FIELD_ID."' is required.");
|
|
}
|
|
return (string) $this->data[static::FIELD_ID];
|
|
}
|
|
/**
|
|
* @return Api
|
|
*/
|
|
public function getApi() {
|
|
return $this->api;
|
|
}
|
|
/**
|
|
* Get the values which have changed
|
|
*
|
|
* @return array Key value pairs of changed variables
|
|
*/
|
|
public function getChangedValues() {
|
|
return $this->changedFields;
|
|
}
|
|
/**
|
|
* Get the name of the fields that have changed
|
|
*
|
|
* @return array Array of changed field names
|
|
*/
|
|
public function getChangedFields() {
|
|
return array_keys($this->changedFields);
|
|
}
|
|
/**
|
|
* Get the values which have changed, converting them to scalars
|
|
*/
|
|
public function exportData() {
|
|
$data = array();
|
|
foreach ($this->changedFields as $key => $val) {
|
|
$data[$key] = parent::exportValue($val);
|
|
}
|
|
return $data;
|
|
}
|
|
/**
|
|
* @return void
|
|
*/
|
|
protected function clearHistory() {
|
|
$this->changedFields = array();
|
|
}
|
|
/**
|
|
* @param string $name
|
|
* @param mixed $value
|
|
*/
|
|
public function __set($name, $value) {
|
|
if (!array_key_exists($name, $this->data)
|
|
|| $this->data[$name] !== $value) {
|
|
$this->changedFields[$name] = $value;
|
|
}
|
|
parent::__set($name, $value);
|
|
}
|
|
/**
|
|
* @param string[] $fields
|
|
*/
|
|
public static function setDefaultReadFields(array $fields = array()) {
|
|
static::$defaultReadFields = $fields;
|
|
}
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public static function getDefaultReadFields() {
|
|
return static::$defaultReadFields;
|
|
}
|
|
/**
|
|
* @return string
|
|
*/
|
|
protected function getNodePath() {
|
|
return '/'.$this->assureId();
|
|
}
|
|
/**
|
|
* @deprecated
|
|
* use (ParentObject)->creatXXX() instead
|
|
* Create function for the object.
|
|
*
|
|
* @param array $params Additional parameters to include in the request
|
|
* @return $this
|
|
* @throws \Exception
|
|
*/
|
|
public function create(array $params = array()) {
|
|
$warning_message = sprintf('%s is being deprecated, please try not to use'.
|
|
' this in new code.',__FUNCTION__);
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
if ($this->data[static::FIELD_ID]) {
|
|
throw new \Exception("Object has already an ID");
|
|
}
|
|
$response = $this->getApi()->call(
|
|
'/'.$this->assureParentId().'/'.$this->getEndpoint(),
|
|
RequestInterface::METHOD_POST,
|
|
array_merge($this->exportData(), $params));
|
|
$this->clearHistory();
|
|
$data = $response->getContent();
|
|
if (!isset($params['execution_options'])){
|
|
$id = is_string($data) ? $data : $data[static::FIELD_ID];
|
|
/** @var AbstractCrudObject $this */
|
|
if ($this instanceof CanRedownloadInterface
|
|
&& isset($params[CanRedownloadInterface::PARAM_REDOWNLOAD])
|
|
&& $params[CanRedownloadInterface::PARAM_REDOWNLOAD] === true
|
|
&& isset($data['data'][$id])
|
|
&& is_array($data['data'][$id])
|
|
) {
|
|
$this->setDataWithoutValidation($data['data'][$id]);
|
|
}
|
|
$this->data[static::FIELD_ID] = (string) $id;
|
|
}
|
|
return $this;
|
|
}
|
|
/**
|
|
* @deprecated
|
|
* use getSelf() instead
|
|
* Read object data from the graph
|
|
*
|
|
* @param string[] $fields Fields to request
|
|
* @param array $params Additional request parameters
|
|
* @return $this
|
|
*/
|
|
public function read(array $fields = array(), array $params = array()) {
|
|
$warning_message = sprintf('%s is being deprecated, please try not to use'.
|
|
' this in new code.',__FUNCTION__);
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
$fields = implode(',', $fields ?: static::getDefaultReadFields());
|
|
if ($fields) {
|
|
$params['fields'] = $fields;
|
|
}
|
|
$response = $this->getApi()->call(
|
|
$this->getNodePath(),
|
|
RequestInterface::METHOD_GET,
|
|
$params);
|
|
$this->setDataWithoutValidation($response->getContent());
|
|
$this->clearHistory();
|
|
return $this;
|
|
}
|
|
/**
|
|
* @deprecated
|
|
* use updateSelf() instead
|
|
* Update the object. Function parameters are similar with the create function
|
|
*
|
|
* @param array $params Update parameters in assoc
|
|
* @return $this
|
|
*/
|
|
public function update(array $params = array()) {
|
|
$warning_message = sprintf('%s is being deprecated, please try not to use'.
|
|
' this in new code.',__FUNCTION__);
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
$this->getApi()->call(
|
|
$this->getNodePath(),
|
|
RequestInterface::METHOD_POST,
|
|
array_merge($this->exportData(), $params));
|
|
$this->clearHistory();
|
|
return $this;
|
|
}
|
|
/**
|
|
* @deprecated
|
|
* use deleteSelf() in each subclass
|
|
* Delete this object from the graph
|
|
*
|
|
* @param array $params
|
|
* @return void
|
|
*/
|
|
public function deleteSelf(array $params = array()) {
|
|
$warning_message = sprintf('%s is being deprecated, please try not to use'.
|
|
' this in new code.',__FUNCTION__);
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
$this->getApi()->call(
|
|
$this->getNodePath(),
|
|
RequestInterface::METHOD_DELETE,
|
|
$params);
|
|
}
|
|
/**
|
|
* @deprecated
|
|
* deprecate with ObjectValidation
|
|
* Perform object upsert
|
|
*
|
|
* Helper function which determines whether an object should be created or
|
|
* updated
|
|
*
|
|
* @param array $params
|
|
* @return $this
|
|
*/
|
|
public function save(array $params = array()) {
|
|
$warning_message = sprintf('%s is being deprecated, please try not to use'.
|
|
' this in new code.',__FUNCTION__);
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
if ($this->data[static::FIELD_ID]) {
|
|
return $this->update($params);
|
|
} else {
|
|
return $this->create($params);
|
|
}
|
|
}
|
|
/**
|
|
* @deprecated
|
|
* deprecate with getEndpoint
|
|
* @param string $prototype_class
|
|
* @param string $endpoint
|
|
* @return string
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
protected function assureEndpoint($prototype_class, $endpoint) {
|
|
$warning_message = sprintf('%s is being deprecated, please try not to use'.
|
|
' this in new code.',__FUNCTION__);
|
|
trigger_error($warning_message, E_USER_DEPRECATED);
|
|
if (!$endpoint) {
|
|
$prototype = new $prototype_class(null, null, $this->getApi());
|
|
if (!$prototype instanceof AbstractCrudObject) {
|
|
throw new \InvalidArgumentException('Either prototype must be instance
|
|
of AbstractCrudObject or $endpoint must be given');
|
|
}
|
|
$endpoint = $prototype->getEndpoint();
|
|
}
|
|
return $endpoint;
|
|
}
|
|
/**
|
|
* @param array $fields
|
|
* @param array $params
|
|
* @param string $prototype_class
|
|
* @param string|null $endpoint
|
|
* @return ResponseInterface
|
|
*/
|
|
protected function fetchConnection(
|
|
array $fields = array(),
|
|
array $params = array(),
|
|
$prototype_class = '',
|
|
$endpoint = null) {
|
|
$fields = implode(',', $fields ?: static::getDefaultReadFields());
|
|
if ($fields) {
|
|
$params['fields'] = $fields;
|
|
}
|
|
$endpoint = $this->assureEndpoint($prototype_class, $endpoint);
|
|
return $this->getApi()->call(
|
|
'/'.$this->assureId().'/'.$endpoint,
|
|
RequestInterface::METHOD_GET,
|
|
$params);
|
|
}
|
|
/**
|
|
* Read a single connection object
|
|
*
|
|
* @param string $prototype_class
|
|
* @param array $fields Fields to request
|
|
* @param array $params Additional filters for the reading
|
|
* @param string|null $endpoint
|
|
* @return AbstractObject
|
|
*/
|
|
protected function getOneByConnection(
|
|
$prototype_class,
|
|
array $fields = array(),
|
|
array $params = array(),
|
|
$endpoint = null) {
|
|
$response = $this->fetchConnection(
|
|
$fields, $params, $prototype_class, $endpoint);
|
|
if (!$response->getContent()) {
|
|
return null;
|
|
}
|
|
$object = new $prototype_class(
|
|
null, null, $this->getApi());
|
|
/** @var AbstractCrudObject $object */
|
|
$object->setDataWithoutValidation($response->getContent());
|
|
return $object;
|
|
}
|
|
/**
|
|
* Read objects from a connection
|
|
*
|
|
* @param string $prototype_class
|
|
* @param array $fields Fields to request
|
|
* @param array $params Additional filters for the reading
|
|
* @param string|null $endpoint
|
|
* @return Cursor
|
|
*/
|
|
protected function getManyByConnection(
|
|
$prototype_class,
|
|
array $fields = array(),
|
|
array $params = array(),
|
|
$endpoint = null) {
|
|
$response = $this->fetchConnection(
|
|
$fields, $params, $prototype_class, $endpoint);
|
|
return new Cursor(
|
|
$response,
|
|
new $prototype_class(null, null, $this->getApi()));
|
|
}
|
|
/**
|
|
* @param string $job_class
|
|
* @param array $fields
|
|
* @param array $params
|
|
* @return AbstractAsyncJobObject
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
protected function createAsyncJob(
|
|
$job_class,
|
|
array $fields = array(),
|
|
array $params = array()) {
|
|
$object = new $job_class(null, $this->assureId(), $this->getApi());
|
|
if (!$object instanceof AbstractAsyncJobObject) {
|
|
throw new \InvalidArgumentException(
|
|
"Class {$job_class} is not of type "
|
|
.AbstractAsyncJobObject::className());
|
|
}
|
|
$params['fields'] = $fields;
|
|
return $object->create($params);
|
|
}
|
|
/**
|
|
* Delete objects.
|
|
*
|
|
* Used batch API calls to delete multiple objects at once
|
|
*
|
|
* @param string[] $ids Array or single Object ID to delete
|
|
* @param Api $api Api Object to use
|
|
* @return bool Returns true on success
|
|
*/
|
|
public static function deleteIds(array $ids, ?Api $api = null) {
|
|
$batch = array();
|
|
foreach ($ids as $id) {
|
|
$request = array(
|
|
'relative_url' => '/'.$id,
|
|
'method' => RequestInterface::METHOD_DELETE,
|
|
);
|
|
$batch[] = $request;
|
|
}
|
|
$api = static::assureApi($api);
|
|
$response = $api->call(
|
|
'/',
|
|
RequestInterface::METHOD_POST,
|
|
array('batch' => json_encode($batch)));
|
|
foreach ($response->getContent() as $result) {
|
|
if (200 != $result['code']) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* Read function for the object. Convert fields and filters into the query
|
|
* part of uri and return objects.
|
|
*
|
|
* @param mixed $ids Array or single object IDs
|
|
* @param array $fields Array of field names to read
|
|
* @param array $params Additional filters for the reading, in assoc
|
|
* @param Api $api Api Object to use
|
|
* @return Cursor
|
|
*/
|
|
public static function readIds(
|
|
array $ids,
|
|
array $fields = array(),
|
|
array $params = array(),
|
|
?Api $api = null) {
|
|
if (empty($fields)) {
|
|
$fields = static::getDefaultReadFields();
|
|
}
|
|
if (!empty($fields)) {
|
|
$params['fields'] = implode(',', $fields);
|
|
}
|
|
$params['ids'] = implode(',', $ids);
|
|
$api = static::assureApi($api);
|
|
$response = $api->call('/', RequestInterface::METHOD_GET, $params);
|
|
$result = array();
|
|
foreach ($response->getContent() as $data) {
|
|
/** @var AbstractObject $object */
|
|
$object = new static(null, null, $api);
|
|
$object->setDataWithoutValidation((array) $data);
|
|
$result[] = $object;
|
|
}
|
|
return $result;
|
|
}
|
|
}
|