We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 49407
    • 159 Posts
    So far this is what I have...

    See attachments for screen shots.

    Here is the whole of the code in "userattributes.class.php", the database table for it is, (prefix: "ext_") (name: "user_attributes").

    <?php
    class extUser extends modUser {
        function __construct(modX & $modx, array $config= array ()) {
            /* Import modx as a reference */
            $this->modx= & $modx;
            /* Establish the environment */
            $this->extPath= $modx->getOption('core_path',null, MODX_CORE_PATH).'components/extUser/';
            $this->modx->addPackage('extUser', $this->extPath .'model/', 'ut_');
            $this->_config= array_merge(array (
                'userID' => $this->modx->user->get('id'),
            ), $config);
            /* Define the user */
            $this->userObj = $this->setUser($this->_config['userID']);
            $this->userID = $this->userObj->get('id');
        }
        function __destruct() {
            unset ($this->extPath, $this->userObj, $this->userID, $this->_config);
        }
        
        /**
         * Returns object of type extUser.
         */
        public function getExtUser() {
            $this->userObj->getOne('ExtUser');
            return $this->userObj->ExtUser;
        }
        
        /**
         * Returns object utUser instance of modUser Defaults to current user.
         * @param $userID
         */
        public function getUserObj($userID) {
            return $this->modx->getObject('modUser', $userID);
        }
        
        /**
         * Establishes the user.
         * @param int $userID
         */
        public function setUser($userID){
            return $this->getUserObj($userID);
        }
        
        public function sendNotification($type = 'general', $origin = 'system', $message = '') {
            // Get the user's prefered notification method/s
            
            // Classify the notification type
            
        }
    }


    Am I getting close to a working extended user class? I have only one user in the system now. I am still assigned the modUser class key until I am sure that the new extension will work. I noticed that when I changed it to extUser I couldn't authenticate, so I went into the phpMyAdmin and set class_key back to modUser for now.

    I would prefer that the class name was actually extUser, not UserAttributes, but I added the ext_user_attributes table in the database to store 'alias', 'bio', 'followers', 'following', and 'upvotes'. Technically it is extending the user attributes, but the class extends modUser for the sake of adding my custom methods, e.g. sendNotification, and any future methods that may be added.

    For as little experience as I have with Modx this is getting a bit tricky. It will undoubtedly be a highly valuable learning experience in the end. I really do appreciate your help with this, BobRay. Thank you.
    • discuss.answer
      • 3749
      • 24,544 Posts
      I looks OK to me. You might want to add a $userArray or $allUserFields class variable and add a method that fills it with an array of all the user's various fields from the user, user profile, and user_attributes tables. I might slow things down a little, but it should make accessing the fields from the class more convenient.

      It's a good idea during development to check the return value of addPackage() (true or false), to make sure things are getting loaded.

        Did I help you? Buy me a beer
        Get my Book: MODX:The Official Guide
        MODX info for everyone: http://bobsguides.com/modx.html
        My MODX Extras
        Bob's Guides is now hosted at A2 MODX Hosting
        • 49407
        • 159 Posts
        I've added {"extendeduser":{"path":"[[++core_path]]components/extUser/model/"}} to the extension_packages setting in the system, but I am getting an invalid password error when I switch to extUser in the modx_users table, using the stored password that authenticates true when I set it back to modUser.

        Is there something else I am missing?

        Does the name of the class file need to match the package name or something?

        UPDATE:

        The error log shows this...
        [2015-10-06 00:14:39] (ERROR @ /manager/index.php) Could not load class: extUser from mysql.extuser.
        [2015-10-06 00:14:39] (ERROR @ /manager/index.php) Could not load class: extUser from mysql.extuser.
        [2015-10-06 00:14:39] (ERROR @ /manager/index.php) Could not load class: extUser from mysql.extuser. [ed. note: aaronkent last edited this post 11 years ago.]
          • 49407
          • 159 Posts
          After many hours of frustration I have opted to try your classExtender extra. Now that I am using a table, ext_user_attributes, for storing some extUser data I figure it should work.

          What I have done so far...

          1) Removed the map and schema files that I had created manually.
          2) Dropped the table that I had created manually.
          3) Setup the MyExtUserSchema file as follows.
          <?xml version="1.0" encoding="UTF-8"?>
          <model package="extendeduser" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" tablePrefix="ext_" version="1.1">
              <!-- inherit the modx user and extend it -->
              <object class="extUser" extends="modUser">
                      <composite alias="ExtUser" local="id" class="UserAttributes" foreign="id" cardinality="one" owner="local" />
              </object>
              <!-- track all user attributes -->
          	<object class="UserAttributes" table="user_attributes" extends="xPDOSimpleObject">
          		<field key="id" dbtype="int" precision="11" phptype="integer" null="false" />
          		<field key="alias" dbtype="varchar" precision="64" phptype="string" null="false" />
          		<field key="bio" dbtype="text" phptype="string" null="false" />
          		<field key="followers" dbtype="longtext" phptype="string" null="false" />
          		<field key="following" dbtype="longtext" phptype="string" null="false" />
          		<field key="upvotes" dbtype="int" precision="11" phptype="integer" null="false" />
          		
          		<index alias="id" name="id" primary="false" unique="true" type="BTREE">
                      <column key="id" length="" collation="A" null="false" />
                  </index>
                  <aggregate alias="User" class="modUser" local="id" foreign="id" cardinality="one" owner="foreign"/>
                  <aggregate alias="Profile" class="modUserProfile" local="id" foreign="internalKey" cardinality="one" owner="foreign"/>
          	</object>
          </model>

          4) Changed the file MyExtraUserFields to this:
          <div class="x-form-item x-tab-item">
              <label class="x-form-item-label" style="width:auto; font-weight:bold; float:none; font-size:12px">Alias</label>
          
              <div class="x-form-item x-tab-item">
                  <input type="text" name="alias" value="[[+alias]]" class="x-form-text x-form-field"
                          onClick="Ext.getCmp('modx-panel-user').markDirty();"/>
              </div>
          </div>
          
          <div class="x-form-item x-tab-item">
              <label class="x-form-item-label" style="width:auto; font-weight:bold; float:none; font-size:12px">Bio</label>
          
              <div class="x-form-item x-tab-item">
                  <input type="textarea" name="bio" value="[[+bio]]" class="x-form-text x-form-field"
                          onClick="Ext.getCmp('modx-panel-user').markDirty();"/>
              </div>
          </div>

          5) Changed the resource content of "Extend modUser" to this.
          [[!ClassExtender?
              &package=`extendeduser`
              &schemaTpl=`MyExtUserSchema`
              &class=`extUser`
              &parentObject=`modUser`
              &tablePrefix=`ext_`
              &tableName=`user_attributes`
              &dirPermission=`0755`
          ]]

          6) Cleared the extension_packages setting from the system settings.
          7) Viewed the class-extender/extend-moduser resource and clicked Submit.

          Here are the results:

          Generating class and map files
          
          Saving schema
          
          Old class and map files removed
          
          Schema parsed
          
          createObjectContainer() failed


          Error in the log is this:

          [2015-10-06 17:34:12] (ERROR @ /index.php) Could not load class: userData from mysql.userdata.

            • 3749
            • 24,544 Posts
            I'm sorry you're running into trouble after all that work. I've seen that error (though not with ClassExtender), but I don't know what causes it.

            You might try deleting all files in the core/cache directory and running CE again.
              Did I help you? Buy me a beer
              Get my Book: MODX:The Official Guide
              MODX info for everyone: http://bobsguides.com/modx.html
              My MODX Extras
              Bob's Guides is now hosted at A2 MODX Hosting
              • 49407
              • 159 Posts
              I've cleared the core/cache folder just now and I'm still getting that createObjectContainer() failed error. It won't make the database table either.

              I'm stumped.
                • 49407
                • 159 Posts
                BobRay, after writing some debug code into the core file "core/xpdo/om/mysql/xpdomanager.class.php" I am getting this in my log...

                [2015-10-07 01:31:31] (ERROR @ /index.php) xPDO Connection Established. classsName: userData.

                Notice that the $className is being passed to createObjectContainer() as "userData" by classExtender.

                I specified a different className than userData on the form in classExtender, and the schema I setup has a different className.

                Why isn't my specified className being passed correctly from the form in CE to createObjectContainer()?

                Update: See Attachment
                  • 49407
                  • 159 Posts
                  After much troubleshooting I have figured out some things about classExtender that are not mentioned (unless I missed them) in the guide.

                  1) You CAN use any table name you want to extend modUser, but whatever table name you use MUST be the class name you use on the form at class-extender/extend-moduser. For example, if you have a table called ext_user_location, you will use the className 'UserLocation'.

                  2) classExtender checks to see if ce_class's value (the form input field) matches with a schema in classextender/model/packagename, if it doesn't then classExtender will try to use the lowercase prefix* of the parentObject concatenated with the suffix 'Data'. If this happens, you will get the "createObjectContainer() failed" error.

                  3) If you get the "createObjectContainer() failed" error, it's because whatever $className you are sending it does not have a complete schema with fields in the xml file.

                  So, I hope this helps someone else.

                  I do still have a couple questions related to this subject.

                  If I add my aliased composite (ExtUser) to the aggregates in my schema will that load the data into the extUser object?

                  What exactly do these following schema lines do?

                  <aggregate alias="User" class="modUser" local="id" foreign="id" cardinality="one" owner="foreign"/>
                          <aggregate alias="Profile" class="modUserProfile" local="id" foreign="internalKey" cardinality="one" owner="foreign"/>
                  [ed. note: aaronkent last edited this post 11 years ago.]
                    • 3749
                    • 24,544 Posts
                    To answer your last question first, those lines allow you to get the relevant modUser and modUserProfile object when you have the extUser (or whatever you call it) object. Assuming that you have that object in $extUser, you can do this:

                    $profileObj = $extUser->getOne('Profile');
                    $userObj = $exsUser->getOne('User');
                    

                    You can then use $obj->toArray() to create an associative array of the objects' fields, and (optionally) merge them all into one big array (be careful not to overwrite the user ID with the id field of the other objects).

                    Those lines also allow you to get any two or all three of the objects in one query with $modx->getCollectionGraph(), though the syntax is a little tricky until you get used to it.

                    Your other points show an impressive grasp of the process and I appreciate the feedback.

                    CE was primarily written for people who wanted a quick and easy way to get extra user fields or extra resource fields into a single table and show them on the Create/Edit User or Resource panel. I sort of threw in the opportunity to use a more complex schema, custom table names, and so on. TBH, I never really tested much beyond the default values because that wasn't the main purpose of the extra. I assumed that people who wanted more would probably not use CE, which is essentially a wizard, with the usual drawbacks the come with wizards.

                    If you have specific suggestions for changes that won't break the existing functionality, I'd love to hear them, though I'm swamped right now and am working on updates to at least five other extras.



                      Did I help you? Buy me a beer
                      Get my Book: MODX:The Official Guide
                      MODX info for everyone: http://bobsguides.com/modx.html
                      My MODX Extras
                      Bob's Guides is now hosted at A2 MODX Hosting
                      • 49407
                      • 159 Posts

                      Quote from: BobRay at Oct 06, 2015, 11:28 PM
                      To answer your last question first, those lines allow you to get the relevant modUser and modUserProfile object when you have the extUser (or whatever you call it) object. Assuming that you have that object in $extUser, you can do this:

                      $profileObj = $extUser->getOne('Profile');
                      $userObj = $exsUser->getOne('User');
                      

                      You can then use $obj->toArray() to create an associative array of the objects' fields, and (optionally) merge them all into one big array (be careful not to overwrite the user ID with the id field of the other objects).

                      Those lines also allow you to get any two or all three of the objects in one query with $modx->getCollectionGraph(), though the syntax is a little tricky until you get used to it.

                      Your other points show an impressive grasp of the process and I appreciate the feedback.

                      CE was primarily written for people who wanted a quick and easy way to get extra user fields or extra resource fields into a single table and show them on the Create/Edit User or Resource panel. I sort of threw in the opportunity to use a more complex schema, custom table names, and so on. TBH, I never really tested much beyond the default values because that wasn't the main purpose of the extra. I assumed that people who wanted more would probably not use CE, which is essentially a wizard, with the usual drawbacks the come with wizards.

                      If you have specific suggestions for changes that won't break the existing functionality, I'd love to hear them, though I'm swamped right now and am working on updates to at least five other extras.

                      I thinks it's great that you show interest in feedback on your extras. Thanks for your dedication to them. The only suggestion I really have would be more in depth explanation in the docs, but coders don't like to write docs. smiley

                      I was able to get the extUser class working for the purpose of accessing data in the fields of the ExtUser object, but I am struggling to get access to $modx from within my method sendNotification.

                      This...
                      function __construct(modX & $modx, array $config= array ()) {
                                  /* Import modx as a reference */
                                  $this->modx= & $modx;
                                  /* Establish the environment */
                                  $this->extPath= $modx->getOption('core_path',null, MODX_CORE_PATH).'components/extendeduser/';
                                  $this->modx->addPackage('extendeduser', $this->extPath .'model/', 'ut_');
                                  $this->_config= array_merge(array (
                                      'userID' => $this->modx->user->get('id'),
                                  ), $config);
                                  /* Define the user */
                                  $this->userObj = $this->setUser($this->_config['userID']);
                                  $this->userID = $this->userObj->get('id');
                              }

                      Causes:
                      PHP Fatal error: Call to a member function getOption() on a non-object in .../core/xpdo/om/xpdoobject.class.php on line 703

                      This...
                      function __construct(xPDO & $xpdo) {
                              parent::__construct($xpdo);
                              $this->set('class_key','extUser');
                          }

                      Does not allow me to use getObject, newObject, log, etc..

                      How do I use $modx->log, $modx->getObject, etc. in my extended class' method?