FeedsConfigurable.inc 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <?php
  2. /**
  3. * @file
  4. * FeedsConfigurable and helper functions.
  5. */
  6. /**
  7. * Used when an object does not exist in the DB or code but should.
  8. */
  9. class FeedsNotExistingException extends Exception {
  10. }
  11. /**
  12. * Base class for configurable classes. Captures configuration handling, form
  13. * handling and distinguishes between in-memory configuration and persistent
  14. * configuration.
  15. *
  16. * Due to the magic method __get(), protected properties from this class and
  17. * from classes that extend this class will be publicly readable (but not
  18. * writeable).
  19. */
  20. abstract class FeedsConfigurable {
  21. // Holds the actual configuration information.
  22. protected $config;
  23. // A unique identifier for the configuration.
  24. protected $id;
  25. /*
  26. CTools export type of this object.
  27. @todo Should live in FeedsImporter. Not all child classes
  28. of FeedsConfigurable are exportable. Same goes for $disabled.
  29. Export type can be one of
  30. FEEDS_EXPORT_NONE - the configurable only exists in memory
  31. EXPORT_IN_DATABASE - the configurable is defined in the database.
  32. EXPORT_IN_CODE - the configurable is defined in code.
  33. EXPORT_IN_CODE | EXPORT_IN_DATABASE - the configurable is defined in code, but
  34. overridden in the database.*/
  35. protected $export_type;
  36. /**
  37. * CTools export enabled status of this object.
  38. */
  39. protected $disabled;
  40. /**
  41. * Instantiates a FeedsConfigurable object.
  42. *
  43. * Don't use directly, use feeds_importer() or feeds_plugin() instead.
  44. *
  45. * @see feeds_importer()
  46. * @see feeds_plugin()
  47. */
  48. public static function instance($class, $id) {
  49. if (!strlen($id)) {
  50. throw new InvalidArgumentException(t('Empty configuration identifier.'));
  51. }
  52. $instances = &drupal_static(__METHOD__, array());
  53. if (!isset($instances[$class][$id])) {
  54. $instances[$class][$id] = new $class($id);
  55. }
  56. return $instances[$class][$id];
  57. }
  58. /**
  59. * Constructor, set id and load default configuration.
  60. */
  61. protected function __construct($id) {
  62. // Set this object's id.
  63. $this->id = $id;
  64. // Per default we assume that a Feeds object is not saved to
  65. // database nor is it exported to code.
  66. $this->export_type = FEEDS_EXPORT_NONE;
  67. // Make sure configuration is populated.
  68. $this->config = $this->configDefaults();
  69. $this->disabled = FALSE;
  70. }
  71. /**
  72. * Override magic method __isset(). This is needed due to overriding __get().
  73. */
  74. public function __isset($name) {
  75. return isset($this->$name) ? TRUE : FALSE;
  76. }
  77. /**
  78. * Determine whether this object is persistent and enabled. I. e. it is
  79. * defined either in code or in the database and it is enabled.
  80. */
  81. public function existing() {
  82. if ($this->export_type == FEEDS_EXPORT_NONE) {
  83. throw new FeedsNotExistingException(t('Object is not persistent.'));
  84. }
  85. if ($this->disabled) {
  86. throw new FeedsNotExistingException(t('Object is disabled.'));
  87. }
  88. return $this;
  89. }
  90. /**
  91. * Save a configuration. Concrete extending classes must implement a save
  92. * operation.
  93. */
  94. public abstract function save();
  95. /**
  96. * Copy a configuration.
  97. */
  98. public function copy(FeedsConfigurable $configurable) {
  99. $this->setConfig($configurable->config);
  100. }
  101. /**
  102. * Set configuration.
  103. *
  104. * @param $config
  105. * Array containing configuration information. Config array will be filtered
  106. * by the keys returned by configDefaults() and populated with default
  107. * values that are not included in $config.
  108. */
  109. public function setConfig($config) {
  110. $defaults = $this->configDefaults();
  111. $this->config = array_intersect_key($config, $defaults) + $defaults;
  112. }
  113. /**
  114. * Similar to setConfig but adds to existing configuration.
  115. *
  116. * @param $config
  117. * Array containing configuration information. Will be filtered by the keys
  118. * returned by configDefaults().
  119. */
  120. public function addConfig($config) {
  121. $this->config = is_array($this->config) ? array_merge($this->config, $config) : $config;
  122. $default_keys = $this->configDefaults();
  123. $this->config = array_intersect_key($this->config, $default_keys);
  124. }
  125. /**
  126. * Overrides magic method __get().
  127. *
  128. * - Makes sure that external reads of FeedsConfigurable::config go through
  129. * ::getConfig();
  130. * - Makes private and protected properties from this class and protected
  131. * properties from child classes publicly readable.
  132. * - Prevents warnings when accessing non-existent properties.
  133. */
  134. public function __get($name) {
  135. if ($name == 'config') {
  136. return $this->getConfig();
  137. }
  138. return isset($this->$name) ? $this->$name : NULL;
  139. }
  140. /**
  141. * Implements getConfig().
  142. *
  143. * Return configuration array, ensure that all default values are present.
  144. */
  145. public function getConfig() {
  146. $defaults = $this->configDefaults();
  147. return $this->config + $defaults;
  148. }
  149. /**
  150. * Return default configuration.
  151. *
  152. * @todo rename to getConfigDefaults().
  153. *
  154. * @return
  155. * Array where keys are the variable names of the configuration elements and
  156. * values are their default values.
  157. */
  158. public function configDefaults() {
  159. return array();
  160. }
  161. /**
  162. * Returns whether or not the configurable has a config form.
  163. *
  164. * @return bool
  165. * True if the configurable has a config form, and false if not.
  166. */
  167. public function hasConfigForm() {
  168. $form_state = array();
  169. return (bool) $this->configForm($form_state);
  170. }
  171. /**
  172. * Return configuration form for this object. The keys of the configuration
  173. * form must match the keys of the array returned by configDefaults().
  174. *
  175. * @return
  176. * FormAPI style form definition.
  177. */
  178. public function configForm(&$form_state) {
  179. return array();
  180. }
  181. /**
  182. * Validation handler for configForm().
  183. *
  184. * Set errors with form_set_error().
  185. *
  186. * @param $values
  187. * An array that contains the values entered by the user through configForm.
  188. */
  189. public function configFormValidate(&$values) {
  190. }
  191. /**
  192. * Submission handler for configForm().
  193. *
  194. * @param $values
  195. */
  196. public function configFormSubmit(&$values) {
  197. $this->addConfig($values);
  198. $this->save();
  199. drupal_set_message(t('Your changes have been saved.'));
  200. feeds_cache_clear(FALSE);
  201. }
  202. /**
  203. * Returns an array of required modules.
  204. *
  205. * @return array
  206. * The modules that this configurable requires.
  207. */
  208. public function dependencies() {
  209. return array();
  210. }
  211. }
  212. /**
  213. * Config form wrapper. Use to render the configuration form of
  214. * a FeedsConfigurable object.
  215. *
  216. * @param $configurable
  217. * FeedsConfigurable object.
  218. * @param $form_method
  219. * The form method that should be rendered.
  220. *
  221. * @return
  222. * Config form array if available. NULL otherwise.
  223. */
  224. function feeds_get_form($configurable, $form_method) {
  225. if (method_exists($configurable, $form_method)) {
  226. return drupal_get_form(get_class($configurable) . '_feeds_form', $configurable, $form_method);
  227. }
  228. }
  229. /**
  230. * Config form callback. Don't call directly, but use
  231. * feeds_get_form($configurable, 'method') instead.
  232. *
  233. * @param
  234. * FormAPI $form_state.
  235. * @param
  236. * FeedsConfigurable object.
  237. * @param
  238. * The object to perform the save() operation on.
  239. * @param $form_method
  240. * The $form_method that should be rendered.
  241. */
  242. function feeds_form($form, &$form_state, $configurable, $form_method) {
  243. $form = $configurable->$form_method($form_state);
  244. $form['#configurable'] = $configurable;
  245. $form['#feeds_form_method'] = $form_method;
  246. $form['#validate'] = array('feeds_form_validate');
  247. $form['#submit'] = array('feeds_form_submit');
  248. $form['submit'] = array(
  249. '#type' => 'submit',
  250. '#value' => t('Save'),
  251. '#weight' => 100,
  252. );
  253. return $form;
  254. }
  255. /**
  256. * Validation handler for feeds_form().
  257. */
  258. function feeds_form_validate($form, &$form_state) {
  259. _feeds_form_helper($form, $form_state, 'Validate');
  260. }
  261. /**
  262. * Submit handler for feeds_form().
  263. */
  264. function feeds_form_submit($form, &$form_state) {
  265. _feeds_form_helper($form, $form_state, 'Submit');
  266. }
  267. /**
  268. * Helper for Feeds validate and submit callbacks.
  269. *
  270. * @todo This is all terrible. Remove.
  271. */
  272. function _feeds_form_helper($form, &$form_state, $action) {
  273. $method = $form['#feeds_form_method'] . $action;
  274. $class = get_class($form['#configurable']);
  275. $id = $form['#configurable']->id;
  276. // Re-initialize the configurable object. Using feeds_importer() and
  277. // feeds_plugin() will ensure that we're using the same instance. We can't
  278. // reuse the previous form instance because feeds_importer() is used to save.
  279. // This will re-initialize all of the plugins anyway, causing some tricky
  280. // saving issues in certain cases.
  281. // See http://drupal.org/node/1672880.
  282. if ($class == variable_get('feeds_importer_class', 'FeedsImporter')) {
  283. $form['#configurable'] = feeds_importer($id);
  284. }
  285. else {
  286. $importer = feeds_importer($id);
  287. $plugin_key = $importer->config[$form['#configurable']->pluginType()]['plugin_key'];
  288. $form['#configurable'] = feeds_plugin($plugin_key, $id);
  289. }
  290. if (method_exists($form['#configurable'], $method)) {
  291. $form['#configurable']->$method($form_state['values']);
  292. }
  293. }