Qurl.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. App::uses('ToolsAppModel', 'Tools.Model');
  3. App::uses('Hash', 'Utility');
  4. /**
  5. * Manage Quick Urls
  6. *
  7. * @author Mark Scherer
  8. * @license http://opensource.org/licenses/mit-license.php MIT
  9. */
  10. class Qurl extends ToolsAppModel {
  11. public $displayField = 'key';
  12. public $scaffoldSkipFields = ['note', 'key', 'content'];
  13. public $order = ['Qurl.created' => 'DESC'];
  14. protected $defaultLength = 22;
  15. protected $validity = YEAR;
  16. public $validate = [
  17. 'key' => [
  18. 'notEmpty' => [
  19. 'rule' => ['notEmpty'],
  20. 'message' => 'valErrMandatoryField',
  21. 'last' => true,
  22. ],
  23. 'isUnique' => [
  24. 'rule' => ['isUnique'],
  25. 'message' => 'valErrQurlKeyExists',
  26. ],
  27. ],
  28. 'url' => [
  29. 'notEmpty' => [
  30. 'rule' => ['notEmpty'],
  31. 'message' => 'valErrMandatoryField',
  32. 'last' => true
  33. ],
  34. 'validateUrl' => [
  35. 'rule' => ['validateUrl', ['deep' => false, 'sameDomain' => true, 'autoComplete' => true]],
  36. 'message' => 'valErrInvalidQurlUrl',
  37. 'last' => true
  38. ]
  39. ],
  40. 'note' => [
  41. ],
  42. 'comment' => [
  43. ],
  44. 'used' => [
  45. 'numeric' => [
  46. 'rule' => ['numeric'],
  47. 'message' => 'valErrMandatoryField',
  48. ],
  49. ],
  50. ];
  51. public function beforeValidate($options = []) {
  52. parent::beforeValidate($options);
  53. if (isset($this->data[$this->alias]['key']) && empty($this->data[$this->alias]['key'])) {
  54. $length = null;
  55. if (!empty($this->data[$this->alias]['key_length'])) {
  56. $length = $this->data[$this->alias]['key_length'];
  57. }
  58. $this->data[$this->alias]['key'] = $this->generateKey($length);
  59. }
  60. return true;
  61. }
  62. public function beforeSave($options = []) {
  63. parent::beforeSave($options);
  64. if (isset($this->data[$this->alias]['content'])) {
  65. $this->data[$this->alias]['content'] = serialize($this->data[$this->alias]['content']);
  66. }
  67. return true;
  68. }
  69. /**
  70. * Qurl::translate()
  71. *
  72. * @param mixed $key
  73. * @return array
  74. */
  75. public function translate($key) {
  76. $res = $this->find('first', ['conditions' => [$this->alias . '.key' => $key, $this->alias . '.active' => 1]]);
  77. if (!$res) {
  78. return false;
  79. }
  80. if ($res[$this->alias]['content']) {
  81. $res[$this->alias]['content'] = unserialize($res[$this->alias]['content']);
  82. } else {
  83. $res[$this->alias]['content'] = [];
  84. }
  85. return $res;
  86. }
  87. /**
  88. * Form the access url by key
  89. *
  90. * @param string $key
  91. * @return string Url (absolute)
  92. */
  93. public static function urlByKey($key, $title = null, $slugTitle = true) {
  94. if ($title && $slugTitle) {
  95. $title = Inflector::slug($title, '-');
  96. }
  97. return Router::url(['admin' => false, 'plugin' => 'tools', 'controller' => 'qurls', 'action' => 'go', $key, $title], true);
  98. }
  99. /**
  100. * Makes an absolute url string ready to input anywhere.
  101. * Uses generate() internally to get the key.
  102. *
  103. * @param mixed $url
  104. * @return string Url (absolute)
  105. */
  106. public function url($url, $data = []) {
  107. $key = $this->generate($url, $data);
  108. if (!$key) {
  109. return false;
  110. }
  111. return $this->urlByKey($key);
  112. }
  113. /**
  114. * Generates a Qurl key
  115. *
  116. * @param mixed $url
  117. * @param string $uid
  118. * @return string Key
  119. */
  120. public function generate($url, $data = []) {
  121. $url = Router::url($url, true);
  122. $content = array_merge($data, ['url' => $url]);
  123. if (!isset($content['key'])) {
  124. $content['key'] = '';
  125. }
  126. $res = $this->save($content);
  127. if (!$res) {
  128. return false;
  129. }
  130. return $res[$this->alias]['key'];
  131. }
  132. /**
  133. * Sets Key to "used" (only once!) - directly by ID
  134. *
  135. * @param id of key to spend: necessary
  136. * @return bool Success
  137. */
  138. public function markAsUsed($id = null) {
  139. if (empty($id)) {
  140. return false;
  141. }
  142. //$this->id = $id;
  143. if ($this->updateAll([$this->alias . '.used' => $this->alias . '.used + 1', $this->alias . '.last_used' => '"' . date(FORMAT_DB_DATETIME) . '"'], [$this->alias . '.id' => $id])) {
  144. return true;
  145. }
  146. return false;
  147. }
  148. /**
  149. * Remove old/invalid keys
  150. * does not remove recently used ones (for proper feedback)!
  151. *
  152. * @return bool success
  153. */
  154. public function garbageCollector() {
  155. $conditions = [
  156. $this->alias . '.created <' => date(FORMAT_DB_DATETIME, time() - $this->validity),
  157. ];
  158. return $this->deleteAll($conditions, false);
  159. }
  160. /**
  161. * Get admin stats
  162. *
  163. * @return array
  164. */
  165. public function stats() {
  166. $keys = [];
  167. $keys['unused_valid'] = $this->find('count', ['conditions' => [$this->alias . '.used' => 0, $this->alias . '.created >=' => date(FORMAT_DB_DATETIME, time() - $this->validity)]]);
  168. $keys['used_valid'] = $this->find('count', ['conditions' => [$this->alias . '.used' => 1, $this->alias . '.created >=' => date(FORMAT_DB_DATETIME, time() - $this->validity)]]);
  169. $keys['unused_invalid'] = $this->find('count', ['conditions' => [$this->alias . '.used' => 0, $this->alias . '.created <' => date(FORMAT_DB_DATETIME, time() - $this->validity)]]);
  170. $keys['used_invalid'] = $this->find('count', ['conditions' => [$this->alias . '.used' => 1, $this->alias . '.created <' => date(FORMAT_DB_DATETIME, time() - $this->validity)]]);
  171. $urls = $this->find('all', ['conditions' => [], 'fields' => ['DISTINCT url']]);
  172. $keys['urls'] = !empty($urls) ? Hash::extract('{n}.' . $this->alias . '.url', $urls) : [];
  173. return $keys;
  174. }
  175. /**
  176. * Generate key
  177. *
  178. * @param length (defaults to defaultLength)
  179. * @return string codekey
  180. */
  181. public function generateKey($length = null) {
  182. if (empty($length)) {
  183. $length = $this->defaultLength;
  184. }
  185. App::uses('RandomLib', 'Tools.Lib');
  186. return RandomLib::generatePassword($length);
  187. }
  188. }