PageRenderTime 24ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/symphony/lib/class.datasource.php

https://github.com/creativedutchmen/symphony-3
PHP | 367 lines | 240 code | 92 blank | 35 comment | 43 complexity | 84a0d6f9b74ec66a83784eac52b4c8f3 MD5 | raw file
  1. <?php
  2. Class DataSourceException extends Exception {}
  3. Class DataSourceFilterIterator extends FilterIterator{
  4. public function __construct($path){
  5. parent::__construct(new DirectoryIterator($path));
  6. }
  7. public function accept(){
  8. if($this->isDir() == false && preg_match('/^.+\.php$/i', $this->getFilename())){
  9. return true;
  10. }
  11. return false;
  12. }
  13. }
  14. Class DataSourceIterator implements Iterator{
  15. private static $datasources;
  16. private $position;
  17. public function __construct(){
  18. $this->position = 0;
  19. if (!empty(self::$datasources)) return;
  20. self::clearCachedFiles();
  21. foreach (new DataSourceFilterIterator(DATASOURCES) as $file) {
  22. self::$datasources[] = $file->getPathname();
  23. }
  24. $extensions = new ExtensionIterator(array(ExtensionIterator::FLAG_STATUS => Extension::STATUS_ENABLED));
  25. foreach ($extensions as $extension) {
  26. $path = Extension::getPathFromClass(get_class($extension));
  27. if (!is_dir($path . '/data-sources')) continue;
  28. foreach (new DataSourceFilterIterator($path . '/data-sources') as $file) {
  29. self::$datasources[] = $file->getPathname();
  30. }
  31. }
  32. }
  33. public static function clearCachedFiles() {
  34. self::$datasources = array();
  35. }
  36. public function length(){
  37. return count(self::$datasources);
  38. }
  39. public function rewind(){
  40. $this->position = 0;
  41. }
  42. public function current(){
  43. return self::$datasources[$this->position]; //Datasource::loadFromPath($this->datasources[$this->position]);
  44. }
  45. public function key(){
  46. return $this->position;
  47. }
  48. public function next(){
  49. ++$this->position;
  50. }
  51. public function valid(){
  52. return isset(self::$datasources[$this->position]);
  53. }
  54. }
  55. ##Interface for datasouce objects
  56. Abstract Class DataSource{
  57. const FILTER_AND = 1;
  58. const FILTER_OR = 2;
  59. protected $_about;
  60. protected $_parameters;
  61. protected static $_loaded;
  62. // Abstract function
  63. abstract public function render(Register $ParameterOutput);
  64. public static function getHandleFromFilename($filename){
  65. return basename($filename, ".php");
  66. }
  67. public function &about(){
  68. return $this->_about;
  69. }
  70. public function &parameters(){
  71. return $this->_parameters;
  72. }
  73. public static function load($pathname){
  74. $pathname = General::fixWinPath($pathname);
  75. if(!is_array(self::$_loaded)){
  76. self::$_loaded = array();
  77. }
  78. if(!is_file($pathname)){
  79. throw new DataSourceException(
  80. __('Could not find Data Source <code>%s</code>. If the Data Source was provided by an Extension, ensure that it is installed, and enabled.', array(basename($pathname)))
  81. );
  82. }
  83. if(!isset(self::$_loaded[$pathname])){
  84. self::$_loaded[$pathname] = require($pathname);
  85. }
  86. $obj = new self::$_loaded[$pathname];
  87. $obj->parameters()->pathname = $pathname;
  88. return $obj;
  89. }
  90. public static function loadFromHandle($name){
  91. return self::load(self::__find($name) . "/{$name}.php");
  92. }
  93. protected static function __find($name){
  94. if(is_file(DATASOURCES . "/{$name}.php")) return DATASOURCES;
  95. else{
  96. foreach(new ExtensionIterator(ExtensionIterator::FLAG_STATUS, Extension::STATUS_ENABLED) as $extension){
  97. $path = Extension::getPathFromClass(get_class($extension));
  98. $handle = Extension::getHandleFromPath($path);
  99. if(is_file(EXTENSIONS . "/{$handle}/data-sources/{$name}.php")) return EXTENSIONS . "/{$handle}/data-sources";
  100. }
  101. /*
  102. $extensions = ExtensionManager::instance()->listInstalledHandles();
  103. if(is_array($extensions) && !empty($extensions)){
  104. foreach($extensions as $e){
  105. if(is_file(EXTENSIONS . "/{$e}/data-sources/{$name}.php")) return EXTENSIONS . "/{$e}/data-sources";
  106. }
  107. }
  108. */
  109. }
  110. return false;
  111. }
  112. ## This function is required in order to edit it in the data source editor page.
  113. ## Overload this function, and return false if you do not want the Data Source editor
  114. ## loading your Data Source
  115. public function allowEditorToParse(){
  116. return true;
  117. }
  118. public function getExtension(){
  119. return NULL;
  120. }
  121. public function getTemplate(){
  122. return NULL;
  123. }
  124. public function prepareSourceColumnValue() {
  125. return Widget::TableData(__('None'), array('class' => 'inactive'));
  126. }
  127. public function __get($name){
  128. if($name == 'handle'){
  129. return Lang::createFilename($this->about()->name);
  130. }
  131. }
  132. public function save(MessageStack $errors){
  133. $editing = (isset($this->parameters()->{'root-element'}))
  134. ? $this->parameters()->{'root-element'}
  135. : false;
  136. // About info:
  137. if (!isset($this->about()->name) || empty($this->about()->name)) {
  138. $errors->append('about::name', __('This is a required field'));
  139. }
  140. try {
  141. $existing = self::loadFromHandle($this->handle);
  142. }
  143. catch (DataSourceException $e) {
  144. // Datasource not found, continue!
  145. }
  146. if($existing instanceof Datasource && $editing != $this->handle) {
  147. throw new DataSourceException(__('A Datasource with the name <code>%s</code> already exists', array($this->about()->name)));
  148. }
  149. // Save type:
  150. if ($errors->length() <= 0) {
  151. $user = Administration::instance()->User;
  152. if (!file_exists($this->getTemplate())) {
  153. $errors->append('write', __("Unable to find Data Source Type template '%s'.", array($this->getTemplate())));
  154. throw new DataSourceException(__("Unable to find Data Source Type template '%s'.", array($this->getTemplate())));
  155. }
  156. $this->parameters()->{'root-element'} = $this->handle;
  157. $classname = Lang::createHandle(ucwords($this->about()->name), '_', false, true, array('/[^a-zA-Z0-9_\x7f-\xff]/' => NULL), true);
  158. $pathname = DATASOURCES . "/" . $this->handle . ".php";
  159. $data = array(
  160. $classname,
  161. // About info:
  162. var_export($this->about()->name, true),
  163. var_export($user->getFullName(), true),
  164. var_export(URL, true),
  165. var_export($user->email, true),
  166. var_export('1.0', true),
  167. var_export(DateTimeObj::getGMT('c'), true),
  168. );
  169. foreach ($this->parameters() as $value) {
  170. $data[] = trim(General::var_export($value, true, (is_array($value) ? 5 : 0)));
  171. }
  172. if(General::writeFile(
  173. $pathname,
  174. vsprintf(file_get_contents($this->getTemplate()), $data),
  175. Symphony::Configuration()->core()->symphony->{'file-write-mode'}
  176. )){
  177. if($editing !== false && $editing != $this->handle) General::deleteFile(DATASOURCES . '/' . $editing . '.php');
  178. return $pathname;
  179. }
  180. $errors->append('write', __('Failed to write datasource "%s" to disk.', array($filename)));
  181. }
  182. throw new DataSourceException('Errors were encountered whilst attempting to save.');
  183. }
  184. public function delete($datasource){
  185. /*
  186. TODO:
  187. Upon deletion of the event, views need to be updated to remove
  188. it's associated with the event
  189. */
  190. if(!$datasource instanceof DataSource) {
  191. $datasource = Datasource::loadFromHandle($datasource);
  192. }
  193. $handle = $datasource->handle;
  194. if(!$datasource->allowEditorToParse()) {
  195. throw new DataSourceException(__('Datasource cannot be deleted, the Editor does not have permission.'));
  196. }
  197. return General::deleteFile(DATASOURCES . "/{$handle}.php");
  198. }
  199. public function emptyXMLSet(DOMElement $root){
  200. if(is_null($root)) {
  201. throw new DataSourceException('No valid DOMDocument present');
  202. }
  203. else {
  204. $root->appendChild(
  205. $root->ownerDocument->createElement('error', __('No records found.'))
  206. );
  207. }
  208. }
  209. public static function determineFilterType($string){
  210. return (strpos($string, '+') !== false ? DataSource::FILTER_AND : DataSource::FILTER_OR);
  211. }
  212. public static function prepareFilterValue($value, Register $ParameterOutput=NULL, &$filterOperationType=DataSource::FILTER_OR){
  213. if(strlen(trim($value)) == 0) return NULL;
  214. if(is_array($value)) {
  215. foreach($value as $k => $v) {
  216. $value[$k] = self::prepareFilterValue($v, $ParameterOutput, $filterOperationType);
  217. }
  218. }
  219. else {
  220. $value = self::replaceParametersInString($value, $ParameterOutput);
  221. $filterOperationType = self::determineFilterType($value);
  222. $pattern = ($filterOperationType == DataSource::FILTER_AND ? '\+' : '(?<!\\\\),');
  223. // This is where the filter value is split by commas or + symbol, denoting
  224. // this as an OR or AND operation. Comma's have already been escaped
  225. $value = preg_split("/{$pattern}\s*/", $value, -1, PREG_SPLIT_NO_EMPTY);
  226. $value = array_map('trim', $value);
  227. // Remove the escapes on commas
  228. $value = array_map(array('General', 'removeEscapedCommas'), $value);
  229. // Pre-escape the filter values. TODO: Should this be here?
  230. $value = array_map(array(Symphony::Database(), 'escape'), $value);
  231. }
  232. return $value;
  233. }
  234. /*
  235. ** Given a string that may contain params in form of {$param}
  236. ** resolve the tokens with their values
  237. **
  238. ** This checks both the Frontend Parameters and Datasource
  239. ** Registers.
  240. */
  241. public static function replaceParametersInString($string, Register $DataSourceParameterOutput = null) {
  242. if(strlen(trim($string)) == 0) return null;
  243. if(preg_match_all('@{([^}]+)}@i', $string, $matches, PREG_SET_ORDER)){
  244. foreach($matches as $match){
  245. list($source, $cleaned) = $match;
  246. $replacement = NULL;
  247. $bits = preg_split('/:/', $cleaned, -1, PREG_SPLIT_NO_EMPTY);
  248. foreach($bits as $param) {
  249. if($param{0} != '$') {
  250. $replacement = $param;
  251. break;
  252. }
  253. $param = trim($param, '$');
  254. $replacement = self::resolveParameter($param, $DataSourceParameterOutput);
  255. if(is_array($replacement)){
  256. $replacement = array_map(array('General', 'escapeCommas'), $replacement);
  257. if(count($replacement) > 1) $replacement = implode(',', $replacement);
  258. else $replacement = end($replacement);
  259. }
  260. if(!is_null($replacement)) break;
  261. }
  262. $string = str_replace($source, $replacement, $string);
  263. }
  264. }
  265. return $string;
  266. }
  267. public static function resolveParameter($param, Register $DataSourceParameterOutput = null) {
  268. // TODO: Allow resolveParamter to check the stack, ie: $ds-blog-tag:$ds-blog-id
  269. $param = trim($param, '$');
  270. if(isset(Frontend::Parameters()->{$param})) return Frontend::Parameters()->{$param}->value;
  271. if(isset($DataSourceParameterOutput->{$param})) return $DataSourceParameterOutput->{$param}->value;
  272. return null;
  273. }
  274. }