* @license PHP License * @package WB */ /** * Load classes */ WBClass::load('WBDatasource_Newsletter'); /** * Wombat Newsletter System * * @version 1.7.1 * @package WB */ class WBDatasource_Newsletter_Subscriber extends WBDatasource_Newsletter { const OBSCURE_CODE_LENGTH = 4; const OBSCURE_FIELD_NAME = 'obscure'; /** * Current subscriber data * @var array */ private $sub = array(); /** * Current subscriber topics * @var array */ private $topic = array(); /** * Primary Key * @param string */ private $pSub = null; /** * Current User * @var WBUser */ private $user; /** * Receiver * @var WBDatasource_Newsletter_Receiver */ private $rcpt; /** * 2nd Constructor * * @param array */ protected function init($parameter = array()) { $this->pSub = $this->table->getIdentifier(WBDatasource_Newsletter::TABLE_SUBSCRIBER); } /** * Get Name Of Primary key * * @return string */ public function getIdentifier() { return $this->pSub; } /** * Inject User Object * * @param WBUser */ public function setUser($user) { $this->user = $user; } /** * Load Subscriber By E-Mail-Address * * Return subscriber's id on success, false otherwise * * @param string * @param string id of loaded subscriber or false */ public function loadByEmail($email) { // don't load twice if (!empty($this->sub[$this->pSub]) && $email == $this->sub['email']) { return $this->sub[$this->pSub]; } $this->sub = array(); $this->topic = array(); $email = trim($email); $email = explode('@', $email); if (2 != count($email)) { return ''; } $clause = array(); $clause[] = array( 'field' => 'emaillocal', 'value' => $email[0] ); $clause[] = array( 'field' => 'emaildomain', 'value' => $email[1] ); $list = $this->table->get(WBDatasource_Newsletter::TABLE_SUBSCRIBER, null, null, $clause); if (1 != count($list)) { return false; } return $this->loadByRaw($list[0]); } /** * Load Subscriber By Id * * Return subscriber's id on success, false otherwise * * @param string * @param string id of loaded subscriber or false */ public function loadById($id) { // don't load twice if (!empty($this->sub[$this->pSub]) && $id == $this->sub[$this->pSub]) { return $id; } $this->sub = array(); $this->topic = array(); $list = $this->table->get(WBDatasource_Newsletter::TABLE_SUBSCRIBER, $id); if (1 != count($list)) { return false; } return $this->loadByRaw($list[0]); } /** * Load Subscriber By User Id * * Return subscriber's id on success, false otherwise * * @param string $user * @return string */ public function loadByUser($uid = null) { // get user's id from parameter or user objsect if (empty($uid)) { if (empty($this->user)) { return false; } $uid = $this->user->getId(); if (empty($uid)) { return false; } } $clause = array(); $clause[] = array( 'field' => $this->table->getIdentifier(WBDatasource::TABLE_USER), 'value' => $uid ); $list = $this->table->get(WBDatasource_Newsletter::TABLE_SUBSCRIBER, null, null, $clause); if (1 != count($list)) { return false; } return $this->loadByRaw($list[0]); } /** * Load Subscriber By Obscure Id * * Use obscure id to load. Return subscriber's id on success, false otherwise * * @param string * @return string */ public function loadByObscureId($oid) { $o = substr($oid, 0, self::OBSCURE_CODE_LENGTH); $i = substr($oid, self::OBSCURE_CODE_LENGTH); if (!$this->loadById($i)) { return false; } if ($o == $this->sub[self::OBSCURE_FIELD_NAME]) { return $i; } $this->sub = array(); $this->topic = array(); return false; } /** * Load Subscriber By Raw Data * * Return subscriber's id on success, false otherwise * * @param array * @return string */ public function loadByRaw($data) { if (!isset($data[$this->pSub])) { return false; } $this->topic = array(); $this->sub = $data; $this->sub['obscureid'] = $this->sub[self::OBSCURE_FIELD_NAME] . $this->sub[$this->pSub]; return $this->sub[$this->pSub]; } /** * Was Subscriber Loaded Properly * * @return bool */ public function isOK() { if (empty($this->sub)) { return false; } return true; } /** * Helper Method to Get Language 4 Subscriber * * @param array * @return string */ private function getLanguage4Subscriber($sub) { $default = $this->config->get('subscriber/default/lang', 'en_GB'); if (empty($sub['lang'])) { $sub['lang'] = $default; } $available = $this->getAvailableLanguages(); if (!in_array($sub['lang'], $available)) { $sub['lang'] = $default; return $default; } return $sub['lang']; } private function getSalutation() { if (!$this->isOK()) { return ''; } if (empty($this->rcpt)) { $this->rcpt = WBClass::create('WBDatasource_Newsletter_Receiver'); } return $this->rcpt->getSalutation4SubscriberData($this->sub); } /** * Get Subscriber's Profile * * @param bool extend data * @return array */ public function get($extended = false) { $sub = $this->sub; if (!$extended) { return $sub; } $sub['salutation'] = $this->getSalutation(); return $sub; } /** * Get List of Subscriber's Topics * * Receive list of topic ids * @param string mandatorId * @return array */ public function getTopic($mandatorId) { if (!$this->isOK()) { return array(); } if (!empty($this->topic)) { return $this->topic; } $this->topic = array(); $clause = array(); $clause[] = array( 'field' => $this->table->getIdentifier(WBDatasource::TABLE_MANDATOR, true), 'value' => $mandatorId ); $clause[] = array( 'field' => $this->pSub, 'value' => $this->sub[$this->pSub] ); $list = $this->table->get(WBDatasource_Newsletter::TABLE_NEWSLETTERSUBSCRIBERTOPIC, null, null, $clause); $pTop = $this->table->getIdentifier(WBDatasource_Newsletter::TABLE_NEWSLETTERTOPIC, true); foreach ($list as $l) { $this->topic[] = $l[$pTop]; } return $this->topic; } /** * Set Subcriber's Topics To All Topics Of Mandator * * @todo reconsider limit of 500 topics * @param string mandatorId */ public function setAllTopic($mandatorId) { if (!$this->isOK()) { return; } $this->flushTopic($mandatorId); $pMan = $this->table->getIdentifier(WBDatasource::TABLE_MANDATOR, true); $pTop = $this->table->getIdentifier(WBDatasource_Newsletter::TABLE_NEWSLETTERTOPIC, true); // add all topics to subcription $clause = array(); $clause[] = array( 'field' => $pMan, 'value' => $mandatorId ); // well, at least the first 500 $option = array('limit' => 500); $top = $this->table->get(WBDatasource_Newsletter::TABLE_NEWSLETTERTOPIC, null, null, $clause, $option); $save = array(); foreach ($top as $t) { $save[] = array( $pTop => $t[$pTop], $pMan => $mandatorId, $this->pSub => $this->sub[$this->pSub] ); } $this->table->save(WBDatasource_Newsletter::TABLE_NEWSLETTERSUBSCRIBERTOPIC, '__new', $save); } /** * Set Subcriber's Topics * * @param string mandatorId * @param array list of topics (ids) */ public function setTopic($mandatorId, $list) { if (!$this->isOK()) { return; } $this->flushTopic($mandatorId); // there are no topics if (empty($list)) { $this->topic = array(); return; } // save new topics $pMan = $this->table->getIdentifier(WBDatasource::TABLE_MANDATOR, true); $pTop = $this->table->getIdentifier(WBDatasource_Newsletter::TABLE_NEWSLETTERTOPIC, true); $save = array(); foreach ($list as $l) { $save[] = array( $this->pSub => $this->sub[$this->pSub], $pTop => $l, $pMan => $mandatorId ); } $this->table->save(WBDatasource_Newsletter::TABLE_NEWSLETTERSUBSCRIBERTOPIC, '__new', $save); $this->topic = $list; } /** * Romove All Subcriber's Topics * * @param string mandatorId */ private function flushTopic($mandatorId) { if (!$this->isOK()) { return; } $pMan = $this->table->getIdentifier(WBDatasource::TABLE_MANDATOR, true); // remove old topics $clause = array(); $clause[] = array( 'field' => $pMan, 'value' => $mandatorId ); $clause[] = array( 'field' => $this->pSub, 'value' => $this->sub[$this->pSub] ); $this->table->delete(WBDatasource_Newsletter::TABLE_NEWSLETTERSUBSCRIBERTOPIC, null, null, $clause); } /** * Get Subscriber's Id * * @return string */ public function getId() { if ($this->isOK()) { return $this->sub[$this->pSub]; } return ''; } /** * Get Subscriber's Obscure Id * * @return string */ public function getObscureId() { if ($this->isOK()) { return $this->sub['obscureid']; } return ''; } /** * Set Subscription Flag * * @param int 0 or 1 */ public function subscribe($sub = 1) { if (!$this->isOK()) { return false; } // normalize integer value $sub = intval($sub); $ts = 'subscribedtimestamp'; if (0 < $sub) { $sub = 1; } else { $ts = 'unsubscribedtimestamp'; $sub = 0; } if ($sub == $this->sub['subscribed']) { return true; } // update subscription $save = array( 'subscribed' => $sub, $ts => gmdate('Y-m-d H:i:s') ); // update internal buffer foreach ($save as $k => $v) { $this->sub[$k] = $v; } $this->table->save(WBDatasource_Newsletter::TABLE_SUBSCRIBER, $this->sub[$this->pSub], $save); WBEvent::trigger('newsletter:subscriber:subscription:' . $sub, 'Change newsletter subscriber subscription status', $this->sub); } /** * Remove Subscription Flag * */ public function unsubscribe() { $this->subscribe(0); } /** * Set Approved Flag * * Used for double opt in * @return bool true if approved */ public function approve() { if (!$this->isOK()) { return false; } if (0 < $this->sub['approved']) { return true; } // update flag $save = array( 'approved' => 1, 'approvedtimestamp' => gmdate('Y-m-d H:i:s') ); // update internal buffer foreach ($save as $k => $v) { $this->sub[$k] = $v; } $this->table->save(WBDatasource_Newsletter::TABLE_SUBSCRIBER, $this->sub[$this->pSub], $save); WBEvent::trigger('newsletter:subscriber:approved', 'Newsletter subscriber approved double-opt-in', $this->sub); return true; } /** * Add or Update Subscriber in Database * * Return subscriber's id or false * * @todo find user by e-mail and merge with user table * @param array * @param string $eventSuffix * @return string */ public function add($data, $eventSuffix = 'default') { $id = $this->addOnly($data); if (empty($id)) { return false; } WBClass::load('WBEvent'); WBEvent::trigger('newsletter:subscriber:added:' . $eventSuffix, 'Registered new newsletter subscriber {EMAIL}', $this->sub); return $id; } /** * Add Subscriber To Database And To Mandator * * Return subscriber's id or false * * @param string * @param array * @return string */ public function add2Mandator($mandatorId, $data, $eventSuffix = 'default') { $id = $this->addOnly($data); if (empty($id)) { return false; } $this->add4Mandator($mandatorId); WBClass::load('WBEvent'); $pMan = $this->table->getIdentifier(WBDatasource::TABLE_MANDATOR); $eData = $this->sub; $eData[$pMan] = $mandatorId; WBEvent::trigger('newsletter:subscriber:added:mandator:' . $eventSuffix, 'Registered new newsletter subscriber {EMAIL}', $eData); return $id; } /** * Add Subscriber To Database * * Return subscriber's id or false * * @param array * @param array override parameter to change default values * @return string */ public function addOnly($data, $override = array()) { $this->sub = array(); $save = array(); $old = $this->prepareSave4New($data, $save, $override); if (!$old) { return false; } if (is_array($old)) { $this->loadByRaw($old); return false; } else { $old = array(); } if (empty($save)) { return false; } $id = $this->table->save(WBDatasource_Newsletter::TABLE_SUBSCRIBER, '__new', $save); $raw = array_merge($old, $save); $raw['id'] = $id; $raw[$this->pSub] = $id; return $this->loadByRaw($raw); } /** * Prepare Data To Save * * Try to find subscriber by e-mail address. If exists return old data (array). * If something went wrong return false. If subscriber was not found, return true. * * Also prefill save array with subscriber's default values, override them and merge with data provided * * @param array * @param array target save data * @param array override default data * @return array|bool */ private function prepareSave4New($data, &$save, $override = array()) { if (empty($data['email'])) { if (empty($data['emailurlid'])) { return false; } // subscribe by emailurlid $dict = WBClass::create('WBDictionary_URL'); $dict->load($data['emailurlid']); $data['email'] = $dict->getWord(); } $email = explode('@', strtolower($data['email'])); $clause = array(); $clause[] = array( 'field' => 'emaillocal', 'value' => $email[0] ); $clause[] = array( 'field' => 'emaildomain', 'value' => $email[1] ); $old = $this->table->get(WBDatasource_Newsletter::TABLE_SUBSCRIBER, null, null, $clause); if (!empty($old)) { return $old[0]; } if (!is_array($override)) { $override = array(); } $save = $this->config->get('subscriber/default', array()); foreach ($save as $k => $v) { if (isset($override[$k])) { $save[$k] = $override[$k]; } } $save[self::OBSCURE_FIELD_NAME] = $this->mkRandomCode(); $save['email'] = $data['email']; $save['lang'] = $this->getLanguage4Subscriber($save); $pUser = $this->table->getIdentifier(WBDatasource::TABLE_USER); if (!empty($data[$pUser])) { $save[$pUser] = $data[$pUser]; } else if (!empty($this->user)) { $save[$pUser] = $this->user->getId(); } $save['subscribedtimestamp'] = gmdate('Y-m-d H:i:s'); $this->mergeSavable($save, $data); return true; } /** * Add Subscriber To Mandator * * Make subscriber available for mandator. Check if subscriber is in list for mandator. If not, add it. * * @param string mandator id * @param bool remove */ public function hasMandator($mId) { if (!$this->isOK()) { return; } if (empty($mId)) { $mId = 0; } $clause = array(); $clause[] = array( 'field' => $this->pSub, 'value' => $this->sub[$this->pSub] ); $pMan = $this->table->getIdentifier(WBDatasource::TABLE_MANDATOR); $clause[] = array( 'field' => $pMan, 'value' => $mId ); // check if subscription exists if (0 < $this->table->count(WBDatasource_Newsletter::TABLE_SUBSCRIBERMANDATOR, null, null, $clause)) { return true; } return false; } /** * Add Subscriber To Mandator * * Make subscriber available for mandator. Check if subscriber is in list for mandator. If not, add it. * * @param string mandator id * @param bool remove */ public function add4Mandator($mId, $rm = false) { if (!$this->isOK()) { return; } if (empty($mId)) { $mId = 0; } if (0 != $mId && 1 != $this->table->count(WBDatasource::TABLE_MANDATOR, $mId)) { return; } $clause = array(); $clause[] = array( 'field' => $this->pSub, 'value' => $this->sub[$this->pSub] ); $pMan = $this->table->getIdentifier(WBDatasource::TABLE_MANDATOR); $clause[] = array( 'field' => $pMan, 'value' => $mId ); // unsubscribe only if ($rm) { // remove relation to mandator to unsubscribe $this->table->delete(WBDatasource_Newsletter::TABLE_SUBSCRIBERMANDATOR, null, null, $clause); // remove topic subscription, as well to keep database clean $this->table->delete(WBDatasource_Newsletter::TABLE_NEWSLETTERSUBSCRIBERTOPIC, null, null, $clause); return; } // check if subscription exists if (0 < $this->table->count(WBDatasource_Newsletter::TABLE_SUBSCRIBERMANDATOR, null, null, $clause)) { return; } // finally subscribe to mandator $save = array( $this->pSub => $this->sub[$this->pSub], $pMan => $mId ); $this->table->save(WBDatasource_Newsletter::TABLE_SUBSCRIBERMANDATOR, '__new', $save); } /** * Remove Subscriber Of Mandator * * @param string mandator id */ public function rm4Mandator($mId) { $this->add4Mandator($mId, true); } /** * Get List of Mandators * * @todo think about limit * @param bool * @param string * @return array */ public function getMandatorList($withSubscription = true, $search = '') { if (!$this->isOK()) { return array(); } $clause = array(); $clause[] = array( 'field' => 'enabled', 'value' => 1 ); $clause[] = array( 'table' => WBDatasource_Newsletter::TABLE_SUBSCRIBERMANDATOR, 'field' => $this->pSub, 'value' => $this->sub[$this->pSub] ); $option = array(); $option['limit'] = 100; $option['join'] = array(); $option['join'][] = array( 'table' => WBDatasource_Newsletter::TABLE_SUBSCRIBERMANDATOR, 'using' => '__auto', ); if ($withSubscription) { return $this->table->get(WBDatasource::TABLE_MANDATOR, null, null, $clause, $option); } // subscriptions $man = $this->table->getIds(WBDatasource::TABLE_MANDATOR, null, $clause, $option); $clause = array(); $clause[] = array( 'field' => 'enabled', 'value' => 1 ); $clause[] = array( 'field' => 'newsletter', 'value' => 1 ); if (!empty($man)) { $clause[] = array( 'field' => $this->table->getIdentifier(WBDatasource::TABLE_MANDATOR, true), 'value' => $man, 'relation' => 'not_in' ); } $option = array(); $option['limit'] = 100; return $this->table->get(WBDatasource::TABLE_MANDATOR, null, null, $clause, $option); } /** * Add Source to Subscriber * * Store relation to other table for subscriber. Extract MandatorId from xid, use 0 as default * * @param string $namespace * @param string $xid * @return bool true on success */ public function addSource($namespace, $xId) { if (!$this->isOK()) { return false; } $xId = explode('-', $xId); if (1 < count($xId)) { $mandatorId = $xId[0]; $xId = $xId[1]; } $pMan = $this->table->getIdentifier(WBDatasource::TABLE_MANDATOR, true); // make sure namespace with same id exists only once $clause = array(); $clause[] = array( 'field' => $this->pSub, 'value' => $this->getId() ); $clause[] = array( 'field' => 'namespace', 'value' => $namespace ); $clause[] = array( 'field' => 'xid', 'value' => $xId ); $clause[] = array( 'field' => $pMan, 'value' => $mandatorId ); $this->table->delete(WBDatasource_Newsletter::TABLE_SUBSCRIBERSOURCE, null, null, $clause); $rel = array( $this->pSub => $this->getId(), 'namespace' => $namespace, 'xid' => $xId, $pMan => $mandatorId ); $this->table->save(WBDatasource_Newsletter::TABLE_SUBSCRIBERSOURCE, '__new', $rel); return true; } /** * Update Subscriber's Profile * * If subscriber was imported, subscribedtimestamp may be still invalid. * If subcription status is subscribed, make subscribedtimestamp a valid date. * This verfifies subscription if someone changes her profile. * * @param array */ public function update($data) { if (!$this->isOK()) { return; } if (isset($data['newslettertopiclist'])) { $this->setTopic($data['newslettertopiclist']); } $save = array(); $this->mergeSavable($save, $data); // don't save empty data if (empty($save)) { return; } // set subscribed timestamp, if not set yet if (0 < $this->sub['subscribed'] && '1970-01-01 00:00:00' == $this->sub['subscribedtimestamp']) { $save['subscribedtimestamp'] = gmdate('Y-m-d H:i:s'); } // update internal buffer $this->table->save(WBDatasource_Newsletter::TABLE_SUBSCRIBER, $this->sub[$this->pSub], $save); $this->sub = array_merge($this->sub, $save); } /** * Merge Subscriber Data for Save * * Only save a predefined fields in newsletter subscriber table * * @param array $save * @param array $data to update */ private function mergeSavable(&$save, $data) { $keys = $this->config->get('subscriber/savable', array()); // don't save empty language if (empty($data['lang'])) { unset($data['lang']); } foreach ($keys as $k) { // select in data if (!isset($data[$k])) { continue; } // only changed data if (isset($this->sub[$k]) && $this->sub[$k] == $data[$k]) { continue; } $save[$k] = $data[$k]; } // two letter language code if (!empty($save['lang'])) { $save['lang'] = substr($save['lang'], 0, 2); } } /** * Generate Random String * * Make random string to be added as obscure code * @return string */ public function mkRandomCode() { $chars = range('a', 'z'); $last = count($chars) - 1; $rnd = ''; for ($i = 0; $i < self::OBSCURE_CODE_LENGTH; ++$i) { $rnd .= $chars[rand(0, $last)]; } return $rnd; } }