PageRenderTime 53ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/extension.driver.php

https://github.com/ophentis/members
PHP | 999 lines | 585 code | 141 blank | 273 comment | 112 complexity | a2e5bf588fb4a8dc07744b7cdb8e6a3d MD5 | raw file
  1. <?php
  2. include_once(TOOLKIT . '/class.entrymanager.php');
  3. include_once(TOOLKIT . '/class.sectionmanager.php');
  4. include_once(EXTENSIONS . '/members/lib/class.role.php');
  5. include_once(EXTENSIONS . '/members/lib/class.members.php');
  6. Class extension_Members extends Extension {
  7. /**
  8. * @var boolean $initialised
  9. */
  10. private static $initialised = false;
  11. /**
  12. * @var integer $members_section
  13. */
  14. private static $members_section = null;
  15. /**
  16. * @var array $member_fields
  17. */
  18. private static $member_fields = array(
  19. 'memberusername', 'memberemail'
  20. );
  21. /**
  22. * @var array $member_events
  23. */
  24. private static $member_events = array(
  25. 'members_regenerate_activation_code',
  26. 'members_activate_account',
  27. 'members_generate_recovery_code',
  28. 'members_reset_password'
  29. );
  30. /**
  31. * Holds the current Member class that is in place, for this release
  32. * this will always the SymphonyMember, which extends Member and implements
  33. * the Member interface.
  34. *
  35. * @see getMemberDriver()
  36. * @var Member $Member
  37. */
  38. protected $Member = null;
  39. /**
  40. * Accessible via `getField()`
  41. *
  42. * @see getField()
  43. * @var array $fields
  44. */
  45. protected static $fields = array();
  46. /**
  47. * Accessible via `getFieldHandle()`
  48. *
  49. * @see getFieldHandle()
  50. * @var array $handles
  51. */
  52. protected static $handles = array();
  53. /**
  54. * Returns an associative array of errors that have occurred while
  55. * logging in or preforming a Members custom event. The key is the
  56. * field's `element_name` and the value is the error message.
  57. *
  58. * @var array $_errors
  59. */
  60. public static $_errors = array();
  61. /**
  62. * By default this is set to `false`. If a Member attempts to login
  63. * but is unsuccessful, this will be `true`. Useful in the
  64. * `appendLoginStatusToEventXML` function to determine whether to display
  65. * `extension_Members::$_errors` or not.
  66. *
  67. * @var boolean $failed_login_attempt
  68. */
  69. public static $_failed_login_attempt = false;
  70. /**
  71. * An instance of the EntryManager class. Keep in mind that the Field Manager
  72. * and Section Manager are accessible via `$entryManager->fieldManager` and
  73. * `$entryManager->sectionManager` respectively.
  74. *
  75. * @var EntryManager $entryManager
  76. */
  77. public static $entryManager = null;
  78. /**
  79. * Only create a Member object on the Frontend of the site.
  80. * There is no need to create this in the Administration context
  81. * as authenticated users are Authors and are handled by Symphony,
  82. * not this extension.
  83. */
  84. public function __construct() {
  85. if(class_exists('Symphony') && Symphony::Engine() instanceof Frontend) {
  86. $this->Member = new SymphonyMember($this);
  87. }
  88. extension_Members::$entryManager = new EntryManager(Symphony::Engine());
  89. if(!extension_Members::$initialised) {
  90. extension_Members::initialise();
  91. }
  92. }
  93. /**
  94. * Loops over the configuration to detect the capabilities of this
  95. * Members setup. Populates two array's, one for Field objects, and
  96. * one for Field handles.
  97. */
  98. public static function initialise() {
  99. extension_Members::$initialised = true;
  100. $sectionManager = extension_Members::$entryManager->sectionManager;
  101. $membersSectionSchema = array();
  102. if(
  103. !is_null(extension_Members::getMembersSection()) &&
  104. is_numeric(extension_Members::getMembersSection())
  105. ) {
  106. $membersSectionSchema = $sectionManager->fetch(
  107. extension_Members::getMembersSection()
  108. )->fetchFieldsSchema();
  109. }
  110. foreach($membersSectionSchema as $field) {
  111. if($field['type'] == 'membertimezone') {
  112. extension_Members::initialiseField($field, 'timezone');
  113. continue;
  114. }
  115. if($field['type'] == 'memberrole') {
  116. extension_Members::initialiseField($field, 'role');
  117. continue;
  118. }
  119. if($field['type'] == 'memberactivation') {
  120. extension_Members::initialiseField($field, 'activation');
  121. continue;
  122. }
  123. if($field['type'] == 'memberusername') {
  124. extension_Members::initialiseField($field, 'identity');
  125. continue;
  126. }
  127. if($field['type'] == 'memberemail') {
  128. extension_Members::initialiseField($field, 'email');
  129. continue;
  130. }
  131. if($field['type'] == 'memberpassword') {
  132. extension_Members::initialiseField($field, 'authentication');
  133. continue;
  134. }
  135. }
  136. }
  137. private static function initialiseField($field, $name) {
  138. extension_Members::$fields[$name] = extension_Members::$entryManager->fieldManager->fetch($field['id']);
  139. if(extension_Members::$fields[$name] instanceof Field) {
  140. extension_Members::$handles[$name] = $field['element_name'];
  141. }
  142. }
  143. public function about(){
  144. return array(
  145. 'name' => 'Members',
  146. 'version' => '1.0',
  147. 'release-date' => 'June 1st 2011',
  148. 'author' => array(
  149. 'name' => 'Symphony Team',
  150. 'website' => 'http://www.symphony-cms.com',
  151. 'email' => 'team@symphony-cms.com'
  152. ),
  153. 'description' => 'Frontend Membership extension for Symphony CMS'
  154. );
  155. }
  156. public function fetchNavigation(){
  157. return array(
  158. array(
  159. 'location' => __('System'),
  160. 'name' => __('Member Roles'),
  161. 'link' => '/roles/'
  162. )
  163. );
  164. }
  165. public function getSubscribedDelegates(){
  166. return array(
  167. /*
  168. FRONTEND
  169. */
  170. array(
  171. 'page' => '/frontend/',
  172. 'delegate' => 'FrontendPageResolved',
  173. 'callback' => 'checkFrontendPagePermissions'
  174. ),
  175. array(
  176. 'page' => '/frontend/',
  177. 'delegate' => 'FrontendParamsResolve',
  178. 'callback' => 'addMemberDetailsToPageParams'
  179. ),
  180. array(
  181. 'page' => '/frontend/',
  182. 'delegate' => 'FrontendProcessEvents',
  183. 'callback' => 'appendLoginStatusToEventXML'
  184. ),
  185. array(
  186. 'page' => '/frontend/',
  187. 'delegate' => 'EventPreSaveFilter',
  188. 'callback' => 'checkEventPermissions'
  189. ),
  190. array(
  191. 'page' => '/frontend/',
  192. 'delegate' => 'EventPostSaveFilter',
  193. 'callback' => 'processPostSaveFilter'
  194. ),
  195. /*
  196. BACKEND
  197. */
  198. array(
  199. 'page' => '/backend/',
  200. 'delegate' => 'AdminPagePreGenerate',
  201. 'callback' => 'appendAssets'
  202. ),
  203. array(
  204. 'page' => '/system/preferences/',
  205. 'delegate' => 'AddCustomPreferenceFieldsets',
  206. 'callback' => 'appendPreferences'
  207. ),
  208. array(
  209. 'page' => '/system/preferences/',
  210. 'delegate' => 'Save',
  211. 'callback' => 'savePreferences'
  212. ),
  213. array(
  214. 'page' => '/blueprints/events/new/',
  215. 'delegate' => 'AppendEventFilter',
  216. 'callback' => 'appendFilter'
  217. ),
  218. array(
  219. 'page' => '/blueprints/events/edit/',
  220. 'delegate' => 'AppendEventFilter',
  221. 'callback' => 'appendFilter'
  222. ),
  223. );
  224. }
  225. /*-------------------------------------------------------------------------
  226. Versioning:
  227. -------------------------------------------------------------------------*/
  228. /**
  229. * Sets the `cookie-prefix` of `sym-members` in the Configuration
  230. * and creates all of the field's tables in the database
  231. *
  232. * @return boolean
  233. */
  234. public function install(){
  235. Symphony::Configuration()->set('cookie-prefix', 'sym-members', 'members');
  236. Administration::instance()->saveConfig();
  237. return Symphony::Database()->import("
  238. DROP TABLE IF EXISTS `tbl_members_roles`;
  239. CREATE TABLE `tbl_members_roles` (
  240. `id` int(11) unsigned NOT NULL auto_increment,
  241. `name` varchar(255) NOT NULL,
  242. `handle` varchar(255) NOT NULL,
  243. PRIMARY KEY (`id`),
  244. UNIQUE KEY `handle` (`handle`)
  245. ) ENGINE=MyISAM;
  246. INSERT INTO `tbl_members_roles` VALUES(1, 'Public', 'public');
  247. DROP TABLE IF EXISTS `tbl_members_roles_event_permissions`;
  248. CREATE TABLE `tbl_members_roles_event_permissions` (
  249. `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  250. `role_id` int(11) unsigned NOT NULL,
  251. `event` varchar(50) NOT NULL,
  252. `action` varchar(60) NOT NULL,
  253. `level` smallint(1) unsigned NOT NULL DEFAULT '0',
  254. PRIMARY KEY (`id`),
  255. KEY `role_id` (`role_id`,`event`,`action`)
  256. ) ENGINE=MyISAM;
  257. DROP TABLE IF EXISTS `tbl_members_roles_forbidden_pages`;
  258. CREATE TABLE `tbl_members_roles_forbidden_pages` (
  259. `id` int(11) unsigned NOT NULL auto_increment,
  260. `role_id` int(11) unsigned NOT NULL,
  261. `page_id` int(11) unsigned NOT NULL,
  262. PRIMARY KEY (`id`),
  263. KEY `role_id` (`role_id`,`page_id`)
  264. ) ENGINE=MyISAM;
  265. ");
  266. }
  267. /**
  268. * Remove's all `members` Configuration values and then drops all the
  269. * database tables created by the Members extension
  270. *
  271. * @return boolean
  272. */
  273. public function uninstall(){
  274. Symphony::Configuration()->remove('members');
  275. Administration::instance()->saveConfig();
  276. return Symphony::Database()->query("
  277. DROP TABLE IF EXISTS
  278. `tbl_fields_memberusername`,
  279. `tbl_fields_memberpassword`,
  280. `tbl_fields_memberemail`,
  281. `tbl_fields_memberactivation`,
  282. `tbl_fields_memberrole`,
  283. `tbl_fields_membertimezone`,
  284. `tbl_members_roles`,
  285. `tbl_members_roles_event_permissions`,
  286. `tbl_members_roles_forbidden_pages`
  287. ");
  288. }
  289. public function update($previousVersion) {
  290. if(version_compare($previousVersion, '1.0 Beta 3', '<')) {
  291. $activation_table = Symphony::Database()->fetchRow(0, "SHOW TABLES LIKE 'tbl_fields_memberactivation';");
  292. if(!empty($activation_table)) {
  293. Symphony::Database()->import("
  294. ALTER TABLE `tbl_fields_memberactivation` ADD `auto_login` ENUM('yes','no') NULL DEFAULT 'yes';
  295. ALTER TABLE `tbl_fields_memberactivation` ADD `deny_login` ENUM('yes','no') NULL DEFAULT 'yes';
  296. ");
  297. }
  298. $password_table = Symphony::Database()->fetchRow(0, "SHOW TABLES LIKE 'tbl_fields_memberpassword';");
  299. if(!empty($password_table)) {
  300. Symphony::Database()->query("
  301. ALTER TABLE `tbl_fields_memberpassword` ADD `code_expiry` VARCHAR(50) NOT NULL;
  302. ");
  303. }
  304. }
  305. if(version_compare($previousVersion, '1.0RC1', '<')) {
  306. // Move the auto_login setting from the Activation Field to the config
  307. if($field = extension_Members::getField('activation') && $field instanceof Field) {
  308. Symphony::Configuration()->set('activate-account-auto-login', $field->get('auto_login'));
  309. $activation_table = Symphony::Database()->fetchRow(0, "SHOW TABLES LIKE 'tbl_fields_memberactivation';");
  310. if(!empty($activation_table)) {
  311. Symphony::Database()->query("
  312. ALTER TABLE `tbl_fields_memberpassword` DROP `auto_login`;
  313. ");
  314. }
  315. }
  316. // These are now loaded dynamically
  317. Symphony::Configuration()->remove('timezone', 'members');
  318. Symphony::Configuration()->remove('role', 'members');
  319. Symphony::Configuration()->remove('activation', 'members');
  320. Symphony::Configuration()->remove('identity', 'members');
  321. Symphony::Configuration()->remove('email', 'members');
  322. Symphony::Configuration()->remove('authentication', 'members');
  323. Administration::instance()->saveConfig();
  324. }
  325. }
  326. /*-------------------------------------------------------------------------
  327. Utilities:
  328. -------------------------------------------------------------------------*/
  329. public static function baseURL(){
  330. return SYMPHONY_URL . '/extension/members/';
  331. }
  332. /**
  333. * Returns an instance of the currently logged in Member, which is a `Entry` object.
  334. * If there is no logged in Member, this function will return `null`
  335. *
  336. * @return Member
  337. */
  338. public function getMemberDriver() {
  339. return $this->Member;
  340. }
  341. /**
  342. * Given a `$handle`, this function will return a value from the Members
  343. * configuration. Typically this is an shortcut accessor to
  344. * `Symphony::Configuration()->get($handle, 'members')`. If no `$handle`
  345. * is given this function will return all the configuration values for
  346. * the Members extension as an array.
  347. *
  348. * @param string $handle
  349. * @return mixed
  350. */
  351. public static function getSetting($handle = null) {
  352. if(is_null($handle)) return Symphony::Configuration()->get('members');
  353. $value = Symphony::Configuration()->get($handle, 'members');
  354. return ((is_numeric($value) && $value == 0) || is_null($value) || empty($value)) ? null : $value;
  355. }
  356. /**
  357. * Shortcut accessor for the active Members Section. This function
  358. * caches the result of the `getSetting('section')`.
  359. *
  360. * @return integer
  361. */
  362. public static function getMembersSection() {
  363. if(is_null(extension_Members::$members_section)) {
  364. extension_Members::$members_section = extension_Members::getSetting('section');
  365. }
  366. return extension_Members::$members_section;
  367. }
  368. /**
  369. * Where `$name` is one of the following values, `role`, `timezone`,
  370. * `email`, `activation`, `authentication` and `identity`, this function
  371. * will return a Field instance. Typically this allows extensions to access
  372. * the Fields that are currently being used in the active Members section.
  373. * If no `$name` is given, an array of all Member fields will be returned.
  374. *
  375. * @param string $name
  376. * @return Field
  377. */
  378. public static function getField($name = null) {
  379. if(is_null($name)) return extension_Members::$fields;
  380. if(!isset(extension_Members::$fields[$name])) return null;
  381. return extension_Members::$fields[$name];
  382. }
  383. /**
  384. * Where `$name` is one of the following values, `role`, `timezone`,
  385. * `email`, `activation`, `authentication` and `identity`, this function
  386. * will return the Field's `element_name`. `element_name` is a handle
  387. * of the Field's label, used most commonly by events in `$_POST` data.
  388. * If no `$name` is given, an array of all Member field handles will
  389. * be returned.
  390. *
  391. * @param string $name
  392. * @return string
  393. */
  394. public static function getFieldHandle($name = null) {
  395. if(is_null($name)) return extension_Members::$handles;
  396. if(!isset(extension_Members::$handles[$name])) return null;
  397. return extension_Members::$handles[$name];
  398. }
  399. /**
  400. * This function will adjust the locale for the currently logged in
  401. * user if the active Member section has a Member: Timezone field.
  402. *
  403. * @param integer $member_id
  404. * @return void
  405. */
  406. public function __updateSystemTimezoneOffset($member_id) {
  407. $timezone = extension_Members::getField('timezone');
  408. if(!$timezone instanceof fieldMemberTimezone) return;
  409. $tz = $timezone->getMemberTimezone($member_id);
  410. if(is_null($tz)) return;
  411. try {
  412. DateTimeObj::setDefaultTimezone($tz);
  413. }
  414. catch(Exception $ex) {
  415. Symphony::$Log->pushToLog(__('Members Timezone') . ': ' . $ex->getMessage(), $code, true);
  416. }
  417. }
  418. /**
  419. * Given an array of grouped options ready for use in `Widget::Select`
  420. * loop over all the options and compare the value to configuration value
  421. * (as specified by `$handle`) and if it matches, set that option to true
  422. *
  423. * @param array $options
  424. * @param string $handle
  425. * @return array
  426. */
  427. public static function setActiveTemplate(array $options, $handle) {
  428. $templates = explode(',', extension_Members::getSetting($handle));
  429. foreach($options as $index => $ext) {
  430. foreach($ext['options'] as $key => $opt) {
  431. if(in_array($opt[0], $templates)) {
  432. $options[$index]['options'][$key][1] = true;
  433. }
  434. }
  435. }
  436. array_unshift($options, array(null,false,null));
  437. return $options;
  438. }
  439. /**
  440. * The Members extension provides a number of filters for users to add their
  441. * events to do various functionality. This negates the need for custom events
  442. *
  443. * @uses AppendEventFilter
  444. */
  445. public function appendFilter($context) {
  446. $selected = !is_array($context['selected']) ? array() : $context['selected'];
  447. // Add Member: Lock Role filter
  448. $context['options'][] = array(
  449. 'member-lock-role',
  450. in_array('member-lock-role', $selected),
  451. __('Members: Lock Role')
  452. );
  453. if(!is_null(extension_Members::getFieldHandle('activation')) && !is_null(extension_Members::getFieldHandle('email'))) {
  454. // Add Member: Lock Activation filter
  455. $context['options'][] = array(
  456. 'member-lock-activation',
  457. in_array('member-lock-activation', $selected),
  458. __('Members: Lock Activation')
  459. );
  460. }
  461. if(!is_null(extension_Members::getFieldHandle('authentication'))) {
  462. // Add Member: Update Password filter
  463. $context['options'][] = array(
  464. 'member-update-password',
  465. in_array('member-update-password', $selected),
  466. __('Members: Update Password')
  467. );
  468. }
  469. }
  470. public static function findCodeExpiry($table) {
  471. $default = array('1 hour' => '1 hour', '24 hours' => '24 hours');
  472. try {
  473. $used = Symphony::Database()->fetchCol('code_expiry', sprintf("
  474. SELECT DISTINCT(code_expiry) FROM `%s`
  475. ", $table));
  476. if(is_array($used) && !empty($used)) {
  477. $default = array_merge($default, array_combine($used, $used));
  478. }
  479. }
  480. catch (DatabaseException $ex) {
  481. // Table doesn't exist yet, it's ok we have defaults.
  482. }
  483. return $default;
  484. }
  485. public static function fetchEmailTemplates() {
  486. $options = array();
  487. // Email Template Filter
  488. // @link http://symphony-cms.com/download/extensions/view/20743/
  489. try {
  490. $driver = Symphony::ExtensionManager()->getInstance('emailtemplatefilter');
  491. if($driver instanceof Extension) {
  492. $templates = $driver->getTemplates();
  493. $g = array('label' => __('Email Template Filter'));
  494. $group_options = array();
  495. foreach($templates as $template) {
  496. $group_options[] = array('etf-'.$template['id'], false, $template['name']);
  497. }
  498. $g['options'] = $group_options;
  499. if(!empty($g['options'])) {
  500. $options[] = $g;
  501. }
  502. }
  503. }
  504. catch(Exception $ex) {}
  505. // Email Template Manager
  506. // @link http://symphony-cms.com/download/extensions/view/64322/
  507. try {
  508. $handles = Symphony::ExtensionManager()->listInstalledHandles();
  509. if(in_array('email_template_manager', $handles)){
  510. if(file_exists(EXTENSIONS . '/email_template_manager/lib/class.emailtemplatemanager.php') && !class_exists("EmailTemplateManager")) {
  511. include_once(EXTENSIONS . '/email_template_manager/lib/class.emailtemplatemanager.php');
  512. }
  513. if(class_exists("EmailTemplateManager")){
  514. $templates = EmailTemplateManager::listAll();
  515. $g = array('label' => __('Email Template Manager'));
  516. $group_options = array();
  517. foreach($templates as $template) {
  518. $group_options[] = array('etm-'.$template->getHandle(), false, $template->getName());
  519. }
  520. $g['options'] = $group_options;
  521. if(!empty($g['options'])) {
  522. $options[] = $g;
  523. }
  524. }
  525. }
  526. }
  527. catch(Exception $ex) {}
  528. return $options;
  529. }
  530. /*-------------------------------------------------------------------------
  531. Preferences:
  532. -------------------------------------------------------------------------*/
  533. /**
  534. * Allows a user to select which section they would like to use as their
  535. * active members section. This allows developers to build multiple sections
  536. * for migration during development.
  537. *
  538. * @uses AddCustomPreferenceFieldsets
  539. * @todo Look at how this could be expanded so users can log into multiple
  540. * sections. This is not in scope for 1.0
  541. */
  542. public function appendPreferences($context) {
  543. $fieldset = new XMLElement('fieldset');
  544. $fieldset->setAttribute('class', 'settings');
  545. $fieldset->appendChild(new XMLElement('legend', __('Members')));
  546. $div = new XMLElement('div');
  547. $label = new XMLElement('label', __('Active Members Section'));
  548. // Get the Sections that contain a Member field.
  549. $sectionManager = self::$entryManager->sectionManager;
  550. $sections = $sectionManager->fetch();
  551. $member_sections = array();
  552. if(is_array($sections) && !empty($sections)) {
  553. foreach($sections as $section) {
  554. $schema = $section->fetchFieldsSchema();
  555. foreach($schema as $field) {
  556. if(!in_array($field['type'], extension_Members::$member_fields)) continue;
  557. if(array_key_exists($section->get('id'), $member_sections)) continue;
  558. $member_sections[$section->get('id')] = $section->get();
  559. }
  560. }
  561. }
  562. // Build the options
  563. $options = array(
  564. array(null, false, null)
  565. );
  566. foreach($member_sections as $section_id => $section) {
  567. $options[] = array($section_id, ($section_id == extension_Members::getMembersSection()), $section['name']);
  568. }
  569. $label->appendChild(Widget::Select('settings[members][section]', $options));
  570. $div->appendChild($label);
  571. if(count($options) == 1) {
  572. $div->appendChild(
  573. new XMLElement('p', __('A Members section will at minimum contain either a Member: Email or a Member: Username field'), array('class' => 'help'))
  574. );
  575. }
  576. $fieldset->appendChild($div);
  577. $context['wrapper']->appendChild($fieldset);
  578. }
  579. /**
  580. * Saves the Member Section to the configuration
  581. *
  582. * @uses savePreferences
  583. */
  584. public function savePreferences(array &$context){
  585. $settings = $context['settings'];
  586. // Active Section
  587. Symphony::Configuration()->set('section', $settings['members']['section'], 'members');
  588. Administration::instance()->saveConfig();
  589. }
  590. /*-------------------------------------------------------------------------
  591. Role Manager:
  592. -------------------------------------------------------------------------*/
  593. public function checkFrontendPagePermissions($context) {
  594. $isLoggedIn = false;
  595. $errors = array();
  596. // Checks $_REQUEST to see if a Member Action has been requested,
  597. // member-action['login'] and member-action['logout']/?member-action=logout
  598. // are the only two supported at this stage.
  599. if(is_array($_REQUEST['member-action'])){
  600. list($action) = array_keys($_REQUEST['member-action']);
  601. } else {
  602. $action = $_REQUEST['member-action'];
  603. }
  604. // Check to see a Member is already logged in.
  605. $isLoggedIn = $this->getMemberDriver()->isLoggedIn($errors);
  606. // Logout
  607. if(trim($action) == 'logout') {
  608. $this->getMemberDriver()->logout();
  609. // If a redirect is provided, redirect to that, otherwise return the user
  610. // to the index of the site. Issue #51 & #121
  611. if(isset($_REQUEST['redirect'])) redirect($_REQUEST['redirect']);
  612. redirect(URL);
  613. }
  614. // Login
  615. else if(trim($action) == 'login' && !is_null($_POST['fields'])) {
  616. // If a Member is already logged in and another Login attempt is requested
  617. // log the Member out first before trying to login with new details.
  618. if($isLoggedIn) {
  619. $this->getMemberDriver()->logout();
  620. }
  621. if($this->getMemberDriver()->login($_POST['fields'])) {
  622. if(isset($_POST['redirect'])) redirect($_POST['redirect']);
  623. }
  624. else {
  625. self::$_failed_login_attempt = true;
  626. }
  627. }
  628. $this->Member->initialiseMemberObject();
  629. if($isLoggedIn && $this->getMemberDriver()->getMember() instanceOf Entry) {
  630. $this->__updateSystemTimezoneOffset($this->getMemberDriver()->getMember()->get('id'));
  631. if(!is_null(extension_Members::getFieldHandle('role'))) {
  632. $role_data = $this->getMemberDriver()->getMember()->getData(extension_Members::getField('role')->get('id'));
  633. }
  634. }
  635. // If there is no role field, or a Developer is logged in, return, as Developers
  636. // should be able to access every page.
  637. if(
  638. is_null(extension_Members::getFieldHandle('role'))
  639. || (Frontend::instance()->Author instanceof Author && Frontend::instance()->Author->isDeveloper())
  640. ) return;
  641. $role_id = ($isLoggedIn) ? $role_data['role_id'] : Role::PUBLIC_ROLE;
  642. $role = RoleManager::fetch($role_id);
  643. if($role instanceof Role && !$role->canAccessPage((int)$context['page_data']['id'])) {
  644. // User has no access to this page, so look for a custom 403 page
  645. if($row = Symphony::Database()->fetchRow(0,"
  646. SELECT `p`.*
  647. FROM `tbl_pages` as `p`
  648. LEFT JOIN `tbl_pages_types` AS `pt` ON(`p`.id = `pt`.page_id)
  649. WHERE `pt`.type = '403'
  650. ")) {
  651. $row['type'] = FrontendPage::fetchPageTypes($row['id']);
  652. $row['filelocation'] = FrontendPage::resolvePageFileLocation($row['path'], $row['handle']);
  653. $context['page_data'] = $row;
  654. return;
  655. }
  656. else {
  657. // No custom 403, just throw default 403
  658. GenericExceptionHandler::$enabled = true;
  659. throw new SymphonyErrorPage(
  660. __('The page you have requested has restricted access permissions.'),
  661. __('Forbidden'),
  662. 'error',
  663. array('header' => 'HTTP/1.0 403 Forbidden')
  664. );
  665. }
  666. }
  667. }
  668. /*-------------------------------------------------------------------------
  669. Events:
  670. -------------------------------------------------------------------------*/
  671. /**
  672. * Adds Javascript to the custom Members events when they are viewed in the
  673. * backend to enable developers to set the appropriate Email Templates for
  674. * each event
  675. *
  676. * @uses AdminPagePreGenerate
  677. */
  678. public function appendAssets(&$context) {
  679. if(class_exists('Administration')
  680. && Administration::instance() instanceof Administration
  681. && Administration::instance()->Page instanceof HTMLPage
  682. ) {
  683. $callback = Administration::instance()->getPageCallback();
  684. // Event Info
  685. if(
  686. $context['oPage'] instanceof contentBlueprintsEvents &&
  687. $callback['context'][0] == "info" &&
  688. in_array($callback['context'][1], extension_Members::$member_events)
  689. ) {
  690. Administration::instance()->Page->addScriptToHead(URL . '/extensions/members/assets/members.events.js', 10001, false);
  691. }
  692. // Temporary fix
  693. else if($context['oPage'] instanceof contentPublish) {
  694. Administration::instance()->Page->addStylesheetToHead(URL . '/extensions/members/assets/members.publish.css', 'screen', 45);
  695. }
  696. }
  697. }
  698. /**
  699. * This function will ensure that the user who has submitted the form (and
  700. * hence is requesting that an event be triggered) is actually allowed to
  701. * do this request.
  702. * There are 2 action types, creation and editing. Creation is a simple yes/no
  703. * affair, whereas editing has three levels of permission, None, Own Entries
  704. * or All Entries:
  705. * - None: This user can't do process this event
  706. * - Own Entries: If the entry the user is trying to update is their own
  707. * determined by if the `entry_id` or, in the case of a SBL or
  708. * similar field, the `entry_id` of the linked entry matches the logged in
  709. * user's id, process the event.
  710. * - All Entries: The user can update any entry in Symphony.
  711. * If there are no Roles in this system, or the event is set to ignore permissions
  712. * (by including a function, `ignoreRolePermissions` that returns `true`, it will
  713. * immediately proceed to processing any of the Filters attached to the event
  714. * before returning.
  715. *
  716. * @uses checkEventPermissions
  717. */
  718. public function checkEventPermissions(array &$context){
  719. // If this system has no Roles, or the event is set to ignore role permissions
  720. // continue straight to processing the Filters
  721. if(
  722. is_null(extension_Members::getFieldHandle('role')) ||
  723. (method_exists($context['event'], 'ignoreRolePermissions') && $context['event']->ignoreRolePermissions() == true)
  724. ) {
  725. return $this->__processEventFilters($context);
  726. }
  727. if(isset($_POST['id'])){
  728. $entry_id = (int)$_POST['id'];
  729. $action = 'edit';
  730. }
  731. else {
  732. $action = 'create';
  733. $entry_id = 0;
  734. }
  735. $required_level = $action == 'create' ? EventPermissions::CREATE : EventPermissions::ALL_ENTRIES;
  736. $role_id = Role::PUBLIC_ROLE;
  737. $isLoggedIn = $this->getMemberDriver()->isLoggedIn();
  738. if($isLoggedIn && $this->getMemberDriver()->initialiseMemberObject()) {
  739. if($this->getMemberDriver()->getMember() instanceOf Entry) {
  740. $required_level = EventPermissions::OWN_ENTRIES;
  741. $role_data = $this->getMemberDriver()->getMember()->getData(extension_Members::getField('role')->get('id'));
  742. $role_id = $role_data['role_id'];
  743. if($action == 'edit' && method_exists($context['event'], 'getSource')) {
  744. $section_id = $context['event']->getSource();
  745. $isOwner = false;
  746. // If the event is the same section as the Members section, then for `$isOwner`
  747. // to be true, the `$entry_id` must match the currently logged in user.
  748. if($section_id == extension_Members::getMembersSection()) {
  749. // Check the logged in member is the same as the `entry_id` that is about to
  750. // be updated. If so the user is the Owner and can modify EventPermissions::OWN_ENTRIES
  751. $isOwner = ($this->getMemberDriver()->getMemberID() == $entry_id);
  752. }
  753. // If the $section_id !== members_section, check the section associations table
  754. // for any links to either of the Member fields that my be used for linking,
  755. // that is the Username or Email field.
  756. else {
  757. $field_ids = array();
  758. // Get the ID's of the fields that may be used for Linking (Username/Email)
  759. if(!is_null(extension_Members::getFieldHandle('identity'))) {
  760. $field_ids[] = extension_Members::getField('identity')->get('id');
  761. }
  762. if(!is_null(extension_Members::getFieldHandle('email'))) {
  763. $field_ids[] = extension_Members::getField('email')->get('id');
  764. }
  765. // Query for the `field_id` of any linking fields that link to the members
  766. // section AND to one of the linking fields (Username/Email)
  767. $fields = Symphony::Database()->fetchCol('child_section_field_id', sprintf("
  768. SELECT `child_section_field_id`
  769. FROM `tbl_sections_association`
  770. WHERE `parent_section_id` = %d
  771. AND `child_section_id` = %d
  772. AND `parent_section_field_id` IN ('%s')
  773. ",
  774. extension_Members::getMembersSection(),
  775. $section_id,
  776. implode("','", $field_ids)
  777. ));
  778. // If there was a link found, get the `relation_id`, which is the `member_id` of
  779. // an entry in the active Members section.
  780. if(!empty($fields)) {
  781. foreach($fields as $field_id) {
  782. if($isOwner === true) break;
  783. $field = self::$entryManager->fieldManager->fetch($field_id);
  784. if($field instanceof Field) {
  785. // So we are trying to find all entries that have selected the Member entry
  786. // to determine ownership. This check will use the `fetchAssociatedEntryIDs`
  787. // function, which typically works backwards, by accepting the `entry_id` (in
  788. // this case, our logged in Member ID). This will return an array of all the
  789. // linked entries, so we then just check that the current entry that is going to
  790. // be updated is in that array
  791. $member_id = $field->fetchAssociatedEntryIDs($this->getMemberDriver()->getMemberID());
  792. $isOwner = in_array($entry_id, $member_id);
  793. }
  794. }
  795. }
  796. }
  797. // User is not the owner, so they can edit EventPermissions::ALL_ENTRIES
  798. if($isOwner === false) $required_level = EventPermissions::ALL_ENTRIES;
  799. }
  800. }
  801. }
  802. try {
  803. $role = RoleManager::fetch($role_id);
  804. $event_handle = strtolower(preg_replace('/^event/i', NULL, get_class($context['event'])));
  805. $success = $role->canProcessEvent($event_handle, $action, $required_level) ? true : false;
  806. $context['messages'][] = array(
  807. 'permission',
  808. $success,
  809. ($success === false) ? __('You are not authorised to perform this action.') : null
  810. );
  811. }
  812. catch (Exception $ex) {
  813. // Unsure of what the possible Exceptions would be here, so lets
  814. // just throw for now for the sake of discovery.
  815. throw new $ex;
  816. }
  817. // Process the Filters for this event.
  818. return $this->__processEventFilters($context);
  819. }
  820. /**
  821. * We can safely assume at this stage of the process that whatever user has
  822. * requested this event has permission to actually do so.
  823. */
  824. private function __processEventFilters(array &$context) {
  825. // Process the Member Lock Role
  826. if (in_array('member-lock-role', $context['event']->eParamFILTERS)) {
  827. $this->getMemberDriver()->filter_LockRole(&$context);
  828. }
  829. // Process the Member Lock Activation
  830. if (in_array('member-lock-activation', $context['event']->eParamFILTERS)) {
  831. $this->getMemberDriver()->filter_LockActivation(&$context);
  832. }
  833. // Process updating a Member's Password
  834. if (in_array('member-update-password', $context['event']->eParamFILTERS)) {
  835. $this->getMemberDriver()->filter_UpdatePassword(&$context);
  836. }
  837. }
  838. /**
  839. * Any post save behaviour
  840. *
  841. * @uses EventPostSaveFilter
  842. */
  843. public function processPostSaveFilter(array &$context) {
  844. // Process updating a Member's Password
  845. if (in_array('member-update-password', $context['event']->eParamFILTERS)) {
  846. $this->getMemberDriver()->filter_UpdatePasswordLogin($context);
  847. }
  848. }
  849. /*-------------------------------------------------------------------------
  850. Output:
  851. -------------------------------------------------------------------------*/
  852. public function addMemberDetailsToPageParams(array $context = null) {
  853. $this->getMemberDriver()->addMemberDetailsToPageParams($context);
  854. }
  855. public function appendLoginStatusToEventXML(array $context = null){
  856. $this->getMemberDriver()->appendLoginStatusToEventXML($context);
  857. }
  858. }