profile2.module 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195
  1. <?php
  2. /**
  3. * @file
  4. * Support for configurable user profiles.
  5. */
  6. /**
  7. * Implements hook_entity_info().
  8. */
  9. function profile2_entity_info() {
  10. $return = array(
  11. 'profile2' => array(
  12. 'label' => t('Profile'),
  13. 'plural label' => t('Profiles'),
  14. 'description' => t('Profile2 user profiles.'),
  15. 'entity class' => 'Profile',
  16. 'controller class' => 'EntityAPIController',
  17. 'base table' => 'profile',
  18. 'fieldable' => TRUE,
  19. 'view modes' => array(
  20. 'account' => array(
  21. 'label' => t('User account'),
  22. 'custom settings' => FALSE,
  23. ),
  24. ),
  25. 'entity keys' => array(
  26. 'id' => 'pid',
  27. 'bundle' => 'type',
  28. 'label' => 'label',
  29. ),
  30. 'bundles' => array(),
  31. 'bundle keys' => array(
  32. 'bundle' => 'type',
  33. ),
  34. 'label callback' => 'entity_class_label',
  35. 'uri callback' => 'entity_class_uri',
  36. 'access callback' => 'profile2_access',
  37. 'module' => 'profile2',
  38. 'metadata controller class' => 'Profile2MetadataController'
  39. ),
  40. );
  41. // Add bundle info but bypass entity_load() as we cannot use it here.
  42. $types = db_select('profile_type', 'p')
  43. ->fields('p')
  44. ->execute()
  45. ->fetchAllAssoc('type');
  46. foreach ($types as $type => $info) {
  47. $return['profile2']['bundles'][$type] = array(
  48. 'label' => $info->label,
  49. 'admin' => array(
  50. 'path' => 'admin/structure/profiles/manage/%profile2_type',
  51. 'real path' => 'admin/structure/profiles/manage/' . $type,
  52. 'bundle argument' => 4,
  53. 'access arguments' => array('administer profiles'),
  54. ),
  55. );
  56. }
  57. // Support entity cache module.
  58. if (module_exists('entitycache')) {
  59. $return['profile2']['field cache'] = FALSE;
  60. $return['profile2']['entity cache'] = TRUE;
  61. }
  62. $return['profile2_type'] = array(
  63. 'label' => t('Profile type'),
  64. 'plural label' => t('Profile types'),
  65. 'description' => t('Profiles types of Profile2 user profiles.'),
  66. 'entity class' => 'ProfileType',
  67. 'controller class' => 'EntityAPIControllerExportable',
  68. 'base table' => 'profile_type',
  69. 'fieldable' => FALSE,
  70. 'bundle of' => 'profile2',
  71. 'exportable' => TRUE,
  72. 'entity keys' => array(
  73. 'id' => 'id',
  74. 'name' => 'type',
  75. 'label' => 'label',
  76. ),
  77. 'access callback' => 'profile2_type_access',
  78. 'module' => 'profile2',
  79. // Enable the entity API's admin UI.
  80. 'admin ui' => array(
  81. 'path' => 'admin/structure/profiles',
  82. 'file' => 'profile2.admin.inc',
  83. 'controller class' => 'Profile2TypeUIController',
  84. ),
  85. );
  86. return $return;
  87. }
  88. /**
  89. * Menu argument loader; Load a profile type by string.
  90. *
  91. * @param $type
  92. * The machine-readable name of a profile type to load.
  93. * @return
  94. * A profile type array or FALSE if $type does not exist.
  95. */
  96. function profile2_type_load($type) {
  97. return profile2_get_types($type);
  98. }
  99. /**
  100. * Implements hook_menu().
  101. */
  102. function profile2_menu() {
  103. $items = array();
  104. // Define page which provides form to generate profiles using
  105. // Devel generate module.
  106. if (module_exists('devel_generate')) {
  107. $items['admin/config/development/generate/profile2'] = array(
  108. 'title' => 'Generate profiles',
  109. 'description' => 'Generate a given number of profiles for users. Optionally override current user profiles.',
  110. 'access arguments' => array('administer profiles'),
  111. 'page callback' => 'drupal_get_form',
  112. 'page arguments' => array('profile2_generate_form'),
  113. 'file' => 'profile2.devel.inc',
  114. );
  115. }
  116. $items['user/%profile2_by_uid/%/delete'] = array(
  117. 'title' => 'Delete',
  118. 'description' => 'Delete Profile of User.',
  119. 'type' => MENU_NORMAL_ITEM,
  120. 'page callback' => 'drupal_get_form',
  121. 'page arguments' => array('profile2_delete_confirm_form', 1),
  122. 'load arguments' => array(2),
  123. 'access callback' => 'profile2_access',
  124. 'access arguments' => array('delete', 1),
  125. 'file' => 'profile2.delete.inc',
  126. );
  127. return $items;
  128. }
  129. /**
  130. * Implements hook_permission().
  131. */
  132. function profile2_permission() {
  133. $permissions = array(
  134. 'administer profile types' => array(
  135. 'title' => t('Administer profile types'),
  136. 'description' => t('Create and delete fields on user profiles, and set their permissions.'),
  137. ),
  138. 'administer profiles' => array(
  139. 'title' => t('Administer profiles'),
  140. 'description' => t('Edit and view all user profiles.'),
  141. ),
  142. );
  143. // Generate per profile type permissions.
  144. foreach (profile2_get_types() as $type) {
  145. $type_name = check_plain($type->type);
  146. $permissions += array(
  147. "edit own $type_name profile" => array(
  148. 'title' => t('%type_name: Edit own profile', array('%type_name' => $type->getTranslation('label'))),
  149. ),
  150. "edit any $type_name profile" => array(
  151. 'title' => t('%type_name: Edit any profile', array('%type_name' => $type->getTranslation('label'))),
  152. ),
  153. "view own $type_name profile" => array(
  154. 'title' => t('%type_name: View own profile', array('%type_name' => $type->getTranslation('label'))),
  155. ),
  156. "view any $type_name profile" => array(
  157. 'title' => t('%type_name: View any profile', array('%type_name' => $type->getTranslation('label'))),
  158. ),
  159. "delete own $type_name profile" => array(
  160. 'title' => t('%type_name: Delete own profile', array('%type_name' => $type->getTranslation('label'))),
  161. ),
  162. );
  163. }
  164. return $permissions;
  165. }
  166. /**
  167. * Gets an array of all profile types, keyed by the type name.
  168. *
  169. * @param $type_name
  170. * If set, the type with the given name is returned.
  171. * @return ProfileType[]
  172. * Depending whether $type isset, an array of profile types or a single one.
  173. */
  174. function profile2_get_types($type_name = NULL) {
  175. $types = entity_load_multiple_by_name('profile2_type', isset($type_name) ? array($type_name) : FALSE);
  176. return isset($type_name) ? reset($types) : $types;
  177. }
  178. /**
  179. * Fetch a profile object.
  180. *
  181. * @param $pid
  182. * Integer specifying the profile id.
  183. * @param $reset
  184. * A boolean indicating that the internal cache should be reset.
  185. * @return
  186. * A fully-loaded $profile object or FALSE if it cannot be loaded.
  187. *
  188. * @see profile2_load_multiple()
  189. */
  190. function profile2_load($pid, $reset = FALSE) {
  191. $profiles = profile2_load_multiple(array($pid), array(), $reset);
  192. return reset($profiles);
  193. }
  194. /**
  195. * Load multiple profiles based on certain conditions.
  196. *
  197. * @param $pids
  198. * An array of profile IDs.
  199. * @param $conditions
  200. * An array of conditions to match against the {profile} table.
  201. * @param $reset
  202. * A boolean indicating that the internal cache should be reset.
  203. * @return
  204. * An array of profile objects, indexed by pid.
  205. *
  206. * @see entity_load()
  207. * @see profile2_load()
  208. * @see profile2_load_by_user()
  209. */
  210. function profile2_load_multiple($pids = array(), $conditions = array(), $reset = FALSE) {
  211. return entity_load('profile2', $pids, $conditions, $reset);
  212. }
  213. /**
  214. * Fetch profiles by account.
  215. *
  216. * @param $account
  217. * The user account to load profiles for, or its uid.
  218. * @param $type_name
  219. * To load a single profile, pass the type name of the profile to load.
  220. * @return
  221. * Either a single profile or an array of profiles keyed by profile type.
  222. *
  223. * @see profile2_load_multiple()
  224. * @see profile2_profile2_delete()
  225. * @see Profile::save()
  226. */
  227. function profile2_load_by_user($account, $type_name = NULL) {
  228. // Use a separate query to determine all profile ids per user and cache them.
  229. // That way we can look up profiles by id and benefit from the static cache
  230. // of the entity loader.
  231. $cache = &drupal_static(__FUNCTION__, array());
  232. $uid = is_object($account) ? $account->uid : $account;
  233. if (!isset($cache[$uid])) {
  234. $cache[$uid] = db_select('profile', 'p')
  235. ->fields('p', array('type', 'pid'))
  236. ->condition('uid', $uid)
  237. ->execute()
  238. ->fetchAllKeyed();
  239. }
  240. if (isset($type_name)) {
  241. return isset($cache[$uid][$type_name]) ? profile2_load($cache[$uid][$type_name]) : FALSE;
  242. }
  243. // Return an array containing profiles keyed by profile type.
  244. return $cache[$uid] ? array_combine(array_keys($cache[$uid]), profile2_load_multiple($cache[$uid])) : $cache[$uid];
  245. }
  246. /**
  247. * Implements hook_profile2_delete().
  248. */
  249. function profile2_profile2_delete($profile) {
  250. // Clear the static cache from profile2_load_by_user().
  251. $cache = &drupal_static('profile2_load_by_user', array());
  252. unset($cache[$profile->uid][$profile->type]);
  253. }
  254. /**
  255. * Deletes a profile.
  256. */
  257. function profile2_delete(Profile $profile) {
  258. $profile->delete();
  259. }
  260. /**
  261. * Delete multiple profiles.
  262. *
  263. * @param $pids
  264. * An array of profile IDs.
  265. */
  266. function profile2_delete_multiple(array $pids) {
  267. entity_get_controller('profile2')->delete($pids);
  268. }
  269. /**
  270. * Implements hook_user_delete().
  271. */
  272. function profile2_user_delete($account) {
  273. foreach (profile2_load_by_user($account) as $profile) {
  274. profile2_delete($profile);
  275. }
  276. }
  277. /**
  278. * Create a new profile object.
  279. */
  280. function profile2_create(array $values) {
  281. return new Profile($values);
  282. }
  283. /**
  284. * Deprecated. Use profile2_create().
  285. */
  286. function profile_create(array $values) {
  287. return new Profile($values);
  288. }
  289. /**
  290. * Saves a profile to the database.
  291. *
  292. * @param $profile
  293. * The profile object.
  294. */
  295. function profile2_save(Profile $profile) {
  296. return $profile->save();
  297. }
  298. /**
  299. * Saves a profile type to the db.
  300. */
  301. function profile2_type_save(ProfileType $type) {
  302. $type->save();
  303. }
  304. /**
  305. * Deletes a profile type from.
  306. */
  307. function profile2_type_delete(ProfileType $type) {
  308. $type->delete();
  309. }
  310. /**
  311. * Implements hook_profile2_type_insert().
  312. */
  313. function profile2_profile2_type_insert(ProfileType $type) {
  314. // Do not directly issue menu rebuilds here to avoid potentially multiple
  315. // rebuilds. Instead, let menu_get_item() issue the rebuild on the next page.
  316. variable_set('menu_rebuild_needed', TRUE);
  317. }
  318. /**
  319. * Implements hook_profile2_type_update().
  320. */
  321. function profile2_profile2_type_update(ProfileType $type) {
  322. // @see profile2_profile2_type_insert()
  323. variable_set('menu_rebuild_needed', TRUE);
  324. }
  325. /**
  326. * Implements hook_profile2_type_delete()
  327. */
  328. function profile2_profile2_type_delete(ProfileType $type) {
  329. // Delete all profiles of this type but only if this is not a revert.
  330. if (!$type->hasStatus(ENTITY_IN_CODE)) {
  331. $pids = array_keys(profile2_load_multiple(FALSE, array('type' => $type->type)));
  332. if ($pids) {
  333. profile2_delete_multiple($pids);
  334. }
  335. // @see profile2_profile2_type_insert()
  336. variable_set('menu_rebuild_needed', TRUE);
  337. }
  338. }
  339. /**
  340. * Implements hook_user_view().
  341. */
  342. function profile2_user_view($account, $view_mode, $langcode) {
  343. foreach (profile2_get_types() as $type => $profile_type) {
  344. if ($profile_type->userView && $profile = profile2_load_by_user($account, $type)) {
  345. if (profile2_access('view', $profile)) {
  346. $account->content['profile_' . $type] = array(
  347. '#type' => 'user_profile_category',
  348. '#title' => $profile_type->getTranslation('label'),
  349. '#prefix' => '<a id="profile-' . $profile->type . '"></a>',
  350. );
  351. $account->content['profile_' . $type]['view'] = $profile->view($view_mode);
  352. }
  353. }
  354. }
  355. }
  356. /**
  357. * Implements hook_form_FORM_ID_alter() for the user edit form.
  358. *
  359. * @see profile2_form_validate_handler
  360. * @see profile2_form_submit_handler
  361. */
  362. function profile2_form_user_profile_form_alter(&$form, &$form_state) {
  363. global $user;
  364. if (($type = profile2_get_types($form['#user_category'])) && $type->userCategory) {
  365. if (empty($form_state['profiles'])) {
  366. $profile = profile2_load_by_user($form['#user'], $form['#user_category']);
  367. if (empty($profile)) {
  368. $profile = profile2_create(array('type' => $form['#user_category'], 'uid' => $form['#user']->uid));
  369. $profile->is_new = TRUE;
  370. }
  371. $form_state['profiles'][$profile->type] = $profile;
  372. if (user_access('administer profiles') && $user->uid != $profile->uid) {
  373. $str_button_value = t('Delete profile');
  374. }
  375. elseif (user_access("delete own $profile->type profile") && $user->uid === $profile->uid) {
  376. $str_button_value = t('Delete this profile');
  377. }
  378. }
  379. if (empty($profile->is_new) && !empty($str_button_value)) {
  380. $form['actions']['delete'] = array(
  381. '#type' => 'submit',
  382. '#value' => $str_button_value,
  383. '#weight' => 45,
  384. '#limit_validation_errors' => array(),
  385. '#submit' => array('profile2_form_submit_own_delete')
  386. );
  387. }
  388. profile2_attach_form($form, $form_state);
  389. }
  390. }
  391. /**
  392. * Profile form submit handler for the delete button.
  393. */
  394. function profile2_form_submit_own_delete($form, &$form_state) {
  395. $profile = $form_state['profiles'][$form['#user_category']];
  396. if (isset($profile) && is_object($profile)) {
  397. $form_state['redirect'] = 'user/' . $profile->uid . '/' . $form['#user_category'] . '/delete';
  398. }
  399. }
  400. /**
  401. * Implements hook_form_FORM_ID_alter() for the registration form.
  402. */
  403. function profile2_form_user_register_form_alter(&$form, &$form_state) {
  404. foreach (profile2_get_types() as $type_name => $profile_type) {
  405. if (!empty($profile_type->data['registration'])) {
  406. if (empty($form_state['profiles'][$type_name])) {
  407. $form_state['profiles'][$type_name] = profile2_create(array('type' => $type_name));
  408. }
  409. }
  410. }
  411. // If we have profiles to attach to the registration form - then do it.
  412. if (!empty($form_state['profiles'])) {
  413. profile2_attach_form($form, $form_state);
  414. // Wrap each profile form in a fieldset.
  415. foreach ($form_state['profiles'] as $type_name => $profile_type) {
  416. $form['profile_' . $type_name] += array(
  417. '#type' => 'fieldset',
  418. '#title' => check_plain($profile_type->getTranslation('label')),
  419. );
  420. }
  421. }
  422. }
  423. /**
  424. * Attaches the profile forms of the profiles set in
  425. * $form_state['profiles'].
  426. *
  427. * Modules may alter the profile2 entity form regardless to which form it is
  428. * attached by making use of hook_form_profile2_form_alter().
  429. *
  430. * @param $form
  431. * The form to which to attach the profile2 form. For each profile the form
  432. * is added to @code $form['profile_' . $profile->type] @endcode. This helper
  433. * also adds in a validation and a submit handler caring for the attached
  434. * profile forms.
  435. *
  436. * @see hook_form_profile2_form_alter()
  437. * @see profile2_form_validate_handler()
  438. * @see profile2_form_submit_handler()
  439. */
  440. function profile2_attach_form(&$form, &$form_state) {
  441. foreach ($form_state['profiles'] as $type => $profile) {
  442. $form['profile_' . $profile->type]['#tree'] = TRUE;
  443. $form['profile_' . $profile->type]['#parents'] = array('profile_' . $profile->type);
  444. field_attach_form('profile2', $profile, $form['profile_' . $profile->type], $form_state);
  445. if (user_access('administer profile types')) {
  446. if (count(field_info_instances('profile2', $profile->type)) == 0) {
  447. $form['profile_' . $profile->type]['message'] = array(
  448. '#markup' => t('No fields have been associated with this profile type. Go to the <a href="!url">Profile types</a> page to add some fields.', array('!url' => url('admin/structure/profiles'))),
  449. );
  450. }
  451. }
  452. // Make sure we don't have duplicate pre render callbacks.
  453. $form['profile_' . $profile->type]['#pre_render'] = array_unique($form['profile_' . $profile->type]['#pre_render']);
  454. // Provide a central place for modules to alter the profile forms, but
  455. // skip that in case the caller cares about invoking the hooks.
  456. // @see profile2_form().
  457. if (!isset($form_state['profile2_skip_hook'])) {
  458. $hooks[] = 'form_profile2_edit_' . $type . '_form';
  459. $hooks[] = 'form_profile2_form';
  460. drupal_alter($hooks, $form, $form_state);
  461. }
  462. }
  463. $form['#validate'][] = 'profile2_form_validate_handler';
  464. // Default name of user registry form callback.
  465. $register_submit_callback = 'user_register_submit';
  466. // LoginToBoggan module replaces default user_register_submit() callback
  467. // with his own. So if this module enabled we need to track his callback
  468. // instead one that comes from the User module.
  469. if (module_exists('logintoboggan')) {
  470. $register_submit_callback = 'logintoboggan_user_register_submit';
  471. }
  472. // Search for key of user register submit callback.
  473. if (!empty($form['#submit']) && is_array($form['#submit'])) {
  474. $submit_key = array_search($register_submit_callback, $form['#submit']);
  475. }
  476. // Add these hooks only when needed, and ensure they are not added twice.
  477. if (isset($submit_key) && $submit_key !== FALSE && !in_array('profile2_form_before_user_register_submit_handler', $form['#submit'])) {
  478. // Insert submit callback right before the user register submit callback.
  479. // Needs for disabling email notification during user registration.
  480. array_splice($form['#submit'], $submit_key, 0, array('profile2_form_before_user_register_submit_handler'));
  481. // Add a submit callback right after the user register submit callback.
  482. // This is needed for creation of a new user profile.
  483. array_splice($form['#submit'], $submit_key + 2, 0, array('profile2_form_submit_handler'));
  484. // Insert submit handler right after the creation of new user profile.
  485. // This is needed for sending email which was blocked during registration.
  486. array_splice($form['#submit'], $submit_key + 3, 0, array('profile2_form_after_user_register_submit_handler'));
  487. }
  488. else {
  489. // Fallback if some contrib module removes user register submit callback
  490. // from form submit functions.
  491. $form['#submit'][] = 'profile2_form_submit_handler';
  492. }
  493. }
  494. /**
  495. * Validation handler for the profile form.
  496. *
  497. * @see profile2_attach_form()
  498. */
  499. function profile2_form_validate_handler(&$form, &$form_state) {
  500. foreach ($form_state['profiles'] as $type => $profile) {
  501. if (isset($form_state['values']['profile_' . $profile->type])) {
  502. // @see entity_form_field_validate()
  503. $pseudo_entity = (object) $form_state['values']['profile_' . $profile->type];
  504. $pseudo_entity->type = $type;
  505. field_attach_form_validate('profile2', $pseudo_entity, $form['profile_' . $profile->type], $form_state);
  506. }
  507. }
  508. }
  509. /**
  510. * User registration form submit handler
  511. * that executes right before user_register_submit().
  512. *
  513. * In generally, this callback disables the notification emails
  514. * during the execution of user_register_submit() callback.
  515. * The reason for this - we want to support profile2 tokens
  516. * in emails during registration, and there is no another
  517. * proper way to do this. See https://drupal.org/node/1097684.
  518. *
  519. * @see profile2_form_after_user_register_submit_handler()
  520. * @see user_register_submit()
  521. * @see profile2_attach_form()
  522. */
  523. function profile2_form_before_user_register_submit_handler(&$form, &$form_state) {
  524. global $conf;
  525. // List of available operations during the registration.
  526. $register_ops = array('register_admin_created', 'register_no_approval_required', 'register_pending_approval');
  527. // We also have to track if we change a variables, because
  528. // later we have to restore them.
  529. $changed_ops = &drupal_static('profile2_register_changed_operations', array());
  530. foreach ($register_ops as $op) {
  531. // Save variable value.
  532. if (isset($conf['user_mail_' . $op . '_notify'])) {
  533. $changed_ops['user_mail_' . $op . '_notify'] = $conf['user_mail_' . $op . '_notify'];
  534. }
  535. // Temporary disable the notification about registration.
  536. $conf['user_mail_' . $op . '_notify'] = FALSE;
  537. }
  538. }
  539. /**
  540. * User registration form submit handler
  541. * that executes right after user_register_submit().
  542. *
  543. * This callback sends delayed email notification to a user
  544. * about his registration. See https://drupal.org/node/1097684.
  545. *
  546. * @see profile2_form_prepare_user_register_submit_handler()
  547. * @see user_register_submit()
  548. * @see profile2_attach_form()
  549. */
  550. function profile2_form_after_user_register_submit_handler(&$form, &$form_state) {
  551. global $conf;
  552. // List of registration operations that where
  553. // notification values were changed.
  554. $changed_ops = &drupal_static('profile2_register_changed_operations', array());
  555. // List of available operations during the registration.
  556. $register_ops = array('register_admin_created', 'register_no_approval_required', 'register_pending_approval');
  557. foreach ($register_ops as $op) {
  558. // If we changed the notification value in
  559. // profile2_form_before_user_register_submit_handler() then change it back.
  560. if (isset($changed_ops['user_mail_' . $op . '_notify'])) {
  561. $conf['user_mail_' . $op . '_notify'] = $changed_ops['user_mail_' . $op . '_notify'];
  562. }
  563. // Otherwise just remove this value from a global variables array.
  564. else {
  565. unset($conf['user_mail_' . $op . '_notify']);
  566. }
  567. }
  568. // Get the values that we need to define which notification
  569. // should be sent to the user. Generally this is a trimmed version
  570. // of user_register_submit() callback.
  571. $admin = !empty($form_state['values']['administer_users']);
  572. $account = $form_state['user'];
  573. $notify = !empty($form_state['values']['notify']);
  574. if ($admin && !$notify) {
  575. // If admin has created a new account and decided to don't notify a user -
  576. // then just do nothing.
  577. }
  578. elseif (!$admin && !variable_get('user_email_verification', TRUE) && $account->status) {
  579. _user_mail_notify('register_no_approval_required', $account);
  580. }
  581. // No administrator approval required.
  582. elseif ($account->status || $notify) {
  583. $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
  584. _user_mail_notify($op, $account);
  585. }
  586. // Administrator approval required.
  587. elseif (!$admin) {
  588. _user_mail_notify('register_pending_approval', $account);
  589. }
  590. }
  591. /**
  592. * Submit handler that builds and saves all profiles in the form.
  593. *
  594. * @see profile2_attach_form()
  595. */
  596. function profile2_form_submit_handler(&$form, &$form_state) {
  597. profile2_form_submit_build_profile($form, $form_state);
  598. // This is needed as some submit callbacks like user_register_submit() rely on
  599. // clean form values.
  600. profile2_form_submit_cleanup($form, $form_state);
  601. foreach ($form_state['profiles'] as $type => $profile) {
  602. // During registration set the uid field of the newly created user.
  603. if (empty($profile->uid) && isset($form_state['user']->uid)) {
  604. $profile->uid = $form_state['user']->uid;
  605. }
  606. profile2_save($profile);
  607. }
  608. }
  609. /**
  610. * Submit builder. Extracts the form values and updates the profile entities.
  611. *
  612. * @see profile2_attach_form()
  613. */
  614. function profile2_form_submit_build_profile(&$form, &$form_state) {
  615. foreach ($form_state['profiles'] as $type => $profile) {
  616. // @see entity_form_submit_build_entity()
  617. if (isset($form['profile_' . $type]['#entity_builders'])) {
  618. foreach ($form['profile_' . $type]['#entity_builders'] as $function) {
  619. $function('profile2', $profile, $form['profile_' . $type], $form_state);
  620. }
  621. }
  622. field_attach_submit('profile2', $profile, $form['profile_' . $type], $form_state);
  623. }
  624. }
  625. /**
  626. * Cleans up the form values as the user modules relies on clean values.
  627. *
  628. * @see profile2_attach_form()
  629. */
  630. function profile2_form_submit_cleanup(&$form, &$form_state) {
  631. foreach ($form_state['profiles'] as $type => $profile) {
  632. unset($form_state['values']['profile_' . $type]);
  633. }
  634. }
  635. /**
  636. * Implements hook_user_categories().
  637. */
  638. function profile2_user_categories() {
  639. $data = array();
  640. foreach (profile2_get_types() as $type => $info) {
  641. if ($info->userCategory) {
  642. $data[] = array(
  643. 'name' => $type,
  644. 'title' => $info->getTranslation('label'),
  645. // Add an offset so a weight of 0 appears right of the account category.
  646. 'weight' => $info->weight + 3,
  647. 'access callback' => 'profile2_category_access',
  648. 'access arguments' => array(1, $type)
  649. );
  650. }
  651. }
  652. return $data;
  653. }
  654. /**
  655. * Menu item access callback - check if a user has access to a profile category.
  656. */
  657. function profile2_category_access($account, $type_name) {
  658. // As there might be no profile yet, create a new object for being able to run
  659. // a proper access check.
  660. $profile = profile2_create(array('type' => $type_name, 'uid' => $account->uid));
  661. return ($account->uid > 0 && $profile->type()->userCategory && profile2_access('edit', $profile));
  662. }
  663. /**
  664. * Determines whether the given user has access to a profile.
  665. *
  666. * @param $op
  667. * The operation being performed. One of 'view', 'update', 'create', 'delete'
  668. * or just 'edit' (being the same as 'create' or 'update').
  669. * @param $profile
  670. * (optional) A profile to check access for. If nothing is given, access for
  671. * all profiles is determined.
  672. * @param $account
  673. * The user to check for. Leave it to NULL to check for the global user.
  674. * @return boolean
  675. * Whether access is allowed or not.
  676. *
  677. * @see hook_profile2_access()
  678. * @see profile2_profile2_access()
  679. */
  680. function profile2_access($op, $profile = NULL, $account = NULL) {
  681. // Check if profile user has current profile available by role.
  682. if (isset($profile->type)) {
  683. $profile_type = profile2_type_load($profile->type);
  684. if (!empty($profile_type) && !empty($profile_type->data['roles']) && isset($profile->uid)) {
  685. $profile_user = user_load($profile->uid);
  686. $profile_roles = array_keys($profile_type->data['roles']);
  687. $user_roles = array_keys($profile_user->roles);
  688. $matches = array_intersect($profile_roles, $user_roles);
  689. if (empty($matches)) {
  690. return FALSE;
  691. }
  692. }
  693. }
  694. // With access to all profiles there is no need to check further.
  695. if (user_access('administer profiles', $account)) {
  696. return TRUE;
  697. }
  698. if ($op == 'create' || $op == 'update') {
  699. $op = 'edit';
  700. }
  701. // Allow modules to grant / deny access.
  702. $access = module_invoke_all('profile2_access', $op, $profile, $account);
  703. // Only grant access if at least one module granted access and no one denied
  704. // access.
  705. if (in_array(FALSE, $access, TRUE)) {
  706. return FALSE;
  707. }
  708. elseif (in_array(TRUE, $access, TRUE)) {
  709. return TRUE;
  710. }
  711. return FALSE;
  712. }
  713. /**
  714. * Implements hook_profile2_access().
  715. */
  716. function profile2_profile2_access($op, $profile = NULL, $account = NULL) {
  717. if (isset($profile) && ($type_name = $profile->type)) {
  718. if (user_access("$op any $type_name profile", $account)) {
  719. return TRUE;
  720. }
  721. $account = isset($account) ? $account : $GLOBALS['user'];
  722. if (isset($profile->uid) && $profile->uid == $account->uid && user_access("$op own $type_name profile", $account)) {
  723. return TRUE;
  724. }
  725. }
  726. // Do not explicitly deny access so others may still grant access.
  727. }
  728. /**
  729. * Access callback for the entity API.
  730. */
  731. function profile2_type_access($op, $type = NULL, $account = NULL) {
  732. return user_access('administer profile types', $account);
  733. }
  734. /**
  735. * Implements hook_theme().
  736. */
  737. function profile2_theme() {
  738. return array(
  739. 'profile2' => array(
  740. 'render element' => 'elements',
  741. 'template' => 'profile2',
  742. ),
  743. );
  744. }
  745. /**
  746. * The class used for profile entities.
  747. */
  748. class Profile extends Entity {
  749. /**
  750. * The profile id.
  751. *
  752. * @var integer
  753. */
  754. public $pid;
  755. /**
  756. * The name of the profile type.
  757. *
  758. * @var string
  759. */
  760. public $type;
  761. /**
  762. * The profile label.
  763. *
  764. * @var string
  765. */
  766. public $label;
  767. /**
  768. * The user id of the profile owner.
  769. *
  770. * @var integer
  771. */
  772. public $uid;
  773. /**
  774. * The Unix timestamp when the profile was created.
  775. *
  776. * @var integer
  777. */
  778. public $created;
  779. /**
  780. * The Unix timestamp when the profile was most recently saved.
  781. *
  782. * @var integer
  783. */
  784. public $changed;
  785. public function __construct($values = array()) {
  786. if (isset($values['user'])) {
  787. $this->setUser($values['user']);
  788. unset($values['user']);
  789. }
  790. if (isset($values['type']) && is_object($values['type'])) {
  791. $values['type'] = $values['type']->type;
  792. }
  793. if (!isset($values['label']) && isset($values['type']) && $type = profile2_get_types($values['type'])) {
  794. // Initialize the label with the type label, so newly created profiles
  795. // have that as interim label.
  796. $values['label'] = $type->label;
  797. }
  798. parent::__construct($values, 'profile2');
  799. }
  800. /**
  801. * Returns the user owning this profile.
  802. */
  803. public function user() {
  804. return user_load($this->uid);
  805. }
  806. /**
  807. * Sets a new user owning this profile.
  808. *
  809. * @param $account
  810. * The user account object or the user account id (uid).
  811. */
  812. public function setUser($account) {
  813. $this->uid = is_object($account) ? $account->uid : $account;
  814. }
  815. /**
  816. * Gets the associated profile type object.
  817. *
  818. * @return ProfileType
  819. */
  820. public function type() {
  821. return profile2_get_types($this->type);
  822. }
  823. /**
  824. * Returns the full url() for the profile.
  825. */
  826. public function url() {
  827. $uri = $this->uri();
  828. return url($uri['path'], $uri);
  829. }
  830. /**
  831. * Returns the drupal path to this profile.
  832. */
  833. public function path() {
  834. $uri = $this->uri();
  835. return $uri['path'];
  836. }
  837. public function defaultUri() {
  838. return array(
  839. 'path' => 'user/' . $this->uid,
  840. 'options' => array('fragment' => 'profile-' . $this->type),
  841. );
  842. }
  843. public function defaultLabel() {
  844. // Return a label that combines the type name and user name, translatable.
  845. return t('@type profile for @user', array(
  846. '@type' => profile2_get_types($this->type)->getTranslation('label'),
  847. '@user' => format_username($this->user()),
  848. ));
  849. }
  850. public function buildContent($view_mode = 'full', $langcode = NULL) {
  851. $content = array();
  852. // Assume newly create objects are still empty.
  853. if (!empty($this->is_new)) {
  854. $content['empty']['#markup'] = '<em class="profile2-no-data">' . t('There is no profile data yet.') . '</em>';
  855. }
  856. return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode, $content);
  857. }
  858. public function save() {
  859. // Don't create a new profile if the user already have one of the same type.
  860. $existing_profile = profile2_load_by_user($this->uid, $this->type);
  861. if (empty($this->pid) && !empty($existing_profile)) {
  862. watchdog('profile2_create', serialize(array(
  863. 'message' => 'Profile already exists',
  864. 'uid' => $this->uid,
  865. 'type' => $this->type,
  866. 'path' => current_path(),
  867. 'logged_in_user' => $GLOBALS['user']->uid,
  868. )), array(), WATCHDOG_WARNING);
  869. return;
  870. }
  871. // Care about setting created and changed values. But do not automatically
  872. // set a created values for already existing profiles.
  873. if (empty($this->created) && (!empty($this->is_new) || !$this->pid)) {
  874. $this->created = REQUEST_TIME;
  875. }
  876. $this->changed = REQUEST_TIME;
  877. // Clear the static cache from profile2_load_by_user() before saving, so
  878. // that profiles are correctly loaded in insert/update hooks.
  879. $cache = &drupal_static('profile2_load_by_user', array());
  880. unset($cache[$this->uid]);
  881. return parent::save();
  882. }
  883. }
  884. /**
  885. * Use a separate class for profile types so we can specify some defaults
  886. * modules may alter.
  887. */
  888. class ProfileType extends Entity {
  889. /**
  890. * Whether the profile type appears in the user categories.
  891. */
  892. public $userCategory = TRUE;
  893. /**
  894. * Whether the profile is displayed on the user account page.
  895. */
  896. public $userView = TRUE;
  897. public $type;
  898. public $label;
  899. public $weight = 0;
  900. public function __construct($values = array()) {
  901. parent::__construct($values, 'profile2_type');
  902. }
  903. /**
  904. * Returns whether the profile type is locked, thus may not be deleted or renamed.
  905. *
  906. * Profile types provided in code are automatically treated as locked, as well
  907. * as any fixed profile type.
  908. */
  909. public function isLocked() {
  910. return isset($this->status) && empty($this->is_new) && (($this->status & ENTITY_IN_CODE) || ($this->status & ENTITY_FIXED));
  911. }
  912. /**
  913. * Overridden, to introduce the method for old entity API versions (BC).
  914. *
  915. * @todo Remove once we bump the required entity API version.
  916. */
  917. public function getTranslation($property, $langcode = NULL) {
  918. if (module_exists('profile2_i18n')) {
  919. return parent::getTranslation($property, $langcode);
  920. }
  921. return $this->$property;
  922. }
  923. /**
  924. * Overrides Entity::save().
  925. */
  926. public function save() {
  927. parent::save();
  928. // Clear field info caches such that any changes to extra fields get
  929. // reflected.
  930. field_info_cache_clear();
  931. }
  932. }
  933. /**
  934. * View a profile.
  935. *
  936. * @see Profile::view()
  937. */
  938. function profile2_view($profile, $view_mode = 'full', $langcode = NULL, $page = NULL) {
  939. return $profile->view($view_mode, $langcode, $page);
  940. }
  941. /**
  942. * Implements hook_form_FORMID_alter().
  943. *
  944. * Adds a checkbox for controlling field view access to fields added to
  945. * profiles.
  946. */
  947. function profile2_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
  948. if (!empty($form['instance']['entity_type']['#value']) && $form['instance']['entity_type']['#value'] == 'profile2') {
  949. $form['field']['settings']['profile2_private'] = array(
  950. '#type' => 'checkbox',
  951. '#title' => t('Make the content of this field private.'),
  952. '#default_value' => !empty($form['#field']['settings']['profile2_private']),
  953. '#description' => t('If checked, the content of this field is only shown to the profile owner and administrators.'),
  954. );
  955. }
  956. else {
  957. // Add the value to the form so it isn't lost.
  958. $form['field']['settings']['profile2_private'] = array(
  959. '#type' => 'value',
  960. '#value' => !empty($form['#field']['settings']['profile2_private']),
  961. );
  962. }
  963. }
  964. /**
  965. * Implements hook_field_access().
  966. */
  967. function profile2_field_access($op, $field, $entity_type, $profile = NULL, $account = NULL) {
  968. if ($entity_type == 'profile2' && $op == 'view' && isset($profile)) {
  969. // Check if the profile type is accessible (e.g. applicable to the role).
  970. if (!profile2_access($op, $profile, $account)) {
  971. return FALSE;
  972. }
  973. // Deny view access, if someone else views a private field.
  974. if (!empty($field['settings']['profile2_private']) && !user_access('administer profiles', $account)) {
  975. $account = isset($account) ? $account : $GLOBALS['user'];
  976. if ($account->uid != $profile->uid) {
  977. return FALSE;
  978. }
  979. }
  980. }
  981. }
  982. /**
  983. * Implements hook_field_extra_fields().
  984. *
  985. * We need to add pseudo fields for profile types to allow for weight settings
  986. * when viewing a user or filling in the profile types while registrating.
  987. */
  988. function profile2_field_extra_fields() {
  989. $extra = array();
  990. foreach (profile2_get_types() as $type_name => $type) {
  991. // Appears on: admin/config/people/accounts/display
  992. if (!empty($type->userView)) {
  993. $extra['user']['user']['display']['profile_' . $type_name] = array(
  994. 'label' => t('Profile: @profile', array('@profile' => $type->label)),
  995. 'weight' => $type->weight,
  996. );
  997. }
  998. // Appears on: admin/config/people/accounts/fields
  999. if (!empty($type->data['registration'])) {
  1000. $extra['user']['user']['form']['profile_' . $type_name] = array(
  1001. 'label' => t('Profile: @profile', array('@profile' => $type->label)),
  1002. 'description' => t('Appears during registration only.'),
  1003. 'weight' => $type->weight,
  1004. );
  1005. }
  1006. }
  1007. return $extra;
  1008. }
  1009. /**
  1010. * Entity metadata callback to load profiles for the given user account.
  1011. */
  1012. function profile2_user_get_properties($account, array $options, $name) {
  1013. // Remove the leading 'profile_' from the property name to get the type name.
  1014. $profile = profile2_load_by_user($account, substr($name, 8));
  1015. return $profile ? $profile : NULL;
  1016. }
  1017. /**
  1018. * Implements hook_ctools_plugin_directory().
  1019. */
  1020. function profile2_ctools_plugin_directory($owner, $plugin_type) {
  1021. if ($owner == 'ctools' && !empty($plugin_type)) {
  1022. return 'plugins/' . $plugin_type;
  1023. }
  1024. }
  1025. /**
  1026. * Implements hook_preprocess_ctools_context_item_form().
  1027. *
  1028. * When the User context is added, CTools will update the relationship dropdown
  1029. * with ajax. The dropdown is passed through theme_ctools_context_item_form
  1030. * before being passed to ajax_render, so that is our best opportunity to
  1031. * alter it.
  1032. *
  1033. * @see ctools_context_ajax_item_add
  1034. */
  1035. function profile2_preprocess_ctools_context_item_form(&$vars) {
  1036. unset($vars['form']['buttons']['relationship']['item']['#options']['entity_from_schema:uid-user-profile2']);
  1037. }
  1038. /**
  1039. * Determines whether the given user has access to delete a profile.
  1040. */
  1041. function profile2_delete_access($uid, $type_name) {
  1042. $profile = profile2_by_uid_load($uid, $type_name);
  1043. return is_object($profile) ? profile2_access('edit', $profile) : FALSE;
  1044. }
  1045. /**
  1046. * Menu load callback.
  1047. *
  1048. * Returns the profile object for the given user. If there is none yet, a new
  1049. * object is created.
  1050. */
  1051. function profile2_by_uid_load($uid, $type_name) {
  1052. if ($uid && is_numeric($uid) && ($account = user_load($uid))) {
  1053. $profile = profile2_load_by_user($account, $type_name);
  1054. if (!$profile) {
  1055. $profile = profile2_create(array('type' => $type_name));
  1056. $profile->setUser($account);
  1057. $profile->is_new = TRUE;
  1058. }
  1059. return $profile;
  1060. }
  1061. return FALSE;
  1062. }
  1063. /**
  1064. * Implements hook_preprocess_page().
  1065. *
  1066. * Fix the page titles on the profile2 edit tabs.
  1067. * We want the titles to be the full profile label, giving the user name & profile name.
  1068. */
  1069. function profile2_preprocess_page(&$vars) {
  1070. // This is true when editing a profile in a tab.
  1071. if (!empty($vars['page']['content']['system_main']['#user_category'])) {
  1072. $ptype = $vars['page']['content']['system_main']['#user_category'];
  1073. if (!empty($vars['page']['content']['system_main']["profile_$ptype"])) {
  1074. $item = $vars['page']['content']['system_main']["profile_$ptype"];
  1075. // If we've found an item, and it has a profile2 entity, display the title.
  1076. if (!empty($item['#entity'])) {
  1077. $vars['title'] = $item['#entity']->label();
  1078. }
  1079. }
  1080. }
  1081. }